1月 292023
 

タイトルが大げさですなぁ;-)。

前回のエントリで構築した VRF が動作する AlmaLinux 8.7 ですが、その AlmaLinux8 は実は Docker ホストとしても動作しています。

 
これも、以前のエントリで「Docker Registry を作る。」というのを書いていますが、このホストと同一となります。

 
今回はこの Docker ホストに Docker コンテナを複数起動して、外部からアクセスできる環境を構築します。
Docker コンテナに対して外部からアクセスするためのポート番号は 80 番 (別に 443 でも良いのだけど、証明書関係が面倒だったので、Port:80 で許してちょんまげ;-) とします。しかし、複数の Docker コンテナが全て Port:80 番で待ち受けるためには NAT 環境では無理です。

今回は Docker ホストとコンテナ間をブリッジで接続して、Docker ホストで利用している外部接続ネットワークをコンテナでもそのまま利用するようにします。

 
図はこんな感じ。

 
VMware ESXi に二つの仮想マシンが動作しています。一個は Docker ホストで、もう一個は FreeBSD が動作して MySQL のサービスを他の仮想マシンに提供しています。

 
で、せっかくなので Docker コンテナがただ HTML なコンテンツを垂れ流す環境を構築してもつまらいないので WordPress が動作する環境を構築したいと思います。
ネタが二つ分の量だけど、大丈夫かなぁ・・。

と、いうことで、まずは Docker ホストの準備から。

とはいいつつ、以前、Docker Registry を作った環境をそのまま利用しています。ただ、 dnf update しているので多少バージョンが上がっているかも;-)。

$ cat /etc/os-release 
NAME="AlmaLinux"
VERSION="8.7 (Stone Smilodon)"
ID="almalinux"
ID_LIKE="rhel centos fedora"
VERSION_ID="8.7"
PLATFORM_ID="platform:el8"
PRETTY_NAME="AlmaLinux 8.7 (Stone Smilodon)"
ANSI_COLOR="0;34"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:almalinux:almalinux:8::baseos"
HOME_URL="https://almalinux.org/"
DOCUMENTATION_URL="https://wiki.almalinux.org/"
BUG_REPORT_URL="https://bugs.almalinux.org/"

ALMALINUX_MANTISBT_PROJECT="AlmaLinux-8"
ALMALINUX_MANTISBT_PROJECT_VERSION="8.7"
REDHAT_SUPPORT_PRODUCT="AlmaLinux"
REDHAT_SUPPORT_PRODUCT_VERSION="8.7"

$ rpm -qa | grep docker
docker-ce-20.10.22-3.el8.x86_64
docker-ce-rootless-extras-20.10.22-3.el8.x86_64
docker-scan-plugin-0.23.0-3.el8.x86_64
docker-ce-cli-20.10.22-3.el8.x86_64

 
いろいろ端折って、既に dockerd が起動しているものとして話を進めていきます。Docker のインストールと起動については他のサイトを参考にしてください。

 
1. Docker イメージの作成

$ docker search almaLinux
NAME                       DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
almalinux                  The official build of AlmaLinux OS.             102       [OK]       
<以下略>

$ docker pull almaLinux
Using default tag: latest
latest: Pulling from library/almalinux
<略>

$ docker image ls
REPOSITORY	TAG       IMAGE ID       CREATED        SIZE
almalinux       latest    acaca326f3b3   6 weeks ago    190MB

$ docker tag almalinux:latest almalinux8:1
$ docker image rm almalinux:latest

$ docker image ls
REPOSITORY	TAG       IMAGE ID       CREATED        SIZE
almalinux8      1         acaca326f3b3   6 weeks ago    190MB

$ docker run -it --name almalinux8 almalinux:1 /bin/bash
[root@b2db6f534790 /]# 

 
docker search で AlmaLinux を探して、オフィシャルサイトから pull して、 almalinux8:1 としてタグ付けしました。
このあと、docker run してコンテナの中に入って dnf update して、必要な rpm をインストールして httpd や php もインストールして、もとの Docker イメージを大幅に更新して tag 付けして Docker イメージを完成させます。

この辺り割愛しますが、各自で頑張って Docker イメージを作成してください;-)。

$ docker stop almalinux8
$ docker container commit almalinux8 almalinux8_wordpress:1
$ docker rm almalinux8

 
apache や php をインストールした新しい almalinux8_wordpress:1 という Docker イメージができました。このイメージをベースに色々やっていきましょう。

 
2. Docker コンテナで利用するネットワークの準備
次にネットワークの設定を行います。今回は複数の Docker コンテナに Dokcer ホストと同じセグメントを割り当てます。
僕の環境の AlmaLinux 8.7 は VMware ESXi 上で動作しています。そして、ネットワーク構成は前回の「AlmaLinux8 の VRF。」に記載してある以下の構成をそのまま利用します。

 
つまり、Service-segment 側はウェブアクセスのために利用し Access-Segment は MySQL アクセスで利用します。あ。MySQL サーバは 192.168.52.204 の FreeBSD 上で動作しています。

箇条書きにするとこんな感じ。

  • WordPress が動作する予定の Docker コンテナは外部からのアクセスは Port:80 で eth0 側に到達するように構築します。
  • その Docker コンテナは外部の MySQL サーバにアクセスするために eth1 のインターフェースを利用します。
  • Docker コンテナは二つのインターフェースを持ち、それぞれが Docker ホストからのブリッジで動作します。
  • 上記の図では、Server1 が Docker ホストで、Server3 が FreeBSD で MySQL サーバが動作しています。

 
と、いうような状況ですな。

その VMware ESXi が Docker ホストに接続するポートグループは「セキュリティ」において「無差別モード」「MACアドレレス変換」「偽装転送」の全てを『承諾』に設定します。eth0 と eth1 の二つのポートグループはブリッジインターフェースを利用するので無差別モード(俗に『プロミスキャスモード』と、言いますな)を有効にする必要があります。
VMware ESXi のネットワークの設定もちゃんとしましょう。

 
3. Docker network でネットワークの作成
と、いうことで Docker コンテナが利用する二つのネットワークを docker network コマンドで作成します。

$ docker network create Service-Segment \
     -o "com.docker.network.bridge.name"="br0" \
     --driver=bridge \
     --subnet 192.168.22.0/24 \
     --gateway 192.168.22.217

# brctl addif br0 eth0
# ifconfig eth0 0.0.0.0 up
# ip link set up dev br0
# route add default gw 192.168.22.1

$ docker network create Access-Segment \
     -o "com.docker.network.bridge.name"="br1" \
     --driver=bridge \
     --subnet 192.168.52.0/24 \
     --gateway 192.168.52.217

# brctl addif br1 eth1
# ifconfig eth1 0.0.0.0 up
# ip link set up dev br1

 
Docker ホストの eth0 の IP アドレスは 192.168.22.217 で、eth1 側は 192.168.52.217 です。

こちらも説明は箇条書きで。

  • eth0 の Service-Segment と eth1 の Access-Segment の Docker ネットワークを作成します。
  • このとき Docker ホストには docker0 というインターフェースが存在していますが、利用しないのでそのまま存在しつつ、無視して問題はありません。
  • NetworkManager でブリッジインターフェースの設定はしません。 /etc/sysconfig/network-scripts/ 配下に ifcfg-br0 などのファイルを置いてもなんの意味もありません。 docker network create コマンドを実行したときに付加するオプションである -o “com.docker.network.bridge.name”=”br0” があるので、既に br0 が存在しているとエラーになります。
  • –gateway オプションに指定する IP アドレスは Docker ホストの IP アドレスになります。
  • AlmaLinux 8.7 に bridge-utils の rpm はないようなのでソースからコンパイルしてインストールします。
  • brctl addif コマンドの実行や eth0 からアドレスを消して br0 にアドレスを付加するなどは全てコマンドで行います。
$ docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
039e8158c77b   Access-Segment    bridge    local
622593f73c12   Service-Segment   bridge    local
9eb6f5610d75   bridge            bridge    local
d30c681a9f92   host              host      local
afbbadaba67f   none              null      local

 
無事に作成できるとこんな感じでしょうか。
ifconfig -a したときには br0 と br1 に IP アドレスが付いていて、eth0 と eth1 には IP アドレスはついていない状態が正解です。

これで Docker ホストとコンテナで同一セグメントを利用する環境が整いました。次にコンテナを run してみましょうかねぇ;-)。

 
4. Docker コンテナの起動
まず、いきなり WordPress を動かすコンテナを起動するのではなく Docker ネットワークに接続するために docker run してみましょう。

$ docker run -d --restart always \
 --net Service-Segment --ip 192.168.22.222 \
 -v /var/www/html/wordpress:/var/www/html \
 -v /etc/httpd/wordpress/conf.d:/etc/httpd/conf.d \
 -v /var/logs/httpd/wordpress/logs:/var/log/httpd \
 --log-driver=syslog --log-opt syslog-facility=local3 --log-opt tag=docker/{{.Name}}/{{.ID}} \
 --name almalinux8_wordpress \
 -d almalinux8:2 /usr/sbin/httpd -DFOREGROUND

 
今回は箇条書きではなく、ベタガキで・・f(^^;;。
-p 0.0.0.0:80:80 というオプションは指定しません。これ付けると一個の Docker コンテナが Docker ホストの Port:80 を掴んでしまいます。
–net で接続する Docker ネットワークと –ip でDocker コンテナが使用する IP アドレスを指定します。今回は固定 IP アドレスで 192.168.22.222 を利用します。docker network のオプションではレンジとか CIDR を指定することができて DHCP みたいな利用形態もできますが、ここでは割愛します。自分で調べてください。
-v で Docker ホスト側のディレクトリをコンテナ側にマウントしています。 Docker イメージを色々なサービスで使い回すならこれらは外出しした方が良いかなぁ。と、僕は思ったので Docker イメージには組み込んでいません。自分・環境のお好みで設定してみてください;-)。
–log-driver=syslog は syslog 出力するための設定です。 /etc/rsyslog.conf には以下を記載してください。

local3.*      /var/log/docker/container.log

 
書いたあと rsyslogd を再起動して、ついでに mkdir /var/log/docker してください。

 
と、いうことで、ここまでは Docker コンテナをブリッジインターフェース経由で外部公開するための起動方法になります。
ただ、これだと MySQL サーバにアクセスできないので、起動した almalinux8_wordpress コンテナに eth1 を生やしてあげるコマンドを投入します。

$ docker network connect Access-Segment --ip 192.168.52.222 almalinux8_wordpress

$ docker exec -it almalinux8_wordpress /bin/bash
[root@64583900cb33 /]# ifconfig -a
eth0: flags=4163  mtu 1500
        inet 192.168.22.222  netmask 255.255.255.0  broadcast 192.168.22.255
        ether 02:42:c0:a8:16:de  txqueuelen 0  (Ethernet)
        RX packets 27884  bytes 2705253 (2.5 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 257  bytes 754283 (736.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth1: flags=4163  mtu 1500
        inet 192.168.52.222  netmask 255.255.255.0  broadcast 192.168.52.255
        ether 02:42:c0:a8:34:de  txqueuelen 0  (Ethernet)
        RX packets 36681  bytes 4887852 (4.6 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 586  bytes 113982 (111.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
<以下略>
[root@64583900cb33 /]# exit

 
と、いう感じで docker network connect コマンドを実行したので Docker コンテナには二つのインターフェースができました。

eth0 側は Dockerホスト外部から httpd に対してアクセスができていると思います。コンテナ起動時に指定した -v /var/www/html/wordpress:/var/www/html のオプションがありますが、 Docker ホスト側の /var/www/html/wordpress/ ディレクトリ配下に WordPress のコンテンツ一式が既にインストールされていると想定しています。

裏を返すと /var/www/html/wordpress/ 配下のコンテンツを何個かの Docker コンテナに -v でマウントさせると Docker コンテナのみで冗長構成を組むことができるのですな。 eth0 側のインターフェースは docker run 時に –ip で指定する IP アドレスを変えつつ起動したあと DNS ラウンドロビンでも良いし、ロードバランサ配下のネットワークに置いても良い。今まで複数の物理や仮想マシンが担当していた部分を Docker コンテナでマカナエル。と、いうことですな。

あ。ちなみに、僕の場合は WordPress の初期環境一式は FreeBSD の ports からインストールして、 FreeBSD 上の /usr/local/www/wordpress/ ディレクトリごと tar でかためて持ってきました;-)。簡単に初期の環境が整うです。で、 IP アドレスにアクセスすると「さぁ。わーどぷれすをいんすとーるしましょうっ!!」みたいな画面がいきなりでます;-)。
あ。実はエラーになってそう簡単には出ないか・・f(^^;;。

 
そーそー。注意点が一個。 NetworkManager を利用しないでブリッジインターフェースの設定をしているので、サーバに再起動が入ると設定が全て飛んでしまいます。 Docker ネットワークの設定は Docker が覚えていてくれるんだけど、ブリッジを設定するためのコマンド brctl addif の設定は再起動で消えてしまうので /etc/rc.local などでリカバリーする必要があるです。

 
5. Docker コンテナ の WordPress 対応
僕は普段 FreeBSD 上で WordPress を動作させているので Linux 上で動作させるには随分難儀なことでして・・。

まずは Docker イメージを作成するとこでイメージに対して httpd とか php をインストールします。そのあと、AlmaLinux8 の rpm の httpd は mod_mpm_event.so で動作しているようで、これをひとまず mod_mpm_prefork.so に変更します。
Docker イメージ内の /etc/httpd/conf.modules.d/00-mpm.conf の設定ファイルの中で LoadModule mpm_event_module modules/mod_mpm_event.so の行をコメントアウトし LoadModule mpm_prefork_module modules/mod_mpm_prefork.so の行を有効化します。
これの変更で一回 docker container commit する必要がありますな。

次に Linux で WordPress を動作させるためには php-fpm というのを起動する必要があるのだそうな。と、いうことで、これも Docker イメージ内でちょっと細工をします。

mkdir /run/php-fpm と chown 48:48 /run/php-fpm ですね。一応、この二つのコマンドを Docker コンテナ内で実行し、docker container commit して環境を整えます。

ふぅ。これで準備整ったかな?と、いうことで、 WordPress が動作する Docker コンテナを起動するスクリプトのまとめとしましょう。

#!/bin/sh

docker run -d --restart always \
 --net Service-Segment --ip 192.168.22.222 \
 -v /var/www/html/wordpress:/var/www/html \
 -v /etc/httpd/conf.d/wordpress:/etc/httpd/conf.d \
 -v /var/logs/httpd/wordpress/logs:/var/log/httpd \
 --log-driver=syslog --log-opt syslog-facility=local3 --log-opt tag=docker/{{.Name}}/{{.ID}} \
 --name almalinux8_wordpress \
 -d almalinux8:2 /usr/sbin/httpd -DFOREGROUND

docker network connect Access-Segment --ip 192.168.52.222 almalinux8_wordpress

export CONTAINER_ID=`docker ps -a | grep " almalinux8_wordpress" | awk '{print $1}'`
export DOCKER_PID=`docker inspect --format '{{.State.Pid}}' $CONTAINER_ID`

sudo nsenter -t $DOCKER_PID -n route delete default
sudo nsenter -t $DOCKER_PID -n route add default gw 192.168.22.217 eth0
sudo nsenter -t $DOCKER_PID -n ping -c 3 192.168.22.217

sudo nsenter -t $DOCKER_PID -m -u -i -n -p -- /usr/sbin/php-fpm --nodaemonize &

 
こんな感じでしょうか。

ちょっと説明を。

  • docker run で Service-Segment の Docker ネットワークに接続し、固定 IP アドレスを利用します。
  • -v で Docker ホスト側に用意したコンテンツや設定ファイルを参照するようにします。
  • コンテナの syslog を Docker ホスト側に出力します。 docker run の説明はここまで。
  • 次に、起動した almalinux8_wordpress に対して、もう一個の Docker ネットワークを接続します。
  • その次は nsenter コマンド連発です;-)。 nsenter コマンドは docker exec しなくとも Docker ホスト側から Docker コンテナに対してコマンドを投入できるコマンドです。そのためには当該の Docker コンテナのプロセス番号が必要になるのでそれを取得します。
  • コンテナ内に eth0 と eth1 が存在するので default gateway を設定してあげます。一旦削除してから再度設定。みたいな感じです。
  • コンテナ内から Docker ホストに対して ping を打つのは、コンテナ起動ごとに MAC アドレスが書き換わるので、上位のルータに対して MAC アドレスの更新をする必要があるためです。ルータ側の MAC アドレス書き換えタイムアウトまで待っても良いですねぇ。根が短気な性分なものでして・・f(^^;;。
  • そして、最後の nsenter コマンドは Linux で WordPress を動かすための php のおまじない。 /usr/sbin/php-fpm を nsenter コマンドで起動してあげます。Docker コンテナ内に入って ps -ax すると httpd と php-fpm の二種類のプロセスが一個のコンテナ内で起動する状態となります。つまり、 php-fpm 専用コンテナは不要である。と、いうことです。
    例えば、(本来、動かす必要はないのだけど) zabbix_agent なども nsenter で動かすことができます。コンテナ自体が固定 IP を利用しているので Zabbix Agent のポートにもアクセスできるようになります。

 
これで Service-Segment 側に指定した IP アドレスにウェブブラウザからアクセスすると、WordPress の初期設定画面が表示できると思います。

データベースは MySQL ですが 192.168.52.204 で動作している MySQL サーバへのアクセスは eth1 側を抜けていくので WordPress の初期設定画面で IP アドレス・ユーザ名・パスワードを設定するとアクセスできると思います。
あ。当然、 MySQL 側ではデータベースとアクセス用のアカウントを作成しておいてください。ここでは割愛しています。

 
さてと。ここまで来てファイアーウォールの設定が一回も登場していません。Docker にはファイアーウォールは必須なので、Docker ホスト側で systemctl start firewalld は必須です。僕の場合は /etc/firewalld/direct.xml で設定していますが、 -i で指定するインターフェース名は eth0 と br0 、 eth1 と br1 の両方を記述する必要があるので、その点は注意してください。

 
あとは・・。

これで多分大丈夫だと思います。

起動する Docker コンテナは、コンテンツと設定ファイル、そして、ログ出力を -v で Docker ホスト側の情報をマウントしているので docker run 時に違うディレクトリを指定し、他にネットワーク、IP アドレスの設定を変えるだけで一個の Docker イメージで複数の、用途の違う Docker コンテナを起動して同じ仕事をしてくれるようにすることも可能です。んー。 Docker らしい使い方ぁ;-)。

 
Docker って、基本 NAT が当たり前で、ブリッジインターフェースを利用した環境で『コンテナに対して直接アクセス』。っての、google で検索してもネタ的に今のところはあまり多くないかな。
しかし、実際にコンテナ使い込むとコンテナごとにポート番号変えるのって、なんか納得いかなくて、その先に『こんなんじゃ実際のサービスに提供できないじゃん。』と、いうところからブリッジインターフェースでコンテナに直接アクセス、ロードバランサから見ると仮想マシンもコンテナも同一の扱いのほうが楽じゃん。と、いう発想があります。そのためにはやっぱりブリッジだよねぇ。みたいな。

 
それにしても、実際に調べてみると Linux の bridge-utils ツール群はヘボいですな。今回、実は IPv6 のことについて全く書いてないんですよ。 bridge-utils は IPv6 側の作りが甘いっ!!
Docker ホストの br0 から出ていく TCP パケットには eth0 の MAC アドレスとか Link Local アドレスが記載されていて、受け取った PC のほうは戻りパケットを送信側 (Docker ホストのこと) の eth0 の MAC アドレスとか Link Local アドレスに投げようとするので、Dockerホスト側の br0 の情報がないために宛先がなくて、実質的に通信できない。と、いう非常にお粗末な状態です。
Docker 自体は IPv6 が使えるかもしれないけど、Linux でブリッジを利用する場合には IPv6 は利用しないほうが良いですな。パケットキャプチャしないと原因が特定できない。ハマる前に IPv6 の利用は諦めたほうが良いかと。

 
と、いうことで、今回、 Docker コンテナをブリッジインターフェースで、Docker ホストと同一のネットワークで動作させる。と、いうことにチャレンジしてました。 VMware ESXi 上で複数の仮想マシンを起動するがごとく、Docker ホスト上で Docker コンテナをドドドと立ち上げることが可能になったので、サーバ資源がますますギューギュー使える状態になったでしょうかねぇ;-)。

 
このあと、docker swarm がまだ待っております。が、オーバーレイネットワークがちゃんと動いてくれてないのよ。とほほ・・。orz