20200523のdockerに関する記事は15件です。

Docker版Oracle11gのデータ永続化

Docker 版 Oracle11g のデータ永続化のやり方をメモる。
使用するイメージはこれ:https://registry.hub.docker.com/r/wnameless/oracle-xe-11g-r2

やり方

  1. 空のコンテナを作る。
  2. コンテナからOracleのインストールフォルダをホストにまるごとコピーする。
  3. コンテナを一旦削除。
  4. mount をつけて再度ンテナを作る。

1、ます、コンテナをそのまま作る。

# mkdir oracle11g
# cd oracle11g
# docker run -d -p 49161:1521 \
  -e ORACLE_ALLOW_REMOTE=true \
  -e ORACLE_DISABLE_ASYNCH_IO=true \
  -e TZ=Asia/Tokyo \
  -v /etc/localtime:/etc/localtime:ro \
  --name oracle11g \
  wnameless/oracle-xe-11g-r2 \
&& docker logs -f oracle11g

以下のログが出力される。30秒~1分ぐらいかかるので、気長に待ちましょう。

f6c46d814f7db9cde443c78f122d91524e334e30cf44e5979836a906beae0649
Starting Oracle Net Listener.
Starting Oracle Database 11g Express Edition instance.


System altered.


System altered.

Shutting down Oracle Database 11g Express Edition instance.
Stopping Oracle Net Listener.

Starting Oracle Net Listener.
Starting Oracle Database 11g Express Edition instance.

ログが出たら、接続可能になります。確認して見てください。
Ctrl+c でログ監視を終了させ、docker ps -a で状況確認。

# docker ps -a
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                                       NAMES
f6c46d814f7d        wnameless/oracle-xe-11g-r2     "/bin/sh -c '/usr/sb乧"   4 minutes ago       Up 4 minutes        22/tcp, 8080/tcp, 0.0.0.0:49161->1521/tcp   oracle11g

問題なさそうですね。あとは接続して試しにテーブルを作ったりして確認。(確認手順は省く)

2、コンテナ内のファイルをホスト側にコピーする。

# docker cp oracle11g:/u01/app/oracle .

フォルダの大きさを確認

# du -sh oracle
1.7G    oracle

だったの1.7GB 、素晴らしい!
Oracle 12c は 10GB 以上必要あるから全然こっちの方が良い!!

※ オプション
1つのフォルダぐらいを mount したいから、tmp フォルダを作っておく。特に必要ないなら、やらなくて良い。

3、コンテナを一旦削除、docker ps -a で削除されたことを確認

# docker stop oracle11g && docker rm oracle11g
oracle11g
oracle11g
# docker ps -a
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                               NAMES

4、もう一回コンテナを作るが、今度はmount付き

docker run -d -p 49161:1521 \
  -e ORACLE_ALLOW_REMOTE=true \
  -e ORACLE_DISABLE_ASYNCH_IO=true \
  -e TZ=Asia/Tokyo \
  -v $(pwd)/tmp/:/tmp/ \
  -v $(pwd)/oracle/:/u01/app/oracle \
  -v /etc/localtime:/etc/localtime:ro \
  --name oracle11g \
  wnameless/oracle-xe-11g-r2 \
  && docker logs -f oracle11g

説明すると

-v $(pwd)/tmp/:/tmp/

/tmp フォルダをマウント。ファイルをコンテナに転送する時は便利。

-v $(pwd)/oracle/:/u01/app/oracle

1回目で作ったコンテナのOracleフォルダをマウント。これでデータ永続化可能なる。

-v /etc/localtime:/etc/localtime:ro

timezone をホスト機と同じにする。

再度接続したら、先程確認で作ったテーブルが存在するはず。

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナとハイパーバイザーの違いとは?

はじめに

今回は、コンテナとハイパーバイザーの違いについてアウトプットを込めて解説していきたいと思います。
この違いは、LPIC304にも試験範囲として出題されていました。
そのため、これから受験しようと思う人には参考になる記事だと思います。

コンテナ(container)とは何か?

1つのOSの中にアプリケーションを格納する箱を複数用意するという考え方の仮想化技術になります。

図にするとこのような形になります。

container.JPG

特徴

  • とにかく処理が軽量
  • 一つのOS内でアプリケーションを動作させることができる
  • コンテナ設定の再利用が可能

代表的なコンテナ技術

  • docker
  • Linuxコンテナ

ハイパーバイザー(Hypervisor)とは何か?

ハイパーバイザーとは、コンピューターを仮想化するためのソフトウェアです。

図にするとこのような形になります。

hypervisor.JPG

ハイパーバイザーという仮想化ソフトウェアの上に複数のOS(仮想マシン)が乗っているという構成になります。

特徴

  • ハイパーバイザーの上に複数の仮想マシンを立てることができる
  • それぞれ別のOSでサーバーを立てることができる

代表的なハイパーバイザー技術

  • KVM
  • OpenStack
  • vSphere
  • Hyper-V
  • Xen

コンテナとハイパーバイザーの違いは?

アプリケーション起動速度

コンテナの方が高速です。

なぜなら、OSを一回一回立ち上げる必要がないからです。

試験的にdockerを構築後に「nginx」を導入したことがあります。
実際に「nginx」を起動させてみました。
結果、vSphere上でOSを起動させるよりも圧倒的に早かったです。(30秒程)

このように、アプリケーション起動速度はコンテナの方が早いです。

OSのカスタマイズ性

こちらは、ハイパーバイザーの方が優れています。

なぜなら、コンテナ型は使用するOSが限られているからです。
一方、ハイパーバイザーはOS毎に箱が作成されます。

「一方のサーバーのOSはLinuxにして、他はWindowsにする」
このような要件であれば、ハイパーバイザーの方が優れています。

このように、OSカスタマイズ性を考えるとハイパーバイザーの方が使いやすいです。

参考記事

用語集|ハイパーバイザー

Dockerとは何かを入門者向けに解説!基本コマンドも

VPSの二大仮想化技術「コンテナ型」vs「VM型」―― その違いとメリット・デメリット

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerfileでRUN sudo〜をつかうとbuildに失敗するときの対処

Dockerfileをbuild中・・

以下のエラーが発生しました。
翻訳すると「sudo: tty が存在せず、askpass プログラムが指定されていない」とのこと。

sudo: no tty present and no askpass program specified

エラーの箇所は・・?

rootユーザからhostと同名のユーザに切り替えて、
sudoを使ってコマンドを実施している部分です。

〜〜〜〜上記略
USER $HOME_USER

RUN sudo apt-get    install -y  \
〜〜〜〜以下略

原因・対応

原因を調べたところ、sudoするときにpassword要求されていることにあるらしいです。
なので、sudoersにpasswordなしの設定を加えることにしました。

コンテナの中の/etc/sudoersをDockerfileと同じ階層にコピーして、最終行に以下を追記します。
(myusernameは$HOME_USERの値)

# /etc/sudoerの最終行に以下を追記
myusername  ALL=(ALL) NOPASSWD: ALL

DokerfileにADD sudoers /etcを追記

〜〜〜〜上記略
ADD sudoers /etc
USER $HOME_USER

RUN sudo apt-get    install -y  \
〜〜〜〜以下略

これで解決・・?

これでsudoするときにパスワードが不要になり、buildし直すとエラー自体は発生しなくなりました。

注意

ただし、個別ユーザにsudoするときに全権を与えるのでセキュリティ的には危険です。
さらに気になって調べた所、Dockerfile のベストプラクティスのUSERの項目に以下の記載がありました。

TTY やシグナル転送を使わないつもりであれば、 sudo のインストールや使用を避けたほうが良いでしょう。使うことで引き起こされる問題の解決は大変だからです。もし、どうしても sudo のような機能が必要であれば(例:root としてデーモンを初期化するが、実行は root 以外で行いたい時)、 「 gosu 」を使うことができます。

結論

というわけで、Dockerfile内ではsudoは使わずに、厳密にUSERコマンドで適宜切り換えていくやり方がいいかもしれません。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker / ECR / ECS コンテナ入門まとめ③

前編

参考文献

前準備

◆ Dockerインストール

$ sudo yum update -y

$ sudo amazon-linux-extras install docker

◆ Docker起動

$ sudo systemctl start docker

◆ dockerグループにユーザ追加

$ sudo usermod -a -G docker ec2-user

◆ dockerアクセス確認

$ docker info

ECR編

◆ ECRログイン

$ aws ecr get-login --no-include-email  ※出力される長いコマンドでログイン

◆ ECRリポジトリ作成

$ aws ecr create-repository --repository-name sample  ※リポジトリ名

◆ 環境変数セット

$ export registryId=[出力されたレジストリId]
$ export region=ap-northeast-1  ※リージョン名
$ export imagename=sample
$ export tag=ver1

◆ Dockerイメージ作成

$ docker build -t ${imagename}:${tag} .

◆ Dockerイメージtag付け

$ docker tag ${imagename}:${tag} ${registryId}.dkr.ecr.${region}.amazonaws.com/${imagename}:${tag}

◆ Dockerイメージプッシュ

$ docker push ${registryId}.dkr.ecr.${region}.amazonaws.com/${imagename}:${tag}

ECS編

◆ IAMロールの作成

  • 「AmazonEC2ContainerServiceforEC2Role」ポリシーの付与
ecs-role
{
  "Version": "2020-5-23",
  "Statement": [
    {
      "Action": "ecs:*",
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

◆ クラスター作成コマンド

$ export region=us-east-1
$ export cluster=sample-cluster
$ aws ecs create-cluster --region ${region} --cluster-name ${cluster}
$ aws ecs list-clusters --region ${region} --output json

◆ ec2作成

  • AMI選択:ami-34ddbe5c
  • IAMロール:上記のecs-roleを使用
  • ユーザデータ:下記入力
userdata
#!/bin/bash
echo ECS_CLUSTER=[クラスター名] >> /etc/ecs/ecs.config

◆ ECS/EC2紐付け確認

$ aws ecs list-container-instances --cluster ${cluster} --output json --region ${region}
$ ssh ec2-user@XX.XX.XX.XX    ※ec2ログイン
$ sudo docker ps

ECSタスク登録・実行

$ vim sample.json
sample.json
[
  {
    "environment": [],
    "name": "sample",
    "image": "sample-image",
    "cpu": 10,
    "portMappings": [],
    "entryPoint": [
      "/bin/sh"
    ],
    "memory": 10,
    "command": [
      "sample",
      "360"
    ],
    "essential": true
  }
]
■ タスク登録コマンド
$ aws ecs register-task-definition --family sample --container-definitions file://sample.json --region ${region} --output json
■ タスク確認
$ aws ecs list-task-definitions --region ${region}
■ タスク実行コマンド
$ aws ecs run-task --cluster ${cluster} --task-definition sample:1 --count 1 --region ${region} --output json
■ タスク実行確認
$ sudo docker ps
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

素人基盤エンジニアがDockerでDjangoを触るシリーズ⑤:View, Template, CSS

TL;DR

素人基盤エンジニアがDockerでDjangoを触るシリーズ④:urls.pyの続き。
1から読みたい場合は↓こちら。
素人基盤エンジニアがDockerでDjangoを触るシリーズ①:DockerでDjangoを作る
今回もDjango Girlsのサンプルを作りながらdjangoと戯れる。
viewでは、modelを介して受け取ったデータをTemplateに渡したり、その他処理を行うことができる。
Templateは見た目を整える部分。htmlやCSSなどはこのTemplateにあたる。
まずは、シンプルなViewを作ることでその構造を見ていく。

view

①views.pyを編集する

views.py

views.py
from django.shortcuts import render
from django.utils import timezone
from .models import Post

def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'posts': posts})

よくわかる解説

from django.shortcuts import render

shortcuts関数は、DjangoにおけるMTVの各レベルを橋渡しするのに必要な関数やクラスを定義している。今回は、引数として指定したtemplateをレンダしてHttpResponseを返すrenderという関数をインポートしている。

from django.utils import timezone
from .models import Post

ユーティリティ関数に含まれている時間に関する機能を提供するtimezoneと、モデルの作成の時に作成したPostのモデルをインポートしている。Viewの立ち位置については第二回の時にも触れたが、モデル関数を利用して実際にデータを取得し、Templateに渡す役割もあるので、利用したいモデルはViewの中でインポートし、Templateのオプションとして引き渡す必要がある。

def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'posts': posts})

post_list関数の定義部分。前回のurls.pyを記述した際に、views.post_listを指定したのは覚えているだろうか。urls.pyへリクエストが来た際に、パスに応じて指定したviews.pyの中の関数が呼び出されリクエストが転送される。そして、必要な処理を行った後にTemplateをレンダリングしてレスポンスを返すということである。二行目では、Postモデルを使ってpublished_dateが現在の時間よりも前の投稿を取得し、新しい順に並び変えた上でblog/post_list.htmlに渡している。

Template

①html用のディレクトリを作成する

cd ~/blog/template
mkdir blog

アプリケーションのディレクトリであるblogディレクトリの下に、templateというディレクトリがあるので、その下にさらにblogというディレクトリを作成する。
なぜこんなややこしい構造にするのかと思うかもしれないが、大規模なアプリケーションを管理しようとするとhtmlに関しても機能ごとにディレクトリを分けておいたほうがよいらしい。

②post_list.htmlを編集する

post_list.html

blog/template/blog/post_list.html
{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>

        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    {% for post in posts %}
                        <div class="post">
                            <div class="date">
                                <p>published: {{ post.published_date }}</p>
                            </div>
                            <h2><a href="">{{ post.title }}</a></h2>
                            <p>{{ post.text|linebreaksbr }}</p>
                        </div>
                    {% endfor %}
                </div>
            </div>
        </div>
    </body>
</html>

HTMLの基本的な部分を解説するのは主旨とずれるので、ここでは重要な部分のみを解説する。

よくわかる解説

{% load static %}

{% load static %}は後述するCSSファイルのような、静的コンテンツを読み込むための記述。

        <link rel="stylesheet" href="{% static 'css/blog.css' %}">

ここで読み込むCSSを指定している。

                    {% for post in posts %}
                        <div class="post">
                            <div class="date">
                                <p>published: {{ post.published_date }}</p>
                            </div>
                            <h2><a href="">{{ post.title }}</a></h2>
                            <p>{{ post.text|linebreaksbr }}</p>
                        </div>
                    {% endfor %}

この部分で注目したいのは、{{ post.OBJECT }}でPostモデルの項目を指定している部分。Postモデルを利用して取得した投稿に関しては、前述したviews.pyの中でこのTemplateに渡されているため、このように簡単に呼び出せるということだ。Postモデルで取得した投稿はリストの形になっているため、for文を利用して複数の表示を実現することもできる。

③CSS用のディレクトリを作成する

cd ~/blog/static
mkdir css

④blog.cssを編集する

blog/static/css/blog.css
.page-header {
    background-color: #C25100;
    margin-top: 0;
    padding: 20px 20px 20px 40px;
}

.page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active {
    color: #ffffff;
    font-size: 36pt;
    text-decoration: none;
}

.content {
    margin-left: 40px;
}

h1, h2, h3, h4 {
    font-family: 'Lobster', cursive;
}

.date {
    color: #828282;
}

.save {
    float: right;
}

.post-form textarea, .post-form input {
    width: 100%;
}

.top-menu, .top-menu:hover, .top-menu:visited {
    color: #ffffff;
    float: right;
    font-size: 26pt;
    margin-right: 20px;![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369619/5f4c27cb-8697-0236-f655-2ec2beaf3805.png)

}

.post {
    margin-bottom: 70px;
}

.post h2 a, .post h2 a:visited {
    color: #000000;
}

CSSの解説についてはさらに本筋ではないので、形を整えるためにはこんなものを書くんだというくらいの認識でコピペいただければと思う。HTML, CSSに関してはそれ専用の本などで勉強されることをお勧めする。

ブログ表示用サイトの起動

docker-compose up

ここまでで、(表示をするのみだが)webサイトが完成しているはずである。
dockercompose.yamlが配置されているディレクトリまで移動して、docker-compose upを使って実際に起動してみよう。

docker-compose up

http://[IPADDRESS]:8000/にアクセスしてみる。

006.png

表示された!
ちなみにThis is test postという投稿は、第三回で説明したadminページを利用して投稿したもの。ユーザがポストを行うには、投稿フォームを備えたページを作成する必要がある。フォームを作成するにあたって、Djangoはformsという便利な機能を提供している。その話はまた次回。

まずは一区切りといった感じだろうか。。

つづく

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeでリモートホスト上のDocker上で開発する

経緯

開発環境がローカルマシンの環境に依存するのって嫌ですよね。
というわけで最近はもっぱらDocker上に開発環境を構築して、VSCodeのRemote-Containersで開発しています。

ただ、使ってるメインマシンがWindowsなのでDockerの使い勝手がいまいち良くない。。。

そこで、押入れサーバの仮想環境(ESXi)上にDocker専用のホストを立てて、そこにWinマシンのVSCodeから接続して開発できるようにしたかったわけです。

ESXi上にDocker用のホストOSをインストールする

DockerのホストOSにはPhotonOSを使います。
VMWareが提供している、VMware vSphereに最適化された最小限のLinuxコンテナホストです。

こんなに、自分の要望をどストライクで満たすOSがあるとはびっくりです。

PhotonOSのダウンロード

ここからOVAファイルをダウンロードしてきます。
今回はOVA with virtual hardware v13 (UEFI Secure Boot)を選択しました。

ESXiにPhotonOSの仮想マシンを作成

「OVFファイルまたはOVAファイルから・・・」を選択
image.png

ダウンロードしたOVAファイルを選択
image.png
image.png

PhotonOSの設定

root / changeme でログイン。パスワードの変更を求められるので対応する。

# アップデート
tdnf update -y

# tarのインストール
tdnf install -y tar

# SSHポートフォワードの許可
vi /etc/ssh/sshd_config
> AllowTcpForwarding yes
systemctl restart sshd

# 一般ユーザの作成
useradd -m -g docker -G wheel user
passwd user

# dockerの自動起動設定
systemctl enable docker
systemctl start docker

# docker動作確認
docker run --rm hello-world

VSCodeの設定

前提条件

  • Windows環境
  • Dockerがインストールされている(クライアントだけ使うのでDockerホストは起動していなくてもOK)
  • VSCodeに以下の拡張が入っている
    • Docker(ms-azuretools.vscode-docker)
    • Remote Development(ms-vscode-remote.vscode-remote-extensionpack)

SSH接続の設定 1

https://code.visualstudio.com/docs/containers/ssh

公開鍵を登録してパスワードなしで接続できるようにする

接続元(windows)
# 鍵ペアの生成 [^1]
ssh-keygen -t ecdsa -b 521

# 公開鍵をリモート側に転送
scp .ssh/id_ecdsa.pub user@remote-host:
接続先(photon)
# ローカル側の公開鍵を登録
cat id_ecdsa.pub >> .ssh/authorized_keys
rm id_ecdsa.pub

管理者権限で以下実行してssh-agentサービスが自動起動するようにする

sc config ssh-agent start=auto
net start ssh-agent
ssh-add .ssh\id_ecdsa

Dockerのリモートホストの設定

https://code.visualstudio.com/docs/remote/containers-advanced#_developing-inside-a-container-on-a-remote-docker-host

VSCodeの設定からdocker.hostにDockerホストサーバへの接続情報を設定

ssh://user@remote-host

うまく行けば、リモートホスト側のDockerの情報が表示されます。
image.png

Remote-Containerでのファイル共有設定

残念ですが、Dockerはリモートサーバ上にいるので、ローカルのファイルをDocker側に共有することはできません。
ローカルからファイルをコピーしたい場合はSCPなどで転送するのがよいでしょう。

NAS上にプロジェクトを作ってしてしまうのもありかと思います。

リモートホスト側のフォルダにDocker内のファイルを共有する場合

https://code.visualstudio.com/docs/remote/containers-advanced#_developing-inside-a-container-on-a-remote-docker-host

ローカルマシン側のdevcontainer.jsonの共有設定を以下のようにする。

"workspaceMount": "source=/absolute/path/on/remote/machine,target=/workspace,type=bind,consistency=cached"

所感

やりたいことがうまく実現できて満足。

開発に使うリソースはすべてサーバ上で完結し、ローカルの環境にはVSCodeとDockerのクライアントのみでよいのでスペックの低いマシンでも存分に開発ができます。

例えば、ノートとデスクトップの2台を持っている場合、どちらからでも同じ開発環境が使えるわけです。

これって、かなり素晴らしいと思うのでみなさんも


  1. Windows 10 build 1909 以前の場合、RSAではなくECDSAをつかう必要がある
    https://code.visualstudio.com/docs/containers/ssh#_tips 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

windows10 HomeでDockerを使う

プロローグ

dockerを使いたいのですが、自宅環境がWindows10 homeです。
通常のdocker for windowsはHyper-vが必要ですが、windows10 homeにはhyper-vが導入されていません。
調べるとdocker toolboxを利用するとwindows10 homeでもdockerが使えるようです。

docker toolboxの導入

docker toolboxの導入方法はこちらの公式に記載がありますので、参考にします。
1.png

exeファイルをGithubからダウンロードするようです。
2.png

exeを実行します。
3.png

インストール先を指定します。
4.png

インストール対象を選択します。(ついでにGitを入れました)
5.png

セットアップの設定です。デフォルトにしました。
6.png

内容に問題がなければそのまま「Install」を押下します。
7.png

確認メッセージがでましたが、そのまま「OK」を押下しました。
8.png

Finishを押下します。
9.png

インストールすると「Docker Quickstart Terminal」というショートカットが作成されました。
実行します。
10.png

5分ほどでクジラの絵が表示されたら導入完了です。
11.png

動作確認

公式が用意している動作確認用のHello-worldコンテナを呼び出してみます。
Hello from Docker!と表示されたら完了です。

docker run hello-world

12.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+Webスクレイピング+DockerのアプリをEC2で立てようとしたら、デフォルト設定だとt2.small(2GB)とかが必要になりそう

※情弱なのでデフォルトのメモリ設定をしてしまってますが、もしかしたらDockerのメモリ割り当てをうまくやればもっと小さいサイズでいけるかもしれません。

概要

dockerでWebサイト(Rails + Webスクレイピング)をAWSのEC2で立てたいと思っていたんですが、自分しか使わないような遊びサイトだったので、できるだけ安い金額で立てられたらと思っていたんですが、見事に撃沈した話です。

結論

デフォルト設定(Docker、Rails)だとt2.smallを使わないと、bundle installnokogiriを入れるときにメモリエラーになります。

この記事のようにDockerを動かすだけならt2.nanoでも動きます。
※t2.smallならスクレイピング(docker-selenium)も動きました

詳細

どんな環境?

Docker: 19.03.6-ce
docker-compose: 1.25.5
ruby:2.5.3 (Dockerhubのruby:2.5.3のイメージを使用)
Rails: 5.2.3

AWSにDockerを入れるのにはこちらを参考にさせてもらいました。

どんなことが起きたの?

t2.nanoとt2.microのdocker環境内でbundle installをしたら以下エラーが起きました。
(どうでもいいですが、ローカル環境で動いていたDockerfileでbundle installしたときに以下エラーが起き、まだイメージが出来上がる前だったので、わざわざDockerfileのbundle installやdocker-compose.ymlのコマンドからbundle installやそれに依存したrails sをコメントアウトしてdocker-compose.ymlでtail -f Gemfileをしてコンテナ延命した状態で以下のように独立してbundle installを実行しました。docker-composeに慣れてdocker runが打てない人間の悲しい性です)

# bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from http://rubygems.org/............
Fetching rake 12.3.3
Installing rake 12.3.3
Fetching concurrent-ruby 1.1.5
Installing concurrent-ruby 1.1.5
Fetching i18n 1.6.0
Installing i18n 1.6.0
Fetching minitest 5.11.3
Installing minitest 5.11.3
Fetching thread_safe 0.3.6
Installing thread_safe 0.3.6
Fetching tzinfo 1.2.5
Installing tzinfo 1.2.5
Fetching activesupport 5.2.3
Installing activesupport 5.2.3
Fetching builder 3.2.3
Installing builder 3.2.3
Fetching erubi 1.8.0
Installing erubi 1.8.0
Using mini_portile2 2.4.0
Fetching nokogiri 1.10.3
Installing nokogiri 1.10.3 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri
/usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/2.5.0 -r
./siteconf20200523-12-t3a56p.rb extconf.rb
checking if the C compiler accepts ... yes
Building nokogiri using packaged libraries.
Using mini_portile version 2.4.0
checking for gzdopen() in -lz... yes
checking for iconv... yes
************************************************************************
IMPORTANT NOTICE:

Building Nokogiri with a packaged version of libxml2-2.9.9
with the following patches applied:
        - 0001-Revert-Do-not-URI-escape-in-server-side-includes.patch
        - 0002-Remove-script-macro-support.patch
        - 0003-Update-entities-to-remove-handling-of-ssi.patch

Team Nokogiri will keep on doing their best to provide security
updates in a timely manner, but if this is a concern for you and want
to use the system library instead; abort this installation process and
reinstall nokogiri as follows:

    gem install nokogiri -- --use-system-libraries
        [--with-xml2-config=/path/to/xml2-config]
        [--with-xslt-config=/path/to/xslt-config]

If you are using Bundler, tell it to use the option:

    bundle config build.nokogiri --use-system-libraries
    bundle install

Note, however, that nokogiri is not fully compatible with arbitrary
versions of libxml2 provided by OS/package vendors.
************************************************************************
Extracting libxml2-2.9.9.tar.gz into
tmp/x86_64-pc-linux-gnu/ports/libxml2/2.9.9... OK
Running git apply with
/usr/local/bundle/gems/nokogiri-1.10.3/patches/libxml2/0001-Revert-Do-not-URI-escape-in-server-side-includes.patch...
OK
Running git apply with
/usr/local/bundle/gems/nokogiri-1.10.3/patches/libxml2/0002-Remove-script-macro-support.patch...
OK
Running git apply with
/usr/local/bundle/gems/nokogiri-1.10.3/patches/libxml2/0003-Update-entities-to-remove-handling-of-ssi.patch...
OK
Running 'configure' for libxml2 2.9.9... OK
Running 'compile' for libxml2 2.9.9... OK
Running 'install' for libxml2 2.9.9... OK
Activating libxml2 2.9.9 (from
/usr/local/bundle/gems/nokogiri-1.10.3/ports/x86_64-pc-linux-gnu/libxml2/2.9.9)...
************************************************************************
IMPORTANT NOTICE:

Building Nokogiri with a packaged version of libxslt-1.1.33
with the following patches applied:
        - 0001-Fix-security-framework-bypass.patch

Team Nokogiri will keep on doing their best to provide security
updates in a timely manner, but if this is a concern for you and want
to use the system library instead; abort this installation process and
reinstall nokogiri as follows:

    gem install nokogiri -- --use-system-libraries
        [--with-xml2-config=/path/to/xml2-config]
        [--with-xslt-config=/path/to/xslt-config]

If you are using Bundler, tell it to use the option:

    bundle config build.nokogiri --use-system-libraries
    bundle install
************************************************************************
Extracting libxslt-1.1.33.tar.gz into
tmp/x86_64-pc-linux-gnu/ports/libxslt/1.1.33... OK
Running git apply with
/usr/local/bundle/gems/nokogiri-1.10.3/patches/libxslt/0001-Fix-security-framework-bypass.patch...
OK
Running 'configure' for libxslt 1.1.33... OK
Running 'compile' for libxslt 1.1.33... OK
Running 'install' for libxslt 1.1.33... OK
Activating libxslt 1.1.33 (from
/usr/local/bundle/gems/nokogiri-1.10.3/ports/x86_64-pc-linux-gnu/libxslt/1.1.33)...
checking for -llzma... yes
checking for xmlParseDoc() in libxml/parser.h... yes
checking for xsltParseStylesheetDoc() in libxslt/xslt.h... yes
checking for exsltFuncRegister() in libexslt/exslt.h... yes
checking for xmlHasFeature()... yes
checking for xmlFirstElementChild()... yes
checking for xmlRelaxNGSetParserStructuredErrors()... yes
checking for xmlRelaxNGSetParserStructuredErrors()... yes
checking for xmlRelaxNGSetValidStructuredErrors()... yes
checking for xmlSchemaSetValidStructuredErrors()... yes
checking for xmlSchemaSetParserStructuredErrors()... yes
creating Makefile

current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri
make "DESTDIR=" clean
current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri
make "DESTDIR="
make failedCannot allocate memory - make "DESTDIR="

Gem files will remain installed in /usr/local/bundle/gems/nokogiri-1.10.3
for inspection.
Results logged to
/usr/local/bundle/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out

An error occurred while installing nokogiri (1.10.3), and Bundler
cannot continue.
Make sure that `gem install nokogiri -v '1.10.3' --source
'http://rubygems.org/'` succeeds before bundling.

In Gemfile:
  rails was resolved to 5.2.3, which depends on
    actioncable was resolved to 5.2.3, which depends on
      actionpack was resolved to 5.2.3, which depends on
        actionview was resolved to 5.2.3, which depends on
          rails-dom-testing was resolved to 2.0.3, which depends on
            nokogiri

長すぎて何がエラーかわからないので、見ろと言われたログを見てみます

# cat /usr/local/bundle/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out
current directory: /usr/local/bundle/gems/nokogiri-1.10.3/ext/nokogiri
/usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/2.5.0 -r ./siteconf20200523-53-7krva6.rb extconf.rb
extconf failedCannot allocate memory - /usr/local/bin/ruby

ここでメモリ不足だとわかりましたorz
そりゃ500MBでdockerだもんな

まとめ

月2000円は痛いんですが、仕方ないかぁといった感じです。
誰か同じ状況の人やAWSに関しての意思決定で同じ問題意識を持った人の一助になれば嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSL 2 対応 Docker Desktop for Windowsを使うための手順

概要

Windows 10 で WSL 2 (Windows Subsystem for Linux)の動作要件を満たしている状態で、Docker Desktop を使えるようにするまでの流れを整理しました。ざっくり書きますと「Your system supports WSL 2!」が表示された時、WSL 2 に移行するための手順です。

image.png

動作環境を満たすと、Docker Desktop 起動時、この表示が出ます。

※ 動作環境は、Winddows 10 Inside Program 等で動作要件を満たすか、あるいは GA 版の提供を待つ必要があります。
※ WSL1 や WSL を使わないバージョンからのデータは自動的に移行されません。

この投稿は、公式の WSL 2 ドキュメント を読んでも、このままでは Docker for Desktop を動かすに至らなかったので、状況整理のために作成しました。

動作要件

WSL2 が動作する Windows 10 ( ※詳細

  • バージョン 2004、ビルド 19041 以上に更新された Windows 10 を実行している。

本原稿執筆時点では、 Inside Program に加入する必要があります。

手順

全く WSL に対応していないシステムで、WSL 2 対応の Docker Desktop for Windows を使うための手順です。

動作要件を満たすかどうかの確認

Docker Desktop for Windows を起動し、次のウインドウが自動的に出ることを確認します。おそらく、システムが対応すると、意図せず次の画面が出ます。

image.png

もし、データ移行の準備が出来ていなかったり、WSL 2 に対する理解が十分でなければ「Now now」をクリックし、現時点では WSL 2 に移行すべきではありません。

ここで「Enable WSL 2」(WSL 2)を有効化するをクリックすると、システム状態のチェックに入ります。デフォルトの Windows 10 では WSL は入っていないため、「WSL 2 is not installed」(WSL 2 はインストールされていません)と表示が出ています。

image.png

ここでセットアップ作業を進行するには「 Stop Docker 」をクリックします。

WSL 2 のセットアップ

ここは基本的に 公式ドキュメント の通り作業をします。

「PowerShell」を管理者として実行します。Windows ボタンを押し、「PowerShell」と入力すると実行するプログラム候補が自動的に出ますので、「管理者として実行」をクリックします。

image.png

次に PowerShell が起動します。

WSL をセットアップするコマンド dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
を実行します。

image.png

公式ドキュメントでは「wsl --set-default-version 2」の実行とありますが、

PS C:\WINDOWS\system32> wsl --set-default-version 2
wsl : 用語 'wsl' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。
名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください
。
発生場所 行:1 文字:1
+ wsl --set-default-version 2
+ ~~~
    + CategoryInfo          : ObjectNotFound: (wsl:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

このように wsl のパスが通っていない旨の表示が出ます。

PC を再起動します。

再起動後 WSL 2 Linux カーネルの更新が必要です( 詳細 )。

マイクロソフトのドキュメントを確認します。ウェブブラウザで、次の URL を開きます。

この中の「x64 マシン用の最新の WSL2 Linux カーネル更新プログラム パッケージをダウンロードしてください。」のリンク先をクリックします。

「wsl_update_x64.msi」のダウンロードが開始しますので、ファイルを保存して、ダウンロードが終わったら実行します。

以上が WSL 2 のシステムまわりの設定です。

Linux ディストリビューションのセットアップ

次は linux ディストリビューションを Windows 10 にセットアップします。Windows 10 では「アプリ」として Linux ディストリビューションが配布されています。WSL 2 で Docker を使うには、Ubuntu など Linux ディストリビューションをセットアップする必要があります( 詳細 )。

まずは、 Microsoft Store を開きます。

image.png

それから「Ubuntu 」を選択し、「入手」をクリックします。

image.png

ダウンロードが完了すると、 Windows のスタートメニューをクリックし、「Ubuntu」と入力します。

image.png

暫く、次の表示が出るまで待ちます。

Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username:

ここで、セットアップする Ubuntu のユーザ名とパスワードを指定します。

Enter new UNIX username: docker
New password:
Retype new password:

ここでは docker と入力し(何でも構いません)、次に、パスワードを設定しています。

そうすると、Ubuntu にログインできます。

Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 20.04 LTS (GNU/Linux 4.19.84-microsoft-standard x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat May 23 12:25:38 JST 2020

  System load:  0.13               Processes:             8
  Usage of /:   0.4% of 250.98GB   Users logged in:       0
  Memory usage: 0%                 IPv4 address for eth0: 172.29.185.157
  Swap usage:   0%

0 updates can be installed immediately.
0 of these updates are security updates.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


This message is shown once once a day. To disable it please create the
/home/docker/.hushlogin file.

この状態では特にすることもないので、 exit を入力し、終了します。

PowerShell で WSL2 の設定

それから、再び PowerShell で管理者モードとして、実行するべき WSL のバージョンを 2 と明示します。

wsl --set-default-version 2

Docker Desktop の確認

あとは Docker Desktop を起動します。

画面上、「Skip tuotrial」を選び、起動した画面の右から3つめに「歯車」のマークがあり、そこをクリックすると User the WSL 2 based engine にチェックが入っていれば、対応完了です。もし入っていなければ、チェックをいれます。

image.png

この状態で、Docker は PowerShell 上で使えますので、「dokcer pull hello-world」や「docker run hello-world」などが使えます。

image.png

参考文章

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでApache + Laravel の環境を構築する時にNot Foundで泣いた話

この記事の対象者

  • DockerでApache + Laravelの環境を作りたい初心者(←重要)

  • Laravelで開発してるけど青売りケーションの仕組みを実はよくわかっていない初心者

  • DockerもLaravelも長年使っていて未熟者のワシにアドバイスくれる先輩

ソースコードはここに載せてます
https://github.com/maip0902/DockerApachePHP

環境

MacOS
Docker 19.03.2

なんで泣いたのか

Laravel + nginx の構成でしか開発を行ったことがなく、初めてApacheで構築しようとしたら

スクリーンショット 2020-05-23 1.23.23.png

ドキュメントルート違ってるやーん ><
ということでこれを直します
せっかくなのでどういう仕組みでアプリケーションの画面が表示されるかにも少し触れながら環境構築を行います〜

ディレクトリ構成

apache-laravel-app という名前のプロジェクトを作成するものとして、最終的に以下のようなディレクトリ構造になります。

  ├ apache-laravel-app   // Laravelで作られるファイル
  |
  ├ ── php 
  |     └ php.ini // なくてもいい  
  |
  ├ Dockerfile
  |
  ├ docker-compose.yml

Dockerfile

今回使うイメージはphp:7.2-apacheです。

Dockerfile
# ①
FROM php:7.2-apache

# ②ドキュメントルートをデフォルトから修正
ENV APACHE_DOCUMENT_ROOT /var/www/html/apache-laravel-app/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

# php.iniを独自で設定するときだけ
COPY ./phhp/php.ini /etc

# ③モジュールのinstall
RUN apt-get update \
  && apt-get install -y zlib1g-dev libpq-dev unzip git vim curl\
  && docker-php-ext-install pdo_mysql zip

# ④Composer入れる
# マルチステージビルドを採用cpm
COPY --from=composer /usr/bin/composer /usr/bin/composer

① 公式からイメージを持ってくる

FROM php:7.2-apache

php:(version)-apacheを使うとapacheにphpのモジュールが入っている状態を持ってこれます。

② ドキュメントルートの修正

ENV APACHE_DOCUMENT_ROOT /var/www/html/apache-laravel-app/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

今回のポイント。
apacheの設定ファイルは /etc/apache2 配下にあるのですが、その中でもドキュメントルートを指定しているファイルがあって、その部分のみ環境変数で置き換えるということをしました。Apche起動後にドキュメントルート書き換えたり、独自の.confを使って

RUN rm /etc/apache2/sites-available/000-default.conf
COPY ./000-default.conf etc/apache2/sites-available

ともできるんですけど(最初自分もこれ考えましたが)環境変数にぶっこんだ方がスマートですね。
まあ公式に書いてあったんですけどねw ⬇️
https://hub.docker.com/_/php

sedコマンドに関しては以下参照。
sedでこういう時はどう書く?

また、なぜドキュメントルートが/var/www/html/apache-laravel-app/publicなのかという話ですが、これはLaravelのプロジェクトを作るとプロジェクト内のpublic配下にindex.phpが作られるためです。また、Apacheの設定で一番最初に見に行くファイルがindex.phpになっています。
実際にindex.phpと書かれているファイルを検索して見てみます。

root@be20b535a937:/var/www/html# grep index.php -rl /etc/apache2
/etc/apache2/mods-available/dir.conf # 利用可能なモジュールのディレクトリに関するファイル
/etc/apache2/conf-available/docker-php.conf # phpが読み込まれた時の処理に関するファイル

ということでphpの処理がApacheでどう行われているかを以下のコマンドで見ます。

root@be20b535a937:/var/www/html# less /etc/apache2/conf-available/docker-php.conf

<FilesMatch \.php$>
        SetHandler application/x-httpd-php
</FilesMatch>

DirectoryIndex disabled
DirectoryIndex index.php index.html

<Directory ${APACHE_DOCUMENT_ROOT}>
        Options -Indexes
        AllowOverride All
</Directory>

DirectryIndexは最初に見に行くファイルで、それにindex.phpが指定されていることがわかりました。Laravelはindex.phpから始まるので、このファイルが置いてある /var/www/html/プロジェクト名/publicをドキュメントルートに指定する必要があります。

③ モジュールのインストール

# ③モジュールのinstall
RUN apt-get update \
  && apt-get install -y zlib1g-dev libpq-dev unzip git vim curl\
  && docker-php-ext-install pdo_mysql zip

RUN apt-get updateはインストール可能なパッケージの一覧の更新だけが行われます。upgradeにするとパッケージ自体の更新がされます。
apt-get install -yも必要なもののインストールです。-y は何か請求があったとき全部YESと答えるという意味です。
docker-php-ext-installの方はphp の拡張機能をインストールしているのですが、このイメージだとmysqliとか入れようとするとエラーになってしまったんですよね、、ここの原因はまだ不明です、、、、

④ Composer入れる
Composer入れます。
こちら参考。
商用環境でも使っている Laravel 用 php-fpm イメージの Dockerfile レシピ

# ④Composer入れる
# マルチステージビルドを採用
COPY --from=composer /usr/bin/composer /usr/bin/composer

docker-compose.yml

docker-compose.yml
version: "3.7"
services:
  app:
    build: .
    container_name: php-apache-app
    ports:
      - 80:80
    volumes:
      - ./apache-laravel-app:/var/www/html

  db:
    image: mysql:5.7
    container_name: php-apache-database
    ports:
      - 3306:3306
    environment:
      - MYSQL_DATABASE=apache_laravel_db
      - MYSQL_ROOT_PASSWORD=root

コンテナ立ち上げる

$ docker-compose up -d

Dockerfileで指定すると拡張モジュールがうまく入らないものがあるので手動で入れる。

$ docker exec -it php-apache-app bash
root@be20b535a937:/var/www/html# docker-php-ext-install mysqli
~ 処理が走る
root@be20b535a937:/var/www/html# php -m // モジュール一覧表示 mysqliがあることを確認

なんで手動だとできるのかもまだ謎、、

プロジェクトを作成します。

root@be20b535a937:/var/www/html# composer create-project laravel/laravel apache-laravel-app --prefer-dist "6.*

Laravelの画面を開く

http://localhost にアクセスしてlaravelのページが開ければオッケーです!
でも!以前nginx + Laravel でホーム以外のページの404エラー解決に投稿したみたいにApacheでも同じこと起こるんじゃないかと思って、Auth入れた後に /login にアクセスしてみた結果、、、

スクリーンショット 2020-05-23 1.23.23.png

ビンゴ!(泣)
ドキュメントルートの問題じゃないのになぜなの。。。

ホーム以外が見れない原因

ApacheでLaravelを動かす時には.htaccessを読ませないといけないのですが、

.htaccess
<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

のようになっていて、mod_rewriteというモジュールが有効でないと動かないんです。そこで、コンテナ内で

root@be20b535a937:/etc# a2enmod rewrite

を実行します。
もしこれでも動かない場合は.htaccess自体が読み込まれない設定になっている可能性があるのでドキュメントルートでAllowOverrideAllになっているか確かめてください!

最後に

本来であればDocker関連のファイルはプロジェクトと同じ階層にあると思うのでドキュメントルートはよしなに変えてください。

参考にさせていただいた記事など

ubuntuでapache2のDocumentRootを変更するまで
【find・grep】特定の文字列を含むファイルのリストを取得する方法。
https://gist.github.com/chronon/95911d21928cff786e306c23e7d1d3f3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker × Mysqlでユーザー作成出来ない

概要

Docker環境でMysqlを使用する際、docker-compose.ymlに記載した環境変数が反映されず、作業ユーザーを作成出来ないエラーに遭遇しました。

docker-compose.yml

version: "3.7"

services:

  ~~~省略~~~

  db:
    container_name: db
    build:
      context: ./db
      dockerfile: Dockerfile
    ports:
      - ${DB_PORT}:3306
    environment:
      MYSQL_DATABASE: ${DB_NAME}      # 作成するデータベース名
      MYSQL_USER: ${DB_USER}            # 作業ユーザー名→上記データベースの捜査権限をもつ
      MYSQL_PASSWORD: ${DB_PASS}        # 作業ユーザーのパスワード
      MYSQL_ROOT_PASSWORD: ${ROOT_PASS} # rootユーザーのパスワード
      TZ: ${TZ}
    volumes:
      - db-data:/var/lib/mysql
      - ./db/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./db/mysql/init:/docker-entrypoint-initdb.d
      - ./log:/var/log/mysql

volumes:
  db-data:
    driver: local

Dockerfile

FROM mysql:8.0
EXPOSE 3306

そもそもなぜ作業ユーザーを作成するか?

下記記事参照。
【MySQL】ユーザーの作成方法について解説!

原因

前作業でrootユーザーのみでdocker構築をしており、volume機能でデータが永続化していたため、後作業で記述した環境変数が反映されていないというものでした。

ビルドし直しても反映されない!そもそも消えることがないようにvolume機能を使用していたのに・・・

解決

MySQLのデータを初期化

$ docker-compose down --volumes
$ docker-compose up -d

downコマンド時に--volumesオプションを追記すれば、volumeも一緒に削除されます。
次回upコマンドで起動すれば、新しいvolumeが再構築されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker imageの削除方法

はじめに

不要なimageはリソースのムダ使いに繋がる為、削除方法を忘備録として記載。

前提条件

削除対象のDocker image:centos

Docker image削除手順

1.Docker image IDを確認する

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              470671670cac        4 months ago        237MB

IMAGE IDに表示されている470671670cacが今回の削除対象のDocker image

IMAGE ID
470671670cac //←このIMAGE IDを指定する

2.Docker imageの削除

Docker imageの削除は下記コマンドで実行可能

//$ docker rmi {削除対象のIMAGE ID}
$ docker rmi 470671670cac
Untagged: centos:latest
Untagged: centos@sha256:fe8d824220415eed5477b63addf40fb06c3b049404242b31982106ac204f6700
Deleted: sha256:470671670cac686c7cf0081e0b37da2e9f4f768ddc5f6a26102ccd1c6954c1ee
Deleted: sha256:0683de2821778aa9546bf3d3e6944df779daba1582631b7ea3517bb36f9e4007

Deletedと表示されていれば完了

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

※以下のようにエラーが表示される場合

停止中のコンテナが利用しているimageのためエラーとなる為、先に停止中のコンテナを削除する必要がある

$ docker rmi 470671670cac
Error response from daemon: conflict: unable to delete 470671670cac (must be forced) - image is being used by stopped container 1ba05d50c062

停止中のコンテナIDを確認する

$ docker psだと停止中のコンテナは表示されない

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

$ docker ps-aをオプションをつけることで停止中のコンテナも表示することが可能

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
124b0dcc686a        centos              "/bin/bash"         32 minutes ago      Exited (0) 32 minutes ago                       practical_ptolemy

下記コンテナIDが削除対象のコンテナID

CONTAINER ID
124b0dcc686a //←このCONTANER IDを利用する

コンテナの削除

以下の方法で停止中のコンテナを削除することが可能

//$ docker rm {{CONTAINER ID}}
$ docker rm 124b0dcc686a
124b0dcc686a //←上手くいくとCONTANER IDが表示される

改めてDocker imageの削除再実行

$ docker rmi 470671670cac
Untagged: centos:latest
Untagged: centos@sha256:fe8d824220415eed5477b63addf40fb06c3b049404242b31982106ac204f6700
Deleted: sha256:470671670cac686c7cf0081e0b37da2e9f4f768ddc5f6a26102ccd1c6954c1ee
Deleted: sha256:0683de2821778aa9546bf3d3e6944df779daba1582631b7ea3517bb36f9e4007

Deletedと表示されていれば完了

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Compose で作った Redmine を Ubuntu 上でいい感じに動かす手抜きレシピ

はじめに

Docker Compose を使った自分好みの Redmine 実行環境 というものを作っています。構築やバージョンアップが何かと大変な Redmine を、プラグインやテーマがてんこもりの欲張りセットにしつつも、なるべく手をかけずにメンテナスできるようにするにはどうすればいいのかを悩みながら、ようやくそれなりの形になってきたかなといったところです。

以下は、試行錯誤の結果をまとめた記事リンクです。

この状態でも動かせるのですが、せっかくですのでもう少しサーバーっぽく仕立てていきたいと思います。相変わらずあまり手をかけたくないので、ちょっと手抜きなレシピですがお付き合いください。(お気に召さないところがあればアレンジいただいても問題ありません)

おことわり

本記事の内容で実際に運用した際、何らかの問題やトラブルが起きても自己責任でお願いします。安全に運用できるようにするために可能な限り注意を払って執筆しておりますが、至らぬところもあるかと思います。

内容の不備や、よりよい実現方法があればコメントいただけると幸いです。

今回のゴール

Ubuntu 18.04 LTS を使って、複数の Redmine を同時に稼働させる環境を作ります。(Ubuntu 20.04 LTS の方が良いかもしれませんが、リリースされてからまだ日が浅いので今回は見送ります)

詳細は以下のとおりです。

  • それぞれの Redmine には HTTPS でアクセスできるようにします。HTTPS 化には Let's Encrypt を使います。
  • どの Redmine にアクセスするかはサブドメインで振り分けるようにします。ついでに、ドメイン名ではなく IP アドレスでアクセスしてきた場合は弾くようにします。
    • 僕は ConoHa の VPSムームードメイン を組み合わせて使っています。自分でサブドメインを自由に割り当てられる環境があれば何でもよいです。
  • リバースプロキシサーバーに Nginx を使います。Docker Compose で作った Redmine はバックエンドで動作させます。
    • 手抜きして、Nginx はホスト側にインストールします。
  • Redmine Security Scanner の検査結果が「A+」になるようにします。
  • ログローテーション、Redmine のリマインダー通知、データベースや添付ファイルのバックアップを定期的に行うようにします。

Nginx をホスト側にインストールする理由

ここまで Docker Compose を使ってきたんだから、Nginx も Docker Compose を使えばいいのにと思われる方もいらっしゃると思いますが、これには理由があります。

それは、今回の構成だと、Nginx に限っては Docker を使うよりもホストに直接インストールする方が手っ取り早いと感じたからです。

Certbot で証明書を発行したり定期更新のタスクを仕掛けるのはホスト側のコマンドラインで操作した方が楽だと思ってますし、サブドメイン 1 つに対して 1 回設定するだけでよいので、そこまで手間ではないと思っています。あとは Nginx の conf ファイルをちょこっと触るくらしかないので、Docker を使おうが使わまいがさほど変わらない気がしています。

また、Nginx と Redmine を同じ Docker 内部ネットワークで接続した方が構成としては綺麗なのかもしれませんが、この程度の規模なら Redmine のポートをホスト側にバインドしてもさほど影響ないのではと思っています。

もちろん、Docker 化した方が望ましいと思われる要件があれば検討したいとは思いますが、今回はどうにもそうは思えなかったです。たとえるなら「100 点満点のテストで 100 点を取ろうとして時間切れで結局 20~30 点しか取れないくらいなら、最初から 60~70 点くらいを狙おう」という考えだということです。

と、長々と言い訳がましく書きましたが、結局のところ以下のような構成となります。

サーバー構成

ホスト側の Nginx と、Docker Compose の Redmine がちょうどいい感じにソーシャルディスタンスを保っているんじゃないかなと思います。このくらいの方が、後になって方向転換したくなったときに融通が利きやすいだろうという見立てです。

レシピ

前置きが長くなりましたが、ここからが詳細なレシピとなります。

まず、Redmine を 1 つ稼働させるところまでを説明します。その後、2 つ目以降の Redmine を追加していきます。

下準備

ホストマシンの準備

Ubuntu 18.04 LTS のマシンを用意します。VPS でも何でもよいです。設定手順は割愛しますが、以下のリンク先などが参考になります。

以降、下記の条件が満たされている前提で進めます。

  • sudo 権限をもつユーザーが作られていること
    • 以降、ユーザー名は [user] で説明しますので、適宜読み替えてください
  • インターネットから IP アドレス指定でアクセスできること

あとは、SSH 公開鍵認証で接続できるようにしておいた方が便利です。SSH は root で接続できなくしておいたり、パスワードでの認証を無効にしたりするなど、セキュリティリスクを抑えるように設定した方が望ましいです。

ドメインの準備

自分でサブドメインを自由に割り当てられる DNS サービスを用意します。以降、ドメイン名は [your-domain] で説明しますので、適宜読み替えてください。

サブドメイン名は何でもよいです。今回は説明の便宜上、名前を決め打ちします。以降の説明では、その名前にあわせてファイル名やディレクトリ名などを決めていきます。みなさんがお手元で試される際は、ご自身の気に入った名前をつけるようにしてください。

今回、1 つ目の Redmine には yuni と名づけます。yuni.[your-domain] で前述の Ubuntu マシンの IP アドレスが引けるように DNS のレコードを追加してください。

ホストマシンの設定

ファイアウォールの設定

ファイアウォールは ufw を使います。今回は以下のポートを開けて、他は塞ぎます。

  • HTTP(80 番ポート)
  • HTTPS(443 番ポート)
  • SSH(22 番ポート) ※ リッスンするポート番号を変更している場合は読み替えてください。

コマンドは以下のとおりです。

$ sudo ufw enable
$ sudo ufw allow 22
$ sudo ufw allow 80
$ sudo ufw allow 443

SSH 接続でファイアウォールの設定をする場合、誤って SSH 接続用ポートを塞いだまま切断してしまうと再接続できなくなってしまうため注意してください。別のセッションで接続確認しながら設定するのがよいです。

設定した内容は ufw status で確認できます。

$ sudo ufw status
状態: アクティブ

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere
80                         ALLOW       Anywhere
443                        ALLOW       Anywhere
22 (v6)                    ALLOW       Anywhere (v6)
80 (v6)                    ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

Docker と Docker Compose のインストール

それぞれ、以下のリンク先を参照してください。

※ Ubuntu 20.04 LTS だと apt install だけでインストールできるらしいので簡単そうです。

なお、動作確認時のバージョンは以下のとおりです。

$ docker -v
Docker version 19.03.8, build afacb8b7f0
$ docker-compose -v
docker-compose version 1.25.5, build 8a1c60f6

インストール先のパスは以下のとおりです。

$ which docker
/usr/bin/docker
$ which docker-compose
/usr/local/bin/docker-compose

Web サービスの設定

Nginx のインストール

以下のリンク先を参照してください。

インストールが完了したら Nginx を起動します。

$ sudo systemctl start nginx.service

起動が完了したら、ブラウザから http://[ホストの IP アドレス]http://yuni.[your-domain] のそれぞれにアクセスして、どちらも Nginx のトップページが表示されることを確認してください。

Nginx のトップページ

Redmine の構築

Redmine を構築するため、 Docker Compose を使った自分好みの Redmine 実行環境 からファイル一式を取得します。

ディレクトリ構成

今回は以下のディレクトリ構成とします。(細部は割愛)
あくまで一例ですので、好みに合わせて変更いただいても問題ありません。

/
+-- opt/
    +-- redmine/
        +-- yuni/
            +-- docker-compose.yml
            +-- db/
            +-- redmine/
+-- srv/
    +-- redmine/
        +-- yuni/
            +-- files/
+-- var/
    +-- log/
        +-- redmine/
            +-- yuni/
        +-- redmine-chupa-text/
            +-- yuni/

ファイル一式の配置

/opt 配下にディレクトリを作って、取得したファイル一式をコピーします。ディレクトリ名を yuni にリネームしておきます。

$ cd /opt
$ sudo mkdir redmine
$ sudo chown [user]:[user] redmine

$ cd /opt/redmine
$ cp /path/to/myfav-redmine .
$ mv myfav-redmine yuni

/path/to はファイル一式を格納した任意のディレクトリを表しますので、適宜読み替えてください。

docker-compose.yml の編集

ディレクトリ構成にあわせてパスを編集します。コンテナ名もあわせて変更しておきます。

以下、元のファイルからの変更箇所がわかるように差分形式で表しています。

/opt/redmine/yuni/docker-compose.yml
version: "3.7"

services:

  redmine:
    build: ./redmine
-    container_name: myfav-redmine
+    container_name: yuni-redmine      
    restart: always
    depends_on:
      - db
      - chupa-text
    ports:
      - "3000:3000"
    environment:
      TZ: Asia/Tokyo
      REDMINE_DB_POSTGRES: db
      REDMINE_DB_DATABASE: redminedb
      REDMINE_DB_USERNAME: redmineuser
      REDMINE_DB_PASSWORD: redminepassword
      REDMINE_PLUGINS_MIGRATE: "true"
    volumes:
-      - "/srv/redmine/files:/usr/src/redmine/files:z"
+      - "/srv/redmine/yuni/files:/usr/src/redmine/files:z"
-      - "/var/log/redmine:/usr/src/redmine/log:z"
+      - "/var/log/redmine/yuni:/usr/src/redmine/log:z"    

  db:
    build: ./db
-    container_name: myfav-redmine-db
+    container_name: yuni-redmine-db
    restart: always
    environment:
      TZ: Asia/Tokyo
      POSTGRES_DB: redminedb
      POSTGRES_USER: redmineuser
      POSTGRES_PASSWORD: redminepassword
    volumes:
      - "dbdata:/var/lib/postgresql/data"

  chupa-text-proxy:
    image: groonga/chupa-text:proxy
-    container_name: myfav-redmine-chupa-text-proxy
+    container_name: yuni-redmine-chupa-text-proxy
    restart: always
    environment:
      TZ: Asia/Tokyo
    volumes:
-      - "/var/log/redmine-chupa-text/proxy:/var/log/squid:z"
+      - "/var/log/redmine-chupa-text/yuni/proxy:/var/log/squid:z"
  chupa-text:
    image: groonga/chupa-text:ubuntu-latest
-    container_name: myfav-redmine-chupa-text
+    container_name: yuni-redmine-chupa-text
    restart: always
    depends_on:
      - chupa-text-proxy
    environment:
      TZ: Asia/Tokyo
      http_proxy: http://chupa-text-proxy:3128/
      https_proxy: http://chupa-text-proxy:3128/
      RAILS_SERVE_STATIC_FILES: "true"
    volumes:
-      - "/var/log/redmine-chupa-text/rails:/home/chupa-text/chupa-text-http-server/log:z"
+      - "/var/log/redmine-chupa-text/yuni/rails:/home/chupa-text/chupa-text-http-server/log:z"

volumes:
  dbdata:

必要に応じて、データベース名/ユーザー名/パスワードも変更してください。

Redmine の起動

必要に応じて、プラグインやテーマの追加、メールの設定を行います。それぞれ下記ファイルを編集します。

  • プラグインやテーマの追加: /opt/redmine/yuni/redmine/Docerfile
  • メールの設定: /opt/redmine/yuni/redmine/config/configuration.yml

準備ができたら Redmine を起動します。

$ cd /opt/redmine/yuni
$ docker-compose up -d --build

docker-compose ps を実行すると、起動できているか確認できます。
以下は、正常に起動できているときの結果です。

$ docker-compose ps
            Name                           Command               State           Ports
-----------------------------------------------------------------------------------------------
yuni-redmine                    /docker-entrypoint.sh pass ...   Up      0.0.0.0:3000->3000/tcp
yuni-redmine-chupa-text         /bin/sh -c ./start.sh            Up
yuni-redmine-chupa-text-proxy   /bin/sh -c ./start.sh            Up
yuni-redmine-db                 docker-entrypoint.sh postgres    Up      5432/tcp

この時点では、ブラウザから Redmine の表示は確認できません。
curl -v http://localhost:3000 を実行して Redmine のトップページの HTML ソースが取得できれば OK です。

Nginx の設定

Nginx を通じてバックエンドの Redmine にアクセスするための設定を追加します。

/etc/nginx/conf.d 配下に yuni-redmine.conf というファイルを作って、以下のように設定します。

/etc/nginx/conf.d/yuni-redmine.conf
server {
    listen 80;
    server_name  yuni.[your-domain];

    client_max_body_size  32m;

    proxy_buffer_size 32k;
    proxy_buffers 100 32k;

    proxy_set_header  Host              $http_host;
    proxy_set_header  X-Real-IP         $remote_addr;
    proxy_set_header  X-Forwarded-Host  $host:$server_port;
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://localhost:3000;
    }
}

Nginx はデフォルトで 1MB までしかリクエストを受け付けません。それ以上のサイズのファイルをアップロードできるようにするため client_max_body_size の設定が必要です。
proxy_buffer_sizeproxy_buffers はバックエンドからのレスポンスデータをバッファリングするための設定です。パフォーマンスに問題があれば調整してください。

設定が完了すれば、 Nginx をリロードしてください。設定が反映されます。

$ sudo systemctl reload nginx.service

設定が反映されないときは、Nginx を再起動してください。

$ sudo systemctl restart nginx.service

ブラウザからの動作確認

ここまでの設定が正しくできていれば、ブラウザから http://yuni.[your-domain] にアクセスすると Redmine のトップページが表示されます。

Redmine のトップページ

なお、 http://[ホストの IP アドレス] にアクセスすると、Nginx のトップページが表示されます。

Let's Encrypt で HTTPS 化

まずは現時点のセキュリティレベルを検査

HTTPS 化する前後でセキュリティレベルの違いを確認できるように、今の時点で先ほどアクセスできるようになった Redmine を Redmine Security Scanner で検査してみます。

検査結果-1

結果は「C」です。「暗号化されていない通信で Redmine にアクセスできてしまうよ」と教えてくれています。これを改善します。

Certbot のインストールと設定

Let's Encrypt の SSL 証明書を取得するために Certbot を使います。
Certbot のインストールと設定は、以下のリンク先を参照してください。

コマンドラインで対話形式にいくつかの質問に答えるだけで簡単に設定できます。途中、「HTTP からのアクセスを HTTPS にリダイレクトするか」と問われたら「リダイレクトする」を選んでください。

設定の完了後に Nginx の設定ファイルを開くと、以下のように変更されていることがわかります。

/etc/nginx/conf.d/yuni-redmine.conf
server {
-    listen 80;
    server_name  yuni.[your-domain];

    client_max_body_size  32m;

    proxy_buffer_size 32k;
    proxy_buffers 100 32k;

    proxy_set_header  Host              $http_host;
    proxy_set_header  X-Real-IP         $remote_addr;
    proxy_set_header  X-Forwarded-Host  $host:$server_port;
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://localhost:3000;
    }
+
+    listen 443 ssl; # managed by Certbot
+    ssl_certificate /etc/letsencrypt/live/yuni.[your-domain]/fullchain.pem; # managed by Certbot
+    ssl_certificate_key /etc/letsencrypt/live/yuni.[your-domain]/privkey.pem; # managed by Certbot
+    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+}
+
+
+server {
+    if ($host = yuni.[your-domain]) {
+        return 301 https://$host$request_uri;
+    } # managed by Certbot
+
+
+    listen 80;
+    server_name  yuni.[your-domain];
+    return 404; # managed by Certbot
+

}

これで https://yuni.[your-domain] にアクセスできるようになりました。
http://yuni.[your-domain] にアクセスしても HTTPS にリダイレクトされるようになります。

もう一度セキュリティレベルを検査(と微調整)

HTTPS 化ができたら、もう一度 Redmine Security Scanner で検査します。

検査結果-2

結果は「A」です。うーん惜しい、あと一歩。どうやらセキュリティ関連のヘッダが足りていないようです。

検査結果詳細

検査結果を追っていくと「HTTP Strict Transport Security ヘッダがセットされていないよ」と教えてくれています。

どうやら Certbot は、設定時に Strict Transport Security ヘッダをセットしてくれないようですので、手動で Nginx の設定に追記します。

せっかくなので、ついでに HTTP/2 の設定も追記しておきます。以下は変更箇所を抜粋したものです。

/etc/nginx/conf.d/yuni-redmine.conf
-    listen 443 ssl; # managed by Certbot
+    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/yuni.[your-domain]/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/yuni.[your-domain]/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+    add_header Strict-Transport-Security "max-age=31536000;";

}

Nginx をリロードまたは再起動させて設定を反映した後に、もう一度検査してみると、結果が「A+」になります。やったぜ。

検査結果-3

IP アドレスでのアクセスを弾く

Let's Encrypt が発行する SSL 証明書は、Certbot で設定した時に選択したドメインに対してのみ有効です。IP アドレスでアクセスすると不正な SSL 証明書とみなされてしまいます。そもそも IP アドレスだと Redmine にアクセスできないので弾いちゃいましょう。

Nginx のデフォルト設定( /etc/nginx/conf.d/default.conf )を以下の内容に差し替えます。

/etc/nginx/conf.d/default.conf
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name  _;
    ssl_certificate     /etc/letsencrypt/live/yuni.[your-domain]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yuni.[your-domain]/privkey.pem;
    return       444;
}

Nginx は return 444; とすると HTTP レスポンスを終了します。これを、他のどの設定の server_name にも一致しなかったときにデフォルトで適用されるようにすることで、IP アドレスでのアクセスを弾けるようになります。

ただし、HTTPS でのアクセスも弾こうとして listen 443 ssl を追加すると、 ssl_certificatessl_certificate_key も追加しないと怒られてしまいます。ここでは苦し紛れで、先ほど作った Let's Encrypt の設定を流用しています。厳密に言えば正しくないのですが、アクセスできないページに労力をかけるのもどうかと思うので、今回はこれでヨシ!とします。

Redmine の設定

ようやく Redmine の設定に触れていきます。といいつつ、基本的には Redmineガイド を参照してください。

ここでは、本環境で固有の設定だけピックアップして説明します。

Full text search plugin「ChupaTextサーバーのURL」

管理 > プラグイン から Full text search plugin の設定画面を開き、「ChupaTextサーバーのURL」に http://chupa-text:3000/extraction.json と設定します。ChupaText の README に書いている説明と、ホスト名:ポート番号が違っているので注意してください。

データベースのダンプ

管理画面から一通り設定が完了したら、データベースのダンプを取っておきましょう。何か問題が起きたとき、データのリストアに使えます。

今回はデータベースに PostgreSQL を使っているので pg_dump を使います。

$ cd /opt/redmine/yuni/
$ docker-compose exec -T db pg_dump -U redmineuser redminedb > yuni-redmine.dump

今回はデフォルトのフォーマットでダンプしているので平文で出力されます。他のフォーマットも選択可能です。詳しくは PostgreSQL 12.0 文書 PostgreSQLクライアントアプリケーション - pg_dump を参照してください。

本記事では、この後 2 つ目の Redmine を作るときにリストアの手順を説明します。

ログローテーションの設定

ログローテーションはホスト側の logrotate.d で行います。
/etc/logrotate.d に格納されている他のログローテーション設定に倣って、以下のように設定します。

Redmine のログローテーション

/etc/logrotate.d/yuni-redmine
/var/log/redmine/yuni/*.log {
  rotate 4
  weekly
  missingok
  notifempty
  compress
  delaycompress
  sharedscripts
  postrotate
    /usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart redmine
  endscript
}

ChupaText のログローテーション

/etc/logrotate.d/yuni-redmine-chupa-text
/var/log/redmine-chupa-text/yuni/*/*.log {
  rotate 4     
  weekly       
  missingok    
  notifempty   
  compress     
  delaycompress
  sharedscripts
  postrotate   
    /usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart chupa-text chupa-text-proxy
  endscript
}

動作確認

動作確認は logrotate コマンドで行います。
-dv オプションで dry-run 、-f オプションで強制手動実行できます。

以下は dry-run のコマンド例です。

$ sudo logrotate -dv /etc/logrotate.d/yuni-redmine
$ sudo logrotate -dv /etc/logrotate.d/yuni-redmine-chupa-text

リマインダー通知の設定

rake タスクの実行方法

リマインダー通知を含む rake タスクの実行方法は以下のとおりです。

$ cd /opt/redmine/yuni/
$ docker-compose exec -T redmine rake redmine:send_reminders

任意のディレクトリから docker-compose コマンドを実行するときは、 -f オプションで docker-compose.yml のパスを指定します。

$ docker-compose -f /opt/redmine/yuni/docker-compose.yml exec -T redmine rake redmine:send_reminders

リマインダー通知のシェルスクリプト

リマインダー通知の設定をメンテナンスしやすくするために /opt/redmine/scripts/yuni 配下にシェルスクリプトを作っておきます。

以下は、期日が 3 日前までに迫ったチケットのリマインダーを通知する例です。

/opt/redmine/scripts/yuni/send_reminders.sh
#!/bin/bash

/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
        exec -T redmine rake redmine:send_reminders days=3

シェルスクリプトのファイルには実行権限を付与してください。( chmod +x

リマインダーの定期実行

リマインダーの定期実行は、ホスト側の cron を使います。

以下は、毎平日の朝 6 時にリマインダー通知を実行する例です。ついでにリマインダーの実行結果をログに残すようにしています。

0 6 * * 1-5 /opt/redmine/scripts/yuni/send_reminders.sh >> /var/log/redmine/yuni/send_reminders.log 2>&1

バックアップ

バックアップの取得先

バックアップの取得先は /var/opt/redmine/yuni/backup にします。念のため root ユーザーしかアクセスできないようにしておきます。

$ sudo mkdir -p /var/opt/redmine/yuni/backup/{db,files}
$ sudo chmod 700 /var/opt/redmine/yuni/backup

バックアップスクリプト

添付ファイルのバックアップには rsync 、データベースのバックアップには前述の pg_dump を使います。

こちらもシェルスクリプトにしておきます。

/opt/redmine/scripts/yuni/backup.sh
#!/bin/bash

# 添付ファイルのバックアップ
rsync -avh /srv/redmine/yuni/files/ /var/opt/redmine/yuni/backup/files

# データベースのバックアップ
/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
        exec -T db pg_dump -U redmineuser redminedb \
        > /var/opt/redmine/yuni/backup/db/yuni-redmine.dump

今回は自サーバー内でバックアップを取得しているため、誤操作などで誤ってデータを紛失したときにリストアが可能ですが、サーバー自身がダメになったときはリストアできません。

取得したバックアップを別のサーバーなどに転送しておけば、サーバー自身がダメになったときもリストアできます。(転送処理は割愛します)

バックアップの定期実行

バックアップの定期実行も、ホスト側の cron を使います。

以下は、毎日朝 4 時にバックアップを実行する例です。ついでにログも残すようにしています。

0 4 * * * /opt/redmine/scripts/yuni/backup.sh >> /var/log/redmine/yuni/backup.log 2>&1

ここまでのまとめ

これで無事に 1 つ目の Redmine の設定が終わりました。Redmine 本体の設定にはあまり触れず、足回りのことばかりダラダラと書いてきましたが…

さて、どうだろうか。

1 つ目の Redmine

端的に換言すれば、いい感じ。

ディレクトリ構成

追加したディレクトリ、ファイルを含めてディレクトリ構成を再度まとめておきます。

/
+-- etc/
    +-- logrotate.d/
        +-- yuni-redmine
        +-- yuni-redmine-chupa-text
    +-- nginx/
        +-- conf.d/
            +-- default.conf
            +-- yuni-redmine.conf
+-- opt/
    +-- redmine/
        +-- scripts/
            +-- yuni/
                +-- backup.sh
                +-- send_reminders.sh
        +-- yuni/
            +-- docker-compose.yml
            +-- db/
            +-- redmine/
+-- srv/
    +-- redmine/
        +-- yuni/
            +-- files/
+-- var/
    +-- log/
        +-- redmine/
            +-- yuni/
        +-- redmine-chupa-text/
            +-- yuni/
    +-- opt/
        +-- redmine/
            +-- yuni/
                +-- backup/
                    +-- db/
                        +-- yuni-redmine.dump
                    +-- files/

2 つ目の Redmine を作ってみる

さて、次は 2 つ目の Redmine を作ってみましょう。やはり名前は何でもいいです。気に入った名前をつけましょう。

本記事では chloe と名づけます。 chloe.[your-domain] で DNS のレコードを追加してください。

ディレクトリ構成

2 つ目の Redmine ができあがると、以下のようなディレクトリ構成になります。
追加するファイルおよびディレクトリを差分で示します。

 /
 +-- etc/
     +-- logrotate.d/
+        +-- chloe-redmine
+        +-- chloe-redmine-chupa-text
         +-- yuni-redmine
         +-- yuni-redmine-chupa-text
     +-- nginx/
         +-- conf.d/
             +-- default.conf
+            +-- chloe-redmine.conf
             +-- yuni-redmine.conf
 +-- opt/
     +-- redmine/
         +-- scripts/
+            +-- chloe/
+                +-- backup.sh
+                +-- send_reminders.sh
            +-- yuni/
                +-- backup.sh
                +-- send_reminders.sh
+        +-- chloe/
+            +-- docker-compose.yml
+            +-- db/
+            +-- redmine/
         +-- yuni/
             +-- docker-compose.yml
             +-- db/
             +-- redmine/
 +-- srv/
     +-- redmine/
+        +-- chloe/
+            +-- files/
         +-- yuni/
             +-- files/
 +-- var/
     +-- log/
         +-- redmine/
+            +-- chloe/
             +-- yuni/
         +-- redmine-chupa-text/
+            +-- chloe/
             +-- yuni/
     +-- opt/
         +-- redmine/
+            +-- chloe/
+                +-- backup/
+                    +-- db/
+                        +-- chloe-redmine.dump
+                    +-- files/
             +-- yuni/
                 +-- backup/
                     +-- db/
                         +-- yuni-redmine.dump
                     +-- files/

Redmine の構築

ファイル一式のコピーと docker-compose.yml の編集

今回は /opt/redmine/yuni をまるっとコピーして /opt/redmine/chloe を作ります。

$ cd /opt/redmine
$ cp -a yuni chloe

コピーができたら次は docker-compose.yml を編集します。
今回は Redmine をホスト側の 3001 番ポートにバインドします。

/opt/redmine/chloe/docker-compose.yml
version: "3.7"

services:

  redmine:
    build: ./redmine
-    container_name: yuni-redmine      
+    container_name: chloe-redmine      
    restart: always
    depends_on:
      - db
      - chupa-text
    ports:
-      - "3000:3000"
+      - "3001:3000"
    environment:
      TZ: Asia/Tokyo
      REDMINE_DB_POSTGRES: db
      REDMINE_DB_DATABASE: redminedb
      REDMINE_DB_USERNAME: redmineuser
      REDMINE_DB_PASSWORD: redminepassword
      REDMINE_PLUGINS_MIGRATE: "true"
    volumes:
-      - "/srv/redmine/yuni/files:/usr/src/redmine/files:z"
+      - "/srv/redmine/chloe/files:/usr/src/redmine/files:z"
-      - "/var/log/redmine/yuni:/usr/src/redmine/log:z"    
+      - "/var/log/redmine/chloe:/usr/src/redmine/log:z"    

  db:
    build: ./db
-    container_name: yuni-redmine-db
+    container_name: chloe-redmine-db
    restart: always
    environment:
      TZ: Asia/Tokyo
      POSTGRES_DB: redminedb
      POSTGRES_USER: redmineuser
      POSTGRES_PASSWORD: redminepassword
    volumes:
      - "dbdata:/var/lib/postgresql/data"

  chupa-text-proxy:
    image: groonga/chupa-text:proxy
-    container_name: yuni-redmine-chupa-text-proxy
+    container_name: chloe-redmine-chupa-text-proxy
    restart: always
    environment:
      TZ: Asia/Tokyo
    volumes:
-      - "/var/log/redmine-chupa-text/yuni/proxy:/var/log/squid:z"
+      - "/var/log/redmine-chupa-text/chloe/proxy:/var/log/squid:z"
  chupa-text:
    image: groonga/chupa-text:ubuntu-latest
-    container_name: yuni-redmine-chupa-text
+    container_name: chloe-redmine-chupa-text
    restart: always
    depends_on:
      - chupa-text-proxy
    environment:
      TZ: Asia/Tokyo
      http_proxy: http://chupa-text-proxy:3128/
      https_proxy: http://chupa-text-proxy:3128/
      RAILS_SERVE_STATIC_FILES: "true"
    volumes:
-      - "/var/log/redmine-chupa-text/yuni/rails:/home/chupa-text/chupa-text-http-server/log:z"
+      - "/var/log/redmine-chupa-text/chloe/rails:/home/chupa-text/chupa-text-http-server/log:z"

volumes:
  dbdata:

この時、変更箇所に漏れがないように気を付けてください。もし変更漏れがあると 1 つ目に作った Redmine と競合してエラいことになります。

競合を避けるために最初から container_name をつけないという流儀もあるようです。

Redmine の起動

準備ができたら 1 つ目と同じように Redmine を起動します。

$ cd /opt/redmine/chloe
$ docker-compose up -d --build

docker-compose ps で起動確認しましょう。

$ docker-compose ps
            Name                           Command               State           Ports
------------------------------------------------------------------------------------------------
chloe-redmine                    /docker-entrypoint.sh pass ...   Up      0.0.0.0:3001->3000/tcp
chloe-redmine-chupa-text         /bin/sh -c ./start.sh            Up
chloe-redmine-chupa-text-proxy   /bin/sh -c ./start.sh            Up
chloe-redmine-db                 docker-entrypoint.sh postgres    Up      5432/tcp

curl -v http://localhost:3001 を実行して Redmine のトップページの HTML ソースが取得できれば OK です。

Nginx の設定

/etc/nginx/conf.d 配下に chloe-redmine.conf というファイルを作って、以下のように設定します。

/etc/nginx/conf.d/chloe-redmine.conf
server {
    listen 80;
    server_name  chloe.[your-domain];

    client_max_body_size  32m;

    proxy_buffer_size 32k;
    proxy_buffers 100 32k;

    proxy_set_header  Host              $http_host;
    proxy_set_header  X-Real-IP         $remote_addr;
    proxy_set_header  X-Forwarded-Host  $host:$server_port;
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://localhost:3001;
    }
}

server_nameproxy_pass のポート番号が違うだけで、残りの箇所は 1 つ目と同じです。

設定が完了すれば、Nginx をリロードまたは再起動して反映してください。
ここまでの設定が正しくできていれば、ブラウザから http://chloe.[your-domain] にアクセスすると Redmine のトップページが表示されます。

Let's Encrypt で HTTPS 化

certbot instructions - Nginx on Ubuntu 18.04 LTS (bionic) の Certbot インストール後の手順から進めます。

対話形式の質問には「 chloe.[your-domain] を HTTPS 化する 」⇒「HTTP からのアクセスを HTTPS にリダイレクトする」を選んでください。

HTTPS 化が終われば Strict Transport Security ヘッダと HTTP/2 の設定を追加します。

/etc/nginx/conf.d/chloe-redmine.conf
-    listen 443 ssl; # managed by Certbot
+    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/chloe.[your-domain]/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/chloe.[your-domain]/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+    add_header Strict-Transport-Security "max-age=31536000;";

}

Nginx をリロードまたは再起動させて設定を反映した後に、 Redmine Security Scannerhttps://chloe.[your-domain] を検査して、結果が「A+」になることを確認しておきましょう。

Redmine の設定

Redmine の設定ですが、今回は 1 つ目の Redmine から取得したデータベースのダンプをリストアしてみましょう。

Redmine を停止してデータベースをいったん空にして、ダンプをリストアして Redmine を起動するという流れになります。

Redmine の停止は以下のコマンドです。

$ cd /opt/redmine/chloe
$ docker-compose stop redmine

次にデータベースを空にする手順ですが、PostgreSQL の場合は「public スキーマを削除してから再作成する」のが手っ取り早いようです。

$ cd /opt/redmine/chloe
$ docker-compose exec -T db psql -U redmineuser redminedb << EOF
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO redmineuser;
GRANT ALL ON SCHEMA public TO public;
COMMENT ON SCHEMA public IS 'standard public schema';
CREATE EXTENSION IF NOT EXISTS pgroonga;
EOF

後はリストアしたい時点のダンプファイルを psql で流し込んで、 Redmine を再起動するだけです。

$ cd /opt/redmine/chloe
$ docker-compose exec -T db psql -U redmineuser redminedb < /path/to/yuni-redmine.dump
$ docker-compose start redmine

/path/to はダンプを格納した任意のディレクトリを表しますので、適宜読み替えてください。

後は、ログインして設定を編集しましょう。
少なくとも 設定 > 全般 の「ホスト名とパス」は変更が必要です。他の箇所は必要に応じて自由に変えましょう。

2 つ目の Redmine

ぷー…まあ、いいけど…てゆか、いいじゃん。

ログローテーションの設定、リマインダー通知の設定、バックアップ

ログローテーションの設定、リマインダー通知の設定、バックアップも同じようにします。
スクリプトや cron ジョブは 1 つ目の Redmine との違いを差分で示します。

Redmine のログローテーション

/etc/logrotate.d/chloe-redmine
-/var/log/redmine/yuni/*.log {
+/var/log/redmine/chloe/*.log {
  rotate 4
  weekly
  missingok
  notifempty
  compress
  delaycompress
  sharedscripts
  postrotate
-    /usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart redmine
+    /usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml restart redmine
  endscript
}

ChupaText のログローテーション

/etc/logrotate.d/chloe-redmine-chupa-text
-/var/log/redmine-chupa-text/yuni/*/*.log {
+/var/log/redmine-chupa-text/chloe/*/*.log {
  rotate 4     
  weekly       
  missingok    
  notifempty   
  compress     
  delaycompress
  sharedscripts
  postrotate   
-    /usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml restart chupa-text chupa-text-proxy
+    /usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml restart chupa-text chupa-text-proxy
  endscript
}

リマインダー通知のシェルスクリプト

/opt/redmine/scripts/chloe/send_reminders.sh
#!/bin/bash

-/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
+/usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml \
        exec -T redmine rake redmine:send_reminders days=3

リマインダーの定期実行

-0 6 * * 1-5 /opt/redmine/scripts/yuni/send_reminders.sh >> /var/log/redmine/yuni/send_reminders.log 2>&1
+0 6 * * 1-5 /opt/redmine/scripts/chloe/send_reminders.sh >> /var/log/redmine/chloe/send_reminders.log 2>&1

バックアップの取得先

$ sudo mkdir -p /var/opt/redmine/chloe/backup/{db,files}
$ sudo chmod 700 /var/opt/redmine/chloe/backup

バックアップスクリプト

/opt/redmine/scripts/chloe/backup.sh
#!/bin/bash

# 添付ファイルのバックアップ
-rsync -avh /srv/redmine/yuni/files/ /var/opt/redmine/yuni/backup/files
+rsync -avh /srv/redmine/chloe/files/ /var/opt/redmine/chloe/backup/files

# データベースのバックアップ
-/usr/local/bin/docker-compose -f /opt/redmine/yuni/docker-compose.yml \
+/usr/local/bin/docker-compose -f /opt/redmine/chloe/docker-compose.yml \
        exec -T db pg_dump -U redmineuser redminedb \
-        > /var/opt/redmine/yuni/backup/db/yuni-redmine.dump
+        > /var/opt/redmine/chroe/backup/db/chroe-redmine.dump

3 つ目以降の Redmine も同様に

3 つ目以降の Redmine を作りたいときも、名前とバインドするポートを変えて、あとは 2 つ目の Redmine と同じく以下の手順に沿って進めれば簡単に作れます。

  1. Redmine の構築
  2. Nginx の設定
  3. Let's Encrypt で HTTPS 化
  4. Redmine の設定
  5. ログローテーションの設定、リマインダー通知、バックアップ

たとえば、3 つ目の Redmine の名前を chieru と名づけて、バインドするポートを 3002 番にして作ることができます。

3 つ目の Redmine

ここまでお読みいただいた皆さんでしたら、詳しく説明しなくてもちぇるっと作れますよね。

以下、 docker-compose.yml と HTTPS 化する前の Nginx 設定ファイルだけ示しておきます。

/opt/redmine/chieru/docker-compose.yml
version: "3.7"

services:

  redmine:
    build: ./redmine
-    container_name: chloe-redmine      
+    container_name: chieru-redmine      
    restart: always
    depends_on:
      - db
      - chupa-text
    ports:
-      - "3001:3000"
+      - "3002:3000"
    environment:
      TZ: Asia/Tokyo
      REDMINE_DB_POSTGRES: db
      REDMINE_DB_DATABASE: redminedb
      REDMINE_DB_USERNAME: redmineuser
      REDMINE_DB_PASSWORD: redminepassword
      REDMINE_PLUGINS_MIGRATE: "true"
    volumes:
-      - "/srv/redmine/chloe/files:/usr/src/redmine/files:z"
+      - "/srv/redmine/chieru/files:/usr/src/redmine/files:z"
-      - "/var/log/redmine/chloe:/usr/src/redmine/log:z"    
+      - "/var/log/redmine/chieru:/usr/src/redmine/log:z"    

  db:
    build: ./db
-    container_name: chloe-redmine-db
+    container_name: chieru-redmine-db
    restart: always
    environment:
      TZ: Asia/Tokyo
      POSTGRES_DB: redminedb
      POSTGRES_USER: redmineuser
      POSTGRES_PASSWORD: redminepassword
    volumes:
      - "dbdata:/var/lib/postgresql/data"

  chupa-text-proxy:
    image: groonga/chupa-text:proxy
-    container_name: chloe-redmine-chupa-text-proxy
+    container_name: chieru-redmine-chupa-text-proxy
    restart: always
    environment:
      TZ: Asia/Tokyo
    volumes:
-      - "/var/log/redmine-chupa-text/chloe/proxy:/var/log/squid:z"
+      - "/var/log/redmine-chupa-text/chieru/proxy:/var/log/squid:z"
  chupa-text:
    image: groonga/chupa-text:ubuntu-latest
-    container_name: chloe-redmine-chupa-text
+    container_name: chieru-redmine-chupa-text
    restart: always
    depends_on:
      - chupa-text-proxy
    environment:
      TZ: Asia/Tokyo
      http_proxy: http://chupa-text-proxy:3128/
      https_proxy: http://chupa-text-proxy:3128/
      RAILS_SERVE_STATIC_FILES: "true"
    volumes:
-      - "/var/log/redmine-chupa-text/chloe/rails:/home/chupa-text/chupa-text-http-server/log:z"
+      - "/var/log/redmine-chupa-text/chieru/rails:/home/chupa-text/chupa-text-http-server/log:z"

volumes:
  dbdata:
/etc/nginx/conf.d/chieru-redmine.conf
server {
    listen 80;
    server_name  chieru.[your-domain];

    client_max_body_size  32m;

    proxy_buffer_size 32k;
    proxy_buffers 100 32k;

    proxy_set_header  Host              $http_host;
    proxy_set_header  X-Real-IP         $remote_addr;
    proxy_set_header  X-Forwarded-Host  $host:$server_port;
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://localhost:3002;
    }
}

おわりに

僕が初めて Redmine に触れてからおそらくは 8 年くらい、自分で構築するようになってから 5 年くらい経ちます。これまでに Bitnami RedmineALMinium を使って構築したり、 ソフトウェアエンジニアリング ― RedmineをCentOS 7上で動かすーUnicornとNginx編 を参考にスクラッチで構築したりしてきました。

しかし、どの方法で構築しても、インストールはともかく頻繁なバージョンアップにつらみを感じていました。スクラッチで構築したときには Ruby を rbenv でインストールしてみたり、Redmine プラグインやテーマを git submodule で Redmine 本体とリンクさせてみたり色々やってみたのですが、2, 3 くらいのインスタンスならまだしも、数十くらい…下手すりゃ百を超えてるかもしれないオーダーで存在するインスタンスのバージョンアップを面倒見るには限界がある、と。

そこで Docker に着目して、どうやったら最小の手数でインストールからバージョンアップまでできるかを考えてみました。それも素の Redmine ではなく、なるべく便利なプラグインや素敵なテーマを詰め込んだ状態で。この Docker Compose で作った Redmine は docker-compose build --no-cache を実行するだけで簡単に最新バージョンに更新することができます。そして、どうやったら誰でもこの Redmine を使って簡単にサーバー構築ができることが伝えられるか、ということで本記事を執筆しました。

このレシピはあくまで一例ですが、これでもっともっと多くの方が最新でプラグインやテーマが盛りだくさんな Redmine にすぐ触れられるようになって、Redmine 界隈がもっともっと盛り上がっていけばいいなと願っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

まだdocker-composeのホスト側portを考えるのに疲弊しているの? 〜IP指定してwell-known ports使い放題、同時に1677万案件回す〜

タイトルは釣りです

1677万案件は試してません

対象読者

  • 開発環境を docker-compose で構築しているWebプログラマの人々
  • 複数案件を抱え、ホスト側ポート番号を考えるのに疲弊している人々
    • HTTP 80 -> 80はとっておいて8080にしたろ!
    • あの案件で8080を使ったからこっちは10080にしたろ!
    • 10080の次だからとりあえず20080にしたろ!
    • うーん、8000!w
    • HMRで8080使いたいけれど使われてるから8081にしたろ!

問題提起

docker-compose.ymlの例

anken-1/docker-compose.yml
version: "3"
services:
  web:
    image: "nginx"
    ports:
      - "80:80" # ...... 1
    ...
  app:
    image: "php-fpm"
    ...
  db:
    image: "mysql:5.7"
    ports:
      - "3306:3306" # ...... 2
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  secure-db:
    image: "mysql:5.7"
    ports:
      - "3307:3306" # ...... 3
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  • ホストOSのブラウザで動作確認するためにHTTPサーバーのポートを公開する(80)
  • 同じくホストから繋ぐためにDBのポートを公開する(3306)
  • DBは複数立っていたりする(3307)

さて別の案件が降ってきました。

anken-2/docker-compose.yml
version: "3"
services:
  app:
    image: "httpd:2.4"
    ports:
      - "8080:80" # ...... 1
    volumes:
      - "./public/:/usr/local/apache2/htdocs/"
  db:
    image: "mysql:5.7"
    ports:
      - "13306:3306" # ...... 2
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  secure-db:
    image: "mysql:5.7"
    ports:
      - "13307:3306" # ...... 3
    environment:
      - "MYSQL_ROOT_PASSWORD=root"

先の案件で振ったポート番号とぶつからないように、well-knownポートの亜種をいい感じに考えて割り当てます

  • 80は別の案件で使ったので8080
  • 3306は別の案件で使ったので13306
  • 3307は別の案件で使ったので13307

さて最初の案件でHMRを導入したくなりました。

anken-1/docker-compose.yml
  version: "3"
  services:
    web:
      image: "nginx"
      ports:
        - "80:80"
      ...
    app:
      image: "php-fpm"
      ...
    db:
      image: "mysql:5.7"
      ports:
        - "3306:3306"
      environment:
        - "MYSQL_ROOT_PASSWORD=root"
    secure-db:
      image: "mysql:5.7"
      ports:
        - "3307:3306"
      environment:
        - "MYSQL_ROOT_PASSWORD=root"
+   node:
+     build:
+       ...
+     ports:
+       - "8080:8080" # ...... 4

HMR用のNode.jsサーバーを立てたくなりました。
んじゃ8080で…

anken-2/docker-compose.yml
...
  app:
    image: "httpd:2.4"
    ports:
      - "8080:80"
...

あ!8080を既に使っていました…

anken-1/docker-compose.yml
  version: "3"
  services:
    web:
      image: "nginx"
      ports:
        - "80:80"
      ...
    app:
      image: "php-fpm"
      ...
    db:
      image: "mysql:5.7"
      ports:
        - "3306:3306"
      environment:
        - "MYSQL_ROOT_PASSWORD=root"
    secure-db:
      image: "mysql:5.7"
      ports:
        - "3307:3306"
      environment:
        - "MYSQL_ROOT_PASSWORD=root"
+   node:
+     build:
+       ...
+     ports:
+       - "8081:8080" # ...... 4

8081にしておこう…と、こうなります。

別々の案件を抱える複数の人間が絡むと混乱は加速します。

そのうちポート番号込みでgit管理するのが嫌になってきて、

「ホスト側portはもう各自の.envで管理してくれ」

となります。

anken-1/docker-compose.yml
version: "3"
services:
  web:
    image: "nginx"
    ports:
      - "${WEB_HOST_PORT}:80"
    ...
  app:
    image: "php-fpm"
    ...
  db:
    image: "mysql:5.7"
    ports:
      - "${DB_HOST_PORT}:3306"
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  secure-db:
    image: "mysql:5.7"
    ports:
      - "${SECURE_DB_HOST_PORT}:3306"
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  node:
    build:
      ...
    ports:
      - "${NODE_HMR_HOST_PORT}:8080"
anken-1/env_example
WEB_HOST_PORT=80
DB_HOST_PORT=3306
SECURE_DB_HOST_PORT=3307
NODE_HMR_HOST_PORT=8080
anken-2/docker-compose.yml
version: "3"
services:
  app:
    image: "httpd:2.4"
    ports:
      - "${APP_HOST_PORT}:80"
    volumes:
      - "./public/:/usr/local/apache2/htdocs/"
  db:
    image: "mysql:5.7"
    ports:
      - "${DB_HOST_PORT}:3306"
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  secure-db:
    image: "mysql:5.7"
    ports:
      - "${SECURE_DB_HOST_PORT}:3306"
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
anken-2/env_example
APP_HOST_PORT=80
DB_HOST_PORT=3306
SECURE_DB_HOST_PORT=3307

幾分マシになった感じがしますが、しばらくすると

  • 「何箇所設定するね〜んww」
  • 「何もしてないのに docker-compose up -d が立ち上がらなくなった」
    • env_exampleの更新を.envへ反映し忘れている

等々の問題が生じてきます。

解決案 -- ポートをバインドするIPを指定する

公式docに書いてありますが、docker run -pではホスト側ポートをバインドするIPアドレスを指定できます。

1つは docker run の実行時、 -p IP:ホスト側ポート:コンテナ側ポート か
-p IP::ポート を指定し、特定の外部インターフェースをバインドする指定ができます。

「IPアドレス」というのは、は論理的なアドレスです。
物理的なホストに対して複数の論理的なアドレスが割り当て可能です。

往来で

「D.Horiyamaさんの80番ポートさん、HTMLファイルをください」

と言われたらHTMLファイルを返しますが、

「‡漆黒の堕天使‡さんの80番ポートさん、HTMLファイルをください」

と言われたら、こっ恥ずかしくて聞いてないふりしますよね。そういうことです。

「この名前で呼ばれた時だけ応答する」ということができるわけです。

さて、このホスト側IP指定、docker-compose.yml でも設定可能です:

docker-compose.yml
version: "3"
services:
  web:
    image: "httpd:2.4"
    ports:
      - "${IP}:80:80"
    volumes:
      - "./public/:/usr/local/apache2/htdocs/"
  db:
    image: "mysql:5.7"
    ports:
      - "${IP}:3306:3306"
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
  secure-db:
    image: "mysql:5.7"
    ports:
      - "${IP}:3307:3306"
    environment:
      - "MYSQL_ROOT_PASSWORD=root"
.env
IP=127.0.0.1

こんな風にします。

127.*.*.*ローカルループバックアドレス と呼ばれる特別なアドレスで、ホスト自身を指します。
127.0.0.1127.255.255.254 は全て別のIPアドレスであり、個々にポートをバインドできます。
これを使って、

「IP=127.0.0.1は案件1用」

というようにして、803306といったwell-known portを惜しげもなくじゃぶじゃぶ使うことができます。

$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                 NAMES
36d5b663edf6        mysql:5.7           "docker-entrypoint.s…"   3 seconds ago       Up 2 seconds        127.0.0.2:3306->3306/tcp, 33060/tcp   anken-2_db_1
c347fe183ad6        httpd:2.4           "httpd-foreground"       3 seconds ago       Up 2 seconds        127.0.0.2:80->80/tcp                  anken-2_web_1
8ad0a5ae018d        mysql:5.7           "docker-entrypoint.s…"   3 seconds ago       Up 2 seconds        33060/tcp, 127.0.0.2:3307->3306/tcp   anken-2_secure-db_1
6955a89c43d9        httpd:2.4           "httpd-foreground"       29 seconds ago      Up 28 seconds       127.0.0.1:80->80/tcp                  anken-1_web_1
f13161e0dfef        mysql:5.7           "docker-entrypoint.s…"   29 seconds ago      Up 28 seconds       33060/tcp, 127.0.0.1:3307->3306/tcp   anken-1_secure-db_1
c1905356e589        mysql:5.7           "docker-entrypoint.s…"   29 seconds ago      Up 28 seconds       127.0.0.1:3306->3306/tcp, 33060/tcp   anken-1_db_1

80,3306,3307をanken-1, anken-2それぞれで利用できています。

127.0.0.1:80, 127.0.0.2:80 にリクエストしてみると:

$ curl 127.0.0.1:80
<html>案件1</html>

$ curl 127.0.0.2:80
<html>案件2</html>

ちゃんと、それぞれ別々のサーバーに処理されていることがわかります。


さて、IPv4発明当初の仕様策定陣は、これほどまでにインターネットが隆盛を極めるとは考えていなかったのでしょう。

全アドレス空間のうち1/256 -- 約1677万個 -- もの膨大なアドレス空間をローカルループバックアドレスに宛ててしまいました。
(グローバルIPアドレスとして使えなくなってしまいました)

おかげさまで、この方法で、理論的には約1677万案件ぶんの docker-compose 環境を同時に抱えられます!(タイトル回収)

生のIPアドレスはつらいので、/etc/hostsを書いたりDNSを立てたりするとより便利になるでしょう:

/etc/hosts
...
127.0.0.1 anken-1
127.0.0.2 anken-2
...

curl

$ curl anken-1:80
<html>案件1</html>

mysql

$ mysql -h anken-1 -u root -proot

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.27 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> \q

nginx-proxyとの違い

nginx-proxy

?「nginx-proxyでいいんじゃ?」

nginx-proxyはHTTPにしか使えないと聞きます。(使ったことない)

対して、本記事の方法はHTTP以外にも適用可能です。

macOS固有の問題

Docker Desktop for mac 2.2.x の不具合

不具合スレ

Docker Desktop for macでは、2.2.x系にて不具合があり、127.0.0.1(デフォルト)以外のIPアドレスのポートをバインドできなかったようです。

さっさと2.3系に上げましょう。

127.0.0.1 以外のローカルループバックアドレスがデフォルトで使えない

困ってる人々

デフォルトでping 127.0.0.2すら通りません。ifconfigでエイリアス定義する必要があります。

下記のようなスクリプトを .zshrcなどで実行するとよいでしょう:

for ((i=2;i<256;i++))
do
    sudo ifconfig lo0 alias 127.0.0.$i up
done
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

centOS8でDockerコンテナから名前解決ができない話をnftコマンドで解決する

はじめに

centOS8にてDocker動かそうと思ったら下記エラーが出て名前解決ができず、カスタムイメージが作れなかった問題を解決できたのでメモ

$ docker run -ti --rm busybox nslookup google.com
nslookup: write to '<ホスト指定のDNS>': No route to host
;; connection timed out; no servers could be reached

TL:DR

結論から言えばCentOS8からデフォルトとなったNFTablesにおいて下記コマンドを実施し、コンテナ内部からのICMP以外のパケットがドロップされないようにする。

nft add rule inet firewalld filter_FWDI_public_allow counter udp dport 53 accept

もしくはもっと簡潔に--net=hostオプションをつける方法やfirewall-cmdを使う方法(CentOS8にDockerをインストール。名前解決できなかったのが解消した。)、バックエンドをiptablesに置き換える方法(CentOS8でDocker CEを使うのは(現状は)やめとけという話)などがあるようです。いろいろなアプローチ方法があります。

前提条件

筆者の環境は、centos8をVirtualBOXにminimalインストールした下記の通りです。

$ cat /etc/os-release 
NAME="CentOS Linux"
VERSION="8 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Linux 8 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-8"
CENTOS_MANTISBT_PROJECT_VERSION="8"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="8"
$ nft --version
nftables v0.9.0 (Fearless Fosdick)

事の成り行きから話すと、まずcentos8にDockerをインストール後(そもそも違うの使えというのが上で挙げた物ですが)、自作イメージをビルドしようとしたら名前解決ができずにエラー。
Dockerコンテナ内からpingはホストのGatewayに通るが、名前解決しようとするとicmp type3 (Destination Unreachable)が帰ってくる。
iptablesでUDPパケットの到達点を地道に調べてみても(参考:Netfilterの処理順序)、POSTROUTING targetに到達する直前にはfilter tableのFORWORD targetにてACCEPTされているハズなのにそこでどこかに消えている。。。

解決

centos8になったことでnetfileterに対するインターフェースがiptablesからnftables, firewalldになっているということだったので試しにnftコマンドによってフィルタリングルールを見てみるとrejectしている奴がいました。

$ nft list ruleset | grep -n reject
244:        reject with icmpx type admin-prohibited
252:        ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 type addr-unreachable
258:        reject with icmpx type admin-prohibited
264:        ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 type addr-unreachable

今回hook forwardしているchainを知りたいので、上記のrejectを行うルールを含むchainを見てみると
ADDRESS FAMILY: inet
TABLE : firewalld
CHAIN : filter_FORWARD
のものがどうやらdocker0からのパケットをrejectしてしまっているようです(iptablesで定義されているTABLEとは異なるため、nftじゃないと参照できない)。

$ nft list chain inet firewalld filter_FORWARD
table inet firewalld {
    chain filter_FORWARD {
        type filter hook forward priority 10; policy accept;
        ct state established,related accept
        ct status dnat accept
        iifname "lo" accept
        ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 type addr-unreachable
        jump filter_FORWARD_IN_ZONES_SOURCE
        jump filter_FORWARD_IN_ZONES
        jump filter_FORWARD_OUT_ZONES_SOURCE
        jump filter_FORWARD_OUT_ZONES
        ct state invalid drop
        reject with icmpx type admin-prohibited # ここまでにacceptされなければrejectされてしまう
    }
}

ちなみにフィルタリングの評価順序についてはこちらの開発元のWikiを参考にすると、priorityの値が小さい順にBase Chain(typeとhookが定められているChain)で評価されていって、dropされない限りは全てのChainを通過するようです。
つまりは上記のchainのなかでrejectされる前のどこかでacceptしてしまえばDNS用のパケットは通過できるようになるハズ(その他の部分ではacceptされるようになっていることを確認済)。
ルールを定めるため、jumpなどからそれっぽいところを辿っていくとfilter_FWDI_public_allowというchainを見つけました。

$ nft list chain inet firewalld filter_FORWARD_IN_ZONES
table inet firewalld {
    chain filter_FORWARD_IN_ZONES {
        iifname "enp0s3" goto filter_FWDI_public
        goto filter_FWDI_public
    }
}
$ nft list chain inet firewalld filter_FWDI_public
table inet firewalld {
    chain filter_FWDI_public {
        jump filter_FWDI_public_pre
        jump filter_FWDI_public_log
        jump filter_FWDI_public_deny
        jump filter_FWDI_public_allow
        jump filter_FWDI_public_post
        meta l4proto { icmp, ipv6-icmp } accept # pingだけ通るようになっていた部分の設定
    }
}
$ nft list chain inet firewalld filter_FWDI_public_allow
table inet firewalld {
    chain filter_FWDI_public_allow {
    }
}

二つ目の、filter_FWDI_publicでicmpはacceptしているからpingだけ通るのだなと納得。
そういうわけで冒頭のコマンドを持ってしてDNS用のパケットをここでacceptするようにすると、無事コンテナ内部から名前解決できるようになりました

$ nft add rule inet firewalld filter_FWDI_public_allow counter udp dport 53 accept
$ nft list chain inet firewalld filter_FWDI_public_allow
table inet firewalld {
    chain filter_FWDI_public_allow {
        counter packets 0 bytes 0 udp dport domain accept #<-追加されたもの
    }
}
$ docker run -ti --rm busybox nslookup google.com
Server:     <ホスト指定のDNS>
Address:    <ホスト指定のDNS>:53

Non-authoritative answer:
Name:   google.com
Address: 172.217.25.238

$ nft list chain inet firewalld filter_FWDI_public_allow
table inet firewalld {
    chain filter_FWDI_public_allow {
        counter packets 2 bytes 112 udp dport domain accept # counterをつけているため、パケットが通過したことが分かる
    }
}

ちなみに同様の手順でhttpを許可するとコンテナ内部でパッケージマネージャも動作するようになります。

$ nft add rule inet firewalld filter_FWDI_public_allow tcp dport { 80,443 } accept
$ nft list chain inet firewalld filter_FWDI_public_allow
table inet firewalld {
    chain filter_FWDI_public_allow {
        counter packets 0 bytes 0 udp dport domain accept
        tcp dport { http, https } accept #<-追加されたもの
    }
}

$ docker run -ti --rm alpine apk add openssl
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
(1/1) Installing openssl (1.1.1g-r0)
Executing busybox-1.31.1-r9.trigger
OK: 6 MiB in 15 packages

めでたしめでたし

追加調査:Netfilterの評価順について

上記で問題は解決しましたが、nftについて少し学んだので合わせてメモ
centos8インストールした状態で、hook forwardに纏わるチェーンは以下のように設定されていました。

$ nft list chains | sed 's/^}$/}\n/' | awk 'BEGIN{RS="";FS="\n"} { for(i=0;i<NF;i++){ if( match($i, "chain") && match($(i+1), "forward") )  {print $1, $i, $(i+1)} } }'
table ip filter {   chain FORWARD {         type filter hook forward priority 0; policy drop;
table ip6 filter {  chain FORWARD {         type filter hook forward priority 0; policy accept;
table bridge filter {   chain FORWARD {         type filter hook forward priority -200; policy accept;
table ip security {     chain FORWARD {         type filter hook forward priority 150; policy accept;
table ip mangle {   chain FORWARD {         type filter hook forward priority -150; policy accept;
table ip6 security {    chain FORWARD {         type filter hook forward priority 150; policy accept;
table ip6 mangle {  chain FORWARD {         type filter hook forward priority -150; policy accept;
table inet firewalld {  chain filter_FORWARD {      type filter hook forward priority 10; policy accept;

本稿の上部で触れたPriorityの話と照らし合わせると、forwardされるパケットは
bridge filter -> ip(ip6) mangle -> ip(ip6) filter -> inet firewalld -> ip(ip6) security
の順で通過するハズである。
テストのため、下記のようにログを取得するルールを追加する。

$ while read AF T C ORD;do
> nft insert rule $AF $T $C counter log prefix \""$ORD FORWARD>> "\"
> done<<EOF
> bridge         filter         FORWARD        1st
> ip             mangle         FORWARD        2nd
> ip6            mangle         FORWARD        3rd
> ip             filter         FORWARD        4th
> ip6            filter         FORWARD        5th
> inet           firewalld      filter_FORWARD 6th
> ip             security       FORWARD        7th
> ip6            security       FORWARD        8th
> EOF

ログ機能が追加されたことを確認。

$ nft list ruleset | grep -v '^$' | sed 's/^}$/}\n/' | awk 'BEGIN{RS="";FS="\n"} { for(i=0;i<NF;i++){ if( match($i, "chain") && match($(i+1), "forward") && match($(i+2), "log"))  {print $1, $i, $(i+1),$(i+2)} } }'
table ip filter {       chain FORWARD {                 type filter hook forward priority 0; policy drop;               counter packets 0 bytes 0 log prefix "4th FORWARD>> "
table ip6 filter {      chain FORWARD {                 type filter hook forward priority 0; policy accept;             counter packets 0 bytes 0 log prefix "5th FORWARD>> "
table bridge filter {   chain FORWARD {                 type filter hook forward priority -200; policy accept;          counter packets 0 bytes 0 log prefix "1st FORWARD>> "
table ip security {     chain FORWARD {                 type filter hook forward priority 150; policy accept;           counter packets 0 bytes 0 log prefix "7th FORWARD>> "
table ip mangle {       chain FORWARD {                 type filter hook forward priority -150; policy accept;          counter packets 0 bytes 0 log prefix "2nd FORWARD>> "
table ip6 security {    chain FORWARD {                 type filter hook forward priority 150; policy accept;           counter packets 0 bytes 0 log prefix "8th FORWARD>> "
table ip6 mangle {      chain FORWARD {                 type filter hook forward priority -150; policy accept;          counter packets 0 bytes 0 log prefix "3rd FORWARD>> "
table inet firewalld {  chain filter_FORWARD {          type filter hook forward priority 10; policy accept;            counter packets 0 bytes 0 log prefix "6th FORWARD>> "

hook forwardさせるため、コンテナ内部から各種通信を行う。
まずはコンテナからホストネットワークへの疎通確認(ping)。ここでは上記の中からアドレスファミリーとしてip, inetが選択されるため、その中のテーブルを通過することになる。そのためsyslogには2nd, 4th, 6th, 7thのログが出力されることになる。

### in container
$ ping 192.168.13.1
### in host
$ tail -n0 -f /var/log/messages | sed 's/... .. ..:..:../MON DD hh:mm:ss/'
MON DD hh:mm:ss centos8 kernel: 2nd FORWARD>> IN=docker0 OUT=enp0s3 PHYSIN=veth8ee9805 MAC=02:42:04:70:8d:9d:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.13.1 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=10740 DF PROTO=ICMP TYPE=8 CODE=0 ID=1536 SEQ=0 
MON DD hh:mm:ss centos8 kernel: 4th FORWARD>> IN=docker0 OUT=enp0s3 PHYSIN=veth8ee9805 MAC=02:42:04:70:8d:9d:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.13.1 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=10740 DF PROTO=ICMP TYPE=8 CODE=0 ID=1536 SEQ=0 
MON DD hh:mm:ss centos8 kernel: 6th FORWARD>> IN=docker0 OUT=enp0s3 PHYSIN=veth8ee9805 MAC=02:42:04:70:8d:9d:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.13.1 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=10740 DF PROTO=ICMP TYPE=8 CODE=0 ID=1536 SEQ=0 
MON DD hh:mm:ss centos8 kernel: 7th FORWARD>> IN=docker0 OUT=enp0s3 PHYSIN=veth8ee9805 MAC=02:42:04:70:8d:9d:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.13.1 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=10740 DF PROTO=ICMP TYPE=8 CODE=0 ID=1536 SEQ=0

次にipv6でのコンテナ間疎通確認。
ここではアドレスファミリーとしてbridge(dockerによって自動生成されたブリッジデバイス間(vethxxxxx)でforwardするため), ip6, inetが選択されるハズなので、本稿で設定した中では 1st, 3rd, 5th, 6th, 8thのhook forwardを経由する。
dockerではipv6はデフォルトでdisableになっているので、こちらに従い各種設定が必要。
バージョンの違いかデフォルトのデバイス(docker0)にipv6のアドレスプールが割り当てられなかったが、直接プールを指定して作成したらなんとかできた。

### in host
# daemonの設定
$ vim /etc/docker/daemon.json
# 下記の設定を書き加える、もしくはデーモン起動オプションに指定
{
 "ipv6": true
}

# 設定のリロード
$ systemctl reload docker

# docker networkの作成
$ docker network create  --ipv6 --subnet fe80:0:0:42:ff::/80 mynetv6

# 上記ネット内でコンテナを二台立ち上げる
$ docker run --rm -ti --net mynetv6 --name C1 busybox sh 
$ docker run --rm -ti --net mynetv6 --name C2 busybox sh 

### in container C1
$ ping -6 C2

### in host
$  tail -n0 -f /var/log/messages | sed 's/... .. ..:..:../MON DD hh:mm:ss/'
MON DD hh:mm:ss centos8 kernel: 1st FORWARD>> IN=veth08a234c OUT=veth116ee6a MAC=33:33:00:00:00:02:02:42:ac:1f:00:02:86:dd SRC=fe80:0000:0000:0042:00ff:0000:0000:0002 DST=ff02:0000:0000:0000:0000:0000:000
0:0002 LEN=56 TC=0 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=133 CODE=0                                                                                                                                      
MON DD hh:mm:ss centos8 kernel: 3rd FORWARD>> IN=br-7e666194822f OUT=br-7e666194822f PHYSIN=veth08a234c PHYSOUT=veth116ee6a MAC=33:33:00:00:00:02:02:42:ac:1f:00:02:86:dd SRC=fe80:0000:0000:0042:00ff:0000:
0000:0002 DST=ff02:0000:0000:0000:0000:0000:0000:0002 LEN=56 TC=0 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=133 CODE=0                                                                                       
MON DD hh:mm:ss centos8 kernel: 5th FORWARD>> IN=br-7e666194822f OUT=br-7e666194822f PHYSIN=veth08a234c PHYSOUT=veth116ee6a MAC=33:33:00:00:00:02:02:42:ac:1f:00:02:86:dd SRC=fe80:0000:0000:0042:00ff:0000:
0000:0002 DST=ff02:0000:0000:0000:0000:0000:0000:0002 LEN=56 TC=0 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=133 CODE=0                                                                                       
MON DD hh:mm:ss centos8 kernel: 6th FORWARD>> IN=br-7e666194822f OUT=br-7e666194822f PHYSIN=veth08a234c PHYSOUT=veth116ee6a MAC=33:33:00:00:00:02:02:42:ac:1f:00:02:86:dd SRC=fe80:0000:0000:0042:00ff:0000:
0000:0002 DST=ff02:0000:0000:0000:0000:0000:0000:0002 LEN=56 TC=0 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=133 CODE=0                                                                                       
MON DD hh:mm:ss centos8 kernel: 8th FORWARD>> IN=br-7e666194822f OUT=br-7e666194822f PHYSIN=veth08a234c PHYSOUT=veth116ee6a MAC=33:33:00:00:00:02:02:42:ac:1f:00:02:86:dd SRC=fe80:0000:0000:0042:00ff:0000:
0000:0002 DST=ff02:0000:0000:0000:0000:0000:0000:0002 LEN=56 TC=0 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=133 CODE=0 

というわけで、無事全ての設定したhook forwardを通過し、想定通りの動作をしていることが確かめられました。

終わりに

元の問題はnftablesとdockerの不整合でしたが、どうせならnftables扱えるようになろうという事で取り組み始めました。ネットワークはやはり奥が深いですね。
記事書くに当たりiptables, nftables, firewalldの関係性が曖昧でしたが、netfilterとfirewalldとiptablesとnftablesの関係の記事がわかりやすくまとめてくださっていたので、ここまで来てくださった方がいたら是非ご覧ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む