はじめに
本記事ではDockerのSwarm機能を使って2ノードクラスタ構成のコンテナ環境を構成します。デプロイするアプリはこれまでと変わらずTERASOLUNAツアー予約システムのサンプルWEBアプリとしますが、コンテナ側の構築をメインに扱うためApacheを除いたTomcat-Postgresの2層のコンテナ構成で実装することにしました。
想定環境
また、本記事は以下3つのサーバが登場人物となります。
ホスト名 |
IPアドレス |
備考 |
docker |
192.168.233.135 (停止) |
DockerイメージでコンテナWEBアプリを構築したコンテナ(VMクローン元として使用) |
master |
192.168.233.135 |
Docker Swarm環境のコントロールとコンテナ実行を行うマスターノード |
node01 |
192.168.233.145 |
Docker Swarm環境でコンテナ実行を行うスレーブノード |
留意事項
表記ルール
・コードブロックにおける"#"はrootユーザでの実行
・コードブロックにおける"$"は非rootユーザでの実行
・コンテナログイン時のプロンプトは"Container> #"で表示
お作法
・ファイルのバックアップなどは各自要否を判断してください。
・想定外動作の原因にもなるので、バージョンはできるだけ揃えることを推奨します。
コンテナWEBアプリを構築した仮想マシンdockerからmaster、node01という2台の仮想マシンを複製する。Windowsであればデフォルトで『C:\Users\\<ユーザ名>\Documents\Virtual Machines』配下に仮想マシンファイルが作成されているため、master、node01向けにフォルダごとコピーする。確実に停止断面を取るため、元の仮想マシン(docker)はパワーオフ状態にしてからコピーすること。コピー後にフォルダ名を仮想マシン名に合わせて修正する。

仮想マシンファイルコピー後、masterフォルダ、node01フォルダ配下のvmx形式ファイルをそれぞれの仮想マシン名に修正する。

master.vmx、node01.vmxファイルのdisplayNameパラメータをそれぞれの仮想マシン名に修正し、仮想マシン登録時の表示名をmaster、node01とする。

vmxファイルの修正が完了したら、VMware Workstation Playerの「仮想マシンを開く」からそれぞれの仮想マシンのvmxファイルを開き、仮想マシンを登録する。登録後、仮想マシンをパワーオンする。パワーオン時は仮想マシンファイルを移動したかコピーしたかを聞かれるため、「コピーしました」と回答する。

OS基本設定
ホスト名設定
masterのホスト名を設定する。hostnamectlコマンドで設定後、rootユーザで再ログインする。
# hostnamectl set-hostname master
# su -
node01も同様に設定する。
# hostnamectl set-hostname node01
# su -
hostsファイル追記
master、node01で名前解決の定義をhostsファイルに追記する。
# cat <<EOF >> /etc/hosts
192.168.233.135 master
192.168.233.145 node01
EOF
Docker環境確認
現在想定しているDocker環境を確認するため、コンテナの起動状況とDockerネットワーク、Dockerイメージを確認する。apache01、tomcat01、postgres01の3つのコンテナでTERASOLUNAツアー予約システムのコンテナWEBアプリを構築した状態です。
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fedd2bce5a6b postgres:12.6 "docker-entrypoint.s…" 7 days ago Exited (0) 6 days ago postgres01
e6ef2b15454e tomcat:8.5.43 "catalina.sh run" 7 days ago Exited (143) 6 days ago tomcat01
fcc15ce57a0b httpd:2.4.37 "httpd-foreground" 7 days ago Exited (0) 6 days ago apache01
# docker network ls
NETWORK ID NAME DRIVER SCOPE
b69861bfb59d bridge bridge local
5e77d95891d3 host host local
a976ce4ece30 none null local
e7a3f2249e33 terasoluna-tourreserve bridge local
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres 12.6 fd94a7538179 11 days ago 314MB
tomcat 8.5.43 6e30b06a90d3 19 months ago 506MB
httpd 2.4.37 ef1dc54703e2 2 years ago 132MB
環境依存ファイル収集
起動用テンプレートディレクトリ作成
Tomcatコンテナ、PostgreSQLコンテナから必要な設定ファイルを格納するためのディレクトリを作成する。masterで実施する。
# mkdir /root/teratour
# cd /root/teratour
# mkdir -p ap/init ap/src db_master/init
# find . -maxdepth 2 -type d
.
./ap
./ap/init
./ap/src
./db
./db_master/init
環境依存ファイルコピー
Tomcatコンテナ、PostgreSQLコンテナから必要な設定ファイルをコピーする。コピー元のコンテナは起動状態である必要があるため、docker startコマンドでコンテナを起動してから実施する。
# docker start tomcat01 postgres01
# docker cp tomcat01:usr/local/tomcat/terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-env/src/main/resources/META-INF/spring ap/src/
# mv ap/src/spring/* ap/src/
# rm -rf ap/src/spring
# ls -l ap/src/
# docker cp postgres01:terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-initdb_master/src/main/sqls/postgres/ db_master/init/
# mv db_master/init/postgres/* db_master/init/
# rm -rf db_master/init/postgres
# ls -l db_master/init/
Dockerイメージ作成
既存のコンテナを停止し、WEBアプリ用のDockerイメージを作成する。イメージの名前はそれぞれ以下の通りとする。
Dockerイメージ作成
masterで以下を実行する。イメージ作成後に一覧情報を出力し、イメージが作成されていることを確認し、Dockerイメージファイルをtarファイルとしてエクスポートする。
# docker stop tomcat01 postgres01
# docker commit tomcat01 teratour-ap:v1.0
# docker commit postgres01 teratour-db:v1.0
# docker images | grep teratour
teratour-db v1.0 bd3368e754cb 2 seconds ago 562MB
teratour-ap v1.0 742a1dffc267 8 seconds ago 1.03GB
# docker save teratour-ap > /root/teratour-ap.tar
# docker save teratour-db > /root/teratour-db.tar
# ls -1 /root/*tar
/root/teratour-ap.tar
/root/teratour-db.tar
Dockerイメージ転送
node01で以下を実行する。masterノードでエクスポートしたDockerイメージのtarファイルをscpでコピーし、インポートする。
# scp -p master:/root/teratour-*.tar .
# ls -1 /root/*tar
# docker load < /root/teratour-ap.tar
# docker load < /root/teratour-db.tar
# docker images | grep teratour
teratour-db v1.0 bd3368e754cb 6 minutes ago 562MB
teratour-ap v1.0 742a1dffc267 6 minutes ago 1.03GB
Dockerイメージtarファイル削除
Dockerイメージの取り込みが完了したら、それぞれのノードからエクスポートしたDockerイメージのtarファイルを削除しておく。
# rm -i /root/teratour-ap.tar /root/teratour-db.tar
# ls -1 /root/*tar
ls: cannot access '/root/*tar': No such file or directory
Docker Swarmクラスタ構築
Docker Swarm初期化
以下をmasterで実行し、Docker Swarmを初期化する。IPアドレスは各自環境のmasterのIPアドレスに置き換えること。Swarm初期化後、masterがSwarmのノードとして追加され、Dockerネットワークにdocker_gwbridgeとingressの2つのネットワークが自動的に追加される。
# docker swarm init --advertise-addr 192.168.233.135
Swarm initialized: current node (4xugu3lsaoousbtpawka3zj8f) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-31298zbvu1q3d0v05181lrc3m8yc4t1afij0vc1liybcw9t7q7-5aqavlnrq9vo3lvhk9ryrzboc 192.168.233.135:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
4xugu3lsaoousbtpawka3zj8f * master Ready Active Leader 20.10.5
# docker network ls
NETWORK ID NAME DRIVER SCOPE
b69861bfb59d bridge bridge local
cbe8768b0b99 docker_gwbridge bridge local
5e77d95891d3 host host local
m5hletl4zvca ingress overlay swarm
a976ce4ece30 none null local
e7a3f2249e33 terasoluna-tourreserve bridge local
Swarm初期化時に他ノードがSwarmに参加するために実行するコマンドが表示されているので、これをnode01で実行する。「This node joined a swarm as a worker.」と表示されていれば問題なくSwarmクラスタにノード追加されている。
# docker swarm join --token SWMTKN-1-31298zbvu1q3d0v05181lrc3m8yc4t1afij0vc1liybcw9t7q7-5aqavlnrq9vo3lvhk9ryrzboc 192.168.233.135:2377
This node joined a swarm as a worker.
Swarmノード追加確認
マスタ側で以下を実行し、Swarmクラスタにノードが追加されていることを確認する。
# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
4xugu3lsaoousbtpawka3zj8f * master Ready Active Leader 20.10.5
lz3sr4fr3k4p71762skhhixc8 node01 Ready Active 20.10.5
コンテナ定義セッティング
Docker-Composeファイル作成
master側で以下を実行し、docker-compose.ymlファイルを作成する。
# cd ~
# cat <<EOF > teratour/docker-compose.yml
version: "3"
services:
ap:
image: teratour-ap:v1.0
depends_on:
- db
deploy:
replicas: 2
restart_policy:
condition: on-failure
ports:
- '8800:8080'
volumes:
- ./ap/init:/tmp
- ./ap/src:/usr/local/tomcat/terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-env/src/main/resources/META-INF/spring
entrypoint: >
sh -c "sh /tmp/container-init.sh"
db_master:
image: teratour-db:v1.0
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "P0stgres"
POSTGRES_DB: "tourreserve"
volumes:
- ./db_master/init:/docker-entrypoint-initdb.d
EOF
ファイル記載内容のポイントは以下の通りである。
- サービス名をap、db_masterで定義する
- apサービスはreplicas: 2で2つのサービスが起動するように定義する
- WEBアプリ接続時に指定するポートは8800とする
- volumesオプションで各コンテナの環境依存ファイルを参照させる
- depends_onで依存関係を指定し、db_master、apの順番でコンテナが起動するように制御する
- ap/init配下のcontainer-init.shを実行し、APコンテナで初回のみビルド処理を行う(後述)
- DBコンテナにはPostgreSQL関連で必要な環境変数を定義する
Tomcatコンテナについてはソースファイルを修正しているので、アプリケーションを再度ビルドする必要がある。APコンテナ起動時に実行する処理を記載したスクリプトを作成し、自動的にアプリケーションのビルドを実行できるようにする。なお、コンテナの起動時に毎回実行されるため、初回のみ実行されるようにフラグファイルで制御するロジックを実装している。Tomcatサービスの起動状態を保つため、最後にcatalina.shをフォアグラウンドで実行している。
# cat <<EOF > teratour/ap/init/container-init.sh
#/bin/bash
if [ ! -e '/setup_flag' ]; then
touch /setup_flag
rm -f terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-web/target/terasoluna-tourreservation-web.war
rm -rf webapps/terasoluna-tourreservation-web*
mvn clean install -f terasoluna-tourreservation-5.6.1.RELEASE/pom.xml
cp -p terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-web/target/terasoluna-tourreservation-web.war /usr/local/tomcat/webapps/
./bin/catalina.sh run
else
./bin/catalina.sh run
fi
EOF
DB接続先定義ファイル修正
Tomcatコンテナで読み込むDB接続先定義ファイルのDB接続先ホスト名をdb-masterに変更する。
# cat teratour/ap/src/terasoluna-tourreservation-infra.properties | grep database.url
database.url=jdbc:postgresql://db_master/tourreserve
# sed -i -e 's/postgres01/db_master/g' teratour/ap/src/terasoluna-tourreservation-infra.properties
# cat teratour/ap/src/terasoluna-tourreservation-infra.properties | grep database.url
database.url=jdbc:postgresql://db_master/tourreserve
ファイルコピー
以下をnode01側で実行し、master側で構成したファイル群をnode01にもコピーする。
# cd ~
# scp -pr master:/root/teratour .
コンテナデプロイ
docker-composeファイルをインプットにコンテナをデプロイする。デプロイ後、起動しているサービスの一覧を表示すると、apサービスが2つ起動していることが確認できます。master、node01それぞれのノードでdockerプロセスを確認すると、それぞれのノードでのコンテナ起動状況が確認できます。
# docker stack deploy -c teratour/docker-compose.yml teratour
# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
fo0amtquiw8x teratour_ap replicated 2/2 teratour-ap:v1.0 *:8800->8080/tcp
2i0imxmpz2t9 teratour_db_master replicated 1/1 teratour-db:v1.0
# docker ps -a
コンテナWEBアプリ接続確認
WEBアプリのURLにアクセスし、テスト接続する。master、node01のどちらのIPアドレスからでもアクセスできることが確認できる思います。
http://192.168.233.135:8800/terasoluna-tourreservation-web/
http://192.168.233.145:8800/terasoluna-tourreservation-web/
レプリカDB追加
Docker-Composeファイル作成
PostgreSQLを使っているdbサービスについてもレプリケーションを試みる。レプリケーション方式はストリーミングレプリケーションを採用し、マスタDBからスレーブDBに物理レベルでデータを同期し、スレーブDBにリードレプリカとしての機能を持たせることができる。作成したdocker-composeファイルを削除し、以下の通りファイルを再作成する。
# rm -i teratour/docker-compose.yml
# cat <<EOF > teratour/docker-compose.yml
version: "3"
services:
ap:
image: teratour-ap:v1.0
depends_on:
- db_master
deploy:
replicas: 2
restart_policy:
condition: on-failure
ports:
- '8800:8080'
volumes:
- ./ap/init:/tmp
- ./ap/src:/usr/local/tomcat/terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-env/src/main/resources/META-INF/spring
entrypoint: >
sh -c "sh /tmp/container-init.sh"
db_master:
image: teratour-db:v1.0
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: P0stgres
POSTGRES_DB: tourreserve
volumes:
- ./db_master/init:/docker-entrypoint-initdb.d
db_replica:
image: teratour-db:v1.0
depends_on:
- db_master
deploy:
replicas: 2
restart_policy:
condition: on-failure
ports:
- 5433:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: P0stgres
volumes:
- ./db_replica/init:/docker-entrypoint-initdb.d
EOF
追加したポイントは以下の通りである。
- レプリカDBサービスdb_replicaを追加する
- db_masterサービス起動後にdb_replicaサービスを起動する
- volumeオプションで指定したディレクトリにdb_master、db_replicaそれぞれ用の起動スクリプトを配置し、実行させる(後述)
- マスタDBは1サービスのみ、レプリカDBは2サービス起動するように指定する
- PostgreSQLのポートはdb_masterとdb_replicaで重複しないようにする
PostgreSQLのレプリケーション向けの設定が必要なため、レプリケーションを行うために必要な設定を行う。なお、このスクリプトがコンテナ初回起動時に最後に実行されるように、既に格納しているSQLファイルよりも大きい数字をファイル名の先頭に付与している。
# cat <<EOF > teratour/db_master/init/00300_configure-replication.sh
#!/bin/bash
sed -i -e 's/#wal_level/wal_level/g' /var/lib/postgresql/data/postgresql.conf
sed -i -e 's/#max_wal_senders/max_wal_senders/g' /var/lib/postgresql/data/postgresql.conf
sed -i -e 's/#wal_keep_segments = 0/wal_keep_segments = 256/g' /var/lib/postgresql/data/postgresql.conf
echo "host replication replication 0.0.0.0/0 trust" >> "/var/lib/postgresql/data/pg_hba.conf"
psql -U postgres -c "CREATE ROLE replication WITH REPLICATION PASSWORD 'replication' LOGIN"
EOF
スレーブDB側で必要な設定を行うスクリプトを作成する。なお、起動時にマスタDBのデータをコピーするため、スレーブ側のinitディレクトリにはSQLファイルは配置する必要はない。
# cd ~
# mkdir -p teratour/db_replica/init
# cat <<EOF > teratour/db_replica/init/00300_configure-replication.sh
#!/bin/bash
/usr/lib/postgresql/12/bin/pg_ctl -D "/var/lib/postgresql/data" -m fast -w stop
sleep 10
rm -rf /var/lib/postgresql/data/*
pg_basebackup -R -h db_master -U replication -D /var/lib/postgresql/data -P
echo "hot_standby = on" >> "/var/lib/postgresql/data/postgresql.conf"
/usr/lib/postgresql/12/bin/pg_ctl -D "/var/lib/postgresql/data" -w start
EOF
ファイルコピー
以下をnode01で実行し、master側で準備したファイルをnode01にもコピーする。
# cd ~
# scp -pr master:/root/teratour .
コンテナデプロイ
デプロイ前に先ほど作成したコンテナを削除してから、master側で再度コンテナのデプロイをする。レプリカDBのサービスが2つ追加で起動していることが確認できます。本記事では割愛しますが、実際にWEBアプリからデータ更新操作などを行うと、スレーブ側のDBにもデータ更新が反映されていることが確認できます。例えば、コンテナWEBアプリから会員登録などをすると、db_master、db_replica双方のcustomerテーブルに登録した会員情報が反映されることが確認できます。
# docker stack rm teratour
# docker stack deploy -c teratour/docker-compose.yml teratour
# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
9du0q1lfo78y teratour_ap replicated 2/2 teratour-ap:v1.0 *:8800->8080/tcp
zr5iy0nna4b6 teratour_db_master replicated 1/1 teratour-db:v1.0 *:5432->5432/tcp
w0ux13ublow8 teratour_db_replica replicated 2/2 teratour-db:v1.0 *:5433->5432/tcp
本記事におけるDocker Swarmを使ったコンテナWEBアプリの構築は完了です。今回の完成図は以下のようになります。

おわりに
本記事ではDocker Swarmを複数のサーバ上でコンテナをより高可用性、スケーラブルな構成で実装することに挑戦してみました。Docker Swarmを使うことで、Swarmクラスタにノードを追加すれば簡単にスケーリングができる構成となりました。PostgreSQLのストリーミング構成は特有の設定が必要なため、作りこみが必要となり少し苦労したものの、とても勉強になりました。本記事の構成だと、WEBアプリの受け口となるIPがバインドされていないなど、まだ改善余地はあるため、そのあたりをトピックにどこかで取り組んでみたいと思います。