有機インフラの庭

有機インフラの庭

某SI企業エンジニアのIT備忘録

【Tips】Docker SwarmでPostgreSQLストリーミングレプリケーションを実装する

はじめに

Docker Swarmを使ったPostgreSQLストリーミングレプリケーション実装の備忘録です。Docker Composeファイルとレプリケーション設定用のスクリプトを組み合わせて実装しています。レプリケーションの動作確認はPostgreSQLのサンプルデータベースとして公開されているDVDレンタル予約データベースで実施しています。


前提条件

  • Docker、Docker Composeがインストール済みであること。
  • Swarmクラスタが構成済みであること。シングルノード構成でもマルチノード構成でも実装は可能です。

参考記事:【Tips】Docker/Compose/Swarm


ソフトウェア構成

ソフトウェア バージョン
Cent OS 8.1
Docker 20.10.5
Docker Compose 1.28.5
PostgreSQL 12.6

ディレクトリ作成

任意の作業ディレクトリ配下でmaster、replicaディレクトリを作成する。

# mkdir -p dvdrental/master dvdrental/replica
# cd dvdrental
# find .
.
./master
./replica

構成ファイル作成

# vi ./docker-compose.yml

PostgreSQLストリーミングレプリケーション用のComposeファイルを作成する。

version: "3"
services:
  master:
    image: postgres:12.6
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: "postgres" # Specify the database user name
      POSTGRES_PASSWORD: "P0stgres" # Specify the user password
      POSTGRES_DB: "dvdrental" # Specify the database name
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C" # Specify encoding and locale
    volumes:
      - ./master:/docker-entrypoint-initdb.d
  replica:
    image: postgres:12.6
    depends_on:
      - master # Specify the order of service startup
    deploy:
      replicas: 3 # Specify the number of replicas
      restart_policy:
        condition: on-failure
    ports:
      - 5433:5432
    environment:
      POSTGRES_USER: postgres # Specify the database user name
      POSTGRES_PASSWORD: P0stgres # Specify the user password
    volumes:
      - ./replica:/docker-entrypoint-initdb.d

# vi ./master/0000_configure_replication_master.sh

マスタ側で必要なレプリケーション関連設定を行うスクリプトを作成する。WAL関連の設定値とレプリケーション用ユーザの名前とパスワードは必要に応じて変更する。

#!/bin/bash

# Replication settings
sed -i -e 's/#wal_level = replica/wal_level = replica/g' /var/lib/postgresql/data/postgresql.conf
sed -i -e 's/#max_wal_senders = 10/max_wal_senders = 10/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"

# Create users for replication
psql -U postgres -c "CREATE ROLE replication WITH REPLICATION PASSWORD 'replication' LOGIN"

# vi ./replica/0000_configure_replication_replica.sh

レプリカ側で必要なレプリケーション関連設定を行うスクリプトを作成する。

#!/bin/bash

# Database shutdwon
/usr/lib/postgresql/12/bin/pg_ctl -D "/var/lib/postgresql/data" -m fast -w stop
sleep 20

# Delete database directories
rm -rf /var/lib/postgresql/data/*

# Master database backup 
pg_basebackup -R -h master -U replication -D /var/lib/postgresql/data -P

# Replication settings
sed -i -e 's/#hot_standby = on/hot_standby = on/g' /var/lib/postgresql/data/postgresql.conf

# Database startup
/usr/lib/postgresql/12/bin/pg_ctl -D "/var/lib/postgresql/data" -w start

# vi ./master/0100_load_sample_data.sh

PostgreSQLのサンプルデータベースを作成するためのスクリプトを作成する。

#!/bin/bash

# Loading sample data
pg_restore -U postgres -d dvdrental /docker-entrypoint-initdb.d/dvdrental.tar

サンプル資材格納

デプロイ後にレプリケーション動作を確認するため、サンプル資材をダウンロードし、コンテナから参照可能なディレクトリに配置する。

PostgreSQL Sample Database:https://www.postgresqltutorial.com/postgresql-sample-database/

# wget https://sp.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip
# unzip dvdrental.zip -d ./master/
# rm -f dvdrental.zip

構成ファイルコピー

マルチノードでSwarmクラスタを構成している場合、すべてのノードにファイルを配置する必要がある。SCPで構成ファイルをノードにコピーする。

# scp -pr `pwd` <node-name>:`pwd`

デプロイ実行

Swarmクラスタにコンテナサービスをデプロイする。

# docker stack deploy -c ./docker-compose.yml dvdrental
# docker service ls
ID             NAME                MODE         REPLICAS   IMAGE           PORTS
suosdfmse9sf   dvdrental_master    replicated   1/1        postgres:12.6   *:5432->5432/tcp
xowrzwwtq7sm   dvdrental_replica   replicated   3/3        postgres:12.6   *:5433->5432/tcp

レプリケーション動作確認

サンプルデータベース更新

マスタ側でサンプルデータベースにログインし、staffテーブルにレコードを追加する。

# docker ps -a
# docker exec -it <master-container-name> /bin/bash

Container> # su - postgres
Container> $ psql -d dvdrental
Container> dvdrental=# \dp
Container> dvdrental=# select * from staff;
 staff_id | first_name | last_name | address_id |            email             | store_id | active | username |                 password                 |        last_update        |      picture
----------+------------+-----------+------------+------------------------------+----------+--------+----------+------------------------------------------+---------------------------+--------------------
        1 | Mike       | Hillyer   |          3 | Mike.Hillyer@sakilastaff.com |        1 | t      | Mike     | 8cb2237d0679ca88db6464eac60da96345513964 | 2006-05-16 16:13:11.79328 | \x89504e470d0a5a0a
        2 | Jon        | Stephens  |          4 | Jon.Stephens@sakilastaff.com |        2 | t      | Jon      | 8cb2237d0679ca88db6464eac60da96345513964 | 2006-05-16 16:13:11.79328 |
(2 rows)

Container> dvdrental=# insert into staff(staff_id,first_name,last_name,address_id,store_id,username) values(3,'Bob','Ludwig','2','2','Bob');
INSERT 0 1
Container> dvdrental=# commit;
Container> dvdrental=# select * from staff;
 staff_id | first_name | last_name | address_id |            email             | store_id | active | username |                 password                 |        last_update         |      picture
----------+------------+-----------+------------+------------------------------+----------+--------+----------+------------------------------------------+----------------------------+--------------------
        1 | Mike       | Hillyer   |          3 | Mike.Hillyer@sakilastaff.com |        1 | t      | Mike     | 8cb2237d0679ca88db6464eac60da96345513964 | 2006-05-16 16:13:11.79328  | \x89504e470d0a5a0a
        2 | Jon        | Stephens  |          4 | Jon.Stephens@sakilastaff.com |        2 | t      | Jon      | 8cb2237d0679ca88db6464eac60da96345513964 | 2006-05-16 16:13:11.79328  |
        3 | Bob        | Ludwig    |          2 |                              |        2 | t      | Bob      |                                          | 2021-03-24 09:48:55.856577 |
(3 rows)
Container> dvdrental=# \q
Container> $ exit
Container> # exit

レプリケーション確認

レプリカ側でサンプルデータベースにログインし、マスタ側のテーブル更新が反映されていることを確認する。また、レプリカ側で更新操作ができないことを確認する。

# docker ps -a
# docker exec -it <replica-container-name> /bin/bash
Container> # su - postgres
Container> $ psql -d dvdrental
Container> dvdrental=# \dp
Container> dvdrental=# select * from staff;
Container> dvdrental=# delete from staff where staff_id='3';
ERROR:  cannot execute DELETE in a read-only transaction
Container> dvdrental=# \q
Container> $ exit
Container> # exit

【Tips】Docker/Compose/Swarm

はじめに

Dockerで覚えたことの備忘録です。随時更新します。

本記事で想定している環境は以下です。

  • Docker 20.10.5

  • Docker Compose 1.28.5

  • Cent OS 8.1


Docker

インストール

  • レポジトリ最新化
# dnf -y update
  • Dockerレポジトリを追加
# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
  • container.ioダウンロード
# wget https://download.docker.com/linux/centos/8/x86_64/stable/Packages/containerd.io-1.4.3-3.1.el8.x86_64.rpm
  • containerd.ioインストール
# dnf install -y containerd.io-1.4.3-3.1.el8.x86_64.rpm
  • Dockerインストール
# dnf install -y docker-ce docker-ce-cli

サービス起動・停止

  • 起動
# systemctl start docker
# systemctl enable docker
  • ステータス確認
# systemctl status docker
# systemctl disable docker
  • 停止
# systemctl stop docker

バージョン確認

# docker --version

ネットワーク関連

  • ネットワーク作成
# docker network create -d [bridge|overlay] <network-name>
  • ネットワーク一覧表示
# docker network ls
  • ネットワーク定義表示
# docker network inspect <network-name>

イメージ操作

  • イメージダウンロード
# docker pull <image-name>:<tag>
  • イメージ一覧表示
# docker images
  • イメージ作成
# docker commit <container-name> <image-name>:<tag>
  • イメージエクスポート
# docker save <image-name> > <output-file-name>
  • イメージインポート
# docker load < <input-file-name>

コンテナ操作

  • コンテナ起動
# docker run -it -d --network <network-name> -p <host-port>:<container-port> --name <container-name> <image-name>:<tag>
  • コンテナプロセス確認
# docker ps -a --no-trunc
  • コンテナ状態確認
# docker inspect --format='{{.State.Status}}' <container-name>
  • コンテナログイン
# docker exec -it <container-name> /bin/bash
# docker cp <container-name>:<container-path> <host-path>

Docker Compose

インストール

  • モジュールダウンロード

Composeのバージョン1.28.5の場合のURLです。

# curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose
  • 実行権限追加
chmod +x /usr/local/bin/docker-compose

バージョン確認

# docker-compose --version

コンテナ起動

実行ディレクトリにdocker-compose.ymlファイルが存在することが前提です。

# docker-compose up -d

Docker Swarm

クラスタ構成

クラスタ構成については、サーバ間で名前解決ができる状態で実施すること。

マスタノード上で実行する。

# docker swarm init --advertise-addr <master-node-ip-address>

マスタノード上で実行する。

# docker node ls

マスターノードで実行。標準出力されたコマンドをクラスタに参加させたいノード上で実行すればSwarmクラスタに参加できる。

# docker swarm join-token worker

デプロイ

# docker stack deploy -c <compose-file-path> <stack-name>
# docker stack rm <stack-name>

Docker SwarmでスケーラブルなコンテナWEBアプリを構築する

はじめに

本記事では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)はパワーオフ状態にしてからコピーすること。コピー後にフォルダ名を仮想マシン名に合わせて修正する。

f:id:chakkyNote:20210324104028p:plain

仮想マシン構成ファイル修正

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

f:id:chakkyNote:20210324104040p:plain

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

f:id:chakkyNote:20210324104053p:plain

仮想マシン登録

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

f:id:chakkyNote:20210324104104p:plain

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イメージを作成する。イメージの名前はそれぞれ以下の通りとする。

コンテナ 作成イメージ名
Tomcatコンテナ teratour-ap
PostgreSQLコンテナ teratour-db

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初期化時に他ノードが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コンテナ起動スクリプト作成

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で重複しないようにする

マスタDB用レプリケーション設定スクリプト作成

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側で必要な設定を行うスクリプトを作成する。なお、起動時にマスタ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アプリの構築は完了です。今回の完成図は以下のようになります。

f:id:chakkyNote:20210324104117p:plain

おわりに

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


コンテナWEBアプリをDocker Composeでクローンする

はじめに

本記事では、『3つの公式Dockerイメージを使用してコンテナWEBアプリを構築する』で構築したコンテナWEBアプリをDockerイメージに変換し、Docker Composeを使って3つのコンテナをひとつのWEBアプリサービス群としてデプロイする。後半セクションでは複数の環境面が必要な場合を想定し、環境依存情報をパラメータとして渡すだけでコンテナWEBアプリをデプロイできるような実装を行う。


想定環境

3つの公式Dockerイメージを使用してコンテナWEBアプリを構築する』のシナリオに沿ってコンテナWEBアプリを構築した状態をスタートとして想定しています。本記事ではコンテナ管理ツールであるDocker Composeのみ新規インストールします。

カテゴリ 製品・バージョン
仮想化製品 VMware Workstation 15 Player
ゲストOS Cent OS 8.1
(CPU : 2 / Memory : 4GB / Disk : 20GB / NW : NAT)
コンテナ製品 Docker 20.10.5
コンテナ管理ツール Docker Compose 1.28.5
WEBサーバ Apache 2.4.37
APサーバ Apache Tomcat 8.5.43
https://tomcat.apache.org/download-80.cgi
データベース psql (PostgreSQL) 12.6-1
https://yum.postgresql.org/repopackages
アプリケーション terasoluna-tourreservation 5.6.1
https://github.com/terasolunaorg/terasoluna-tourreservation

本記事であらかじめ想定しているコンテナ構成を以下に掲載しておきます。

f:id:chakkyNote:20210318130352p:plain


留意事項

表記ルール

・コードブロックにおける"#"はrootユーザでの実行

・コードブロックにおける"$"は非rootユーザでの実行

・コンテナログイン時のプロンプトは"Container> #"で表示

お作法

・ファイルのバックアップなどは各自要否を判断してください。

・想定外動作の原因にもなるので、バージョンはできるだけ揃えることを推奨します。


Docker Composeインストール

コンテナ管理ツールであるDocker Composeをダウンロードし、モジュールに実行権限を追加する。

# curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
# docker-compose --version
docker-compose version 1.28.5, build c4eb3a1f

環境依存ファイル収集

起動用テンプレートディレクトリ作成

各コンテナから環境依存のファイルを格納するためのディレクトリを作成する。このディレクトリをコンテナ起動用のテンプレートファイル群として使用し、このディレクトリをコピー&修正してコンテナのクローンを行う。

# mkdir /root/teratour_template
# cd /root/teratour_template
# mkdir -p web/conf ap/init ap/src db/sql
# find . -maxdepth 2 -type d
.
./web
./web/conf
./ap
./ap/init
./ap/src
./db
./db/sql

環境依存ファイルコピー

各コンテナから環境依存のファイルをコピーする。『3つの公式Dockerイメージを使用してコンテナWEBアプリを構築する』で作成したコンテナが稼働している必要があるため、停止状態の場合はdocker startコマンドでコンテナを起動してから実施する。環境依存ファイルは以下の3つである。

  • Apache設定ファイル:httpd.conf
  • WEBアプリの環境定義ファイル:terasoluna-tourreservation-infra.properties
  • データベースオブジェクト、およびデータを作成するSQLスクリプト
# docker cp apache01:/usr/local/apache2/conf/ web/conf
# mv web/conf/conf/* web/conf/
# rm -rf web/conf/conf
# ls -l web/conf/

# 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/src/main/sqls/postgres/ db/sql/
# mv db/sql/postgres/* db/sql/
# rm -rf db/sql/postgres
# ls -l db/sql/

Dockerイメージ作成

既存のコンテナを停止し、WEBアプリ用のDockerイメージを作成する。イメージの名前はそれぞれ以下の通りとする。

コンテナ 作成イメージ名
Apacheコンテナ teratour-web
Tomcatコンテナ teratour-ap
PostgreSQLコンテナ teratour-db

イメージ作成後に一覧情報を出力し、イメージが作成されていることを確認する。なお、作成したイメージはdocker saveコマンドでファイルとしてエクスポートすることができ、他のサーバ上でインポートすることができる。オフラインの環境などにコンテナイメージを持ち込みたい場合や保存目的でも有用なため、ここでもエクスポートをしておく。圧縮していないため、イメージサイズと同等のサイズとなるため、容量には注意する。

# docker ps -a
CONTAINER ID   IMAGE           COMMAND                  CREATED        STATUS              PORTS                NAMES
fedd2bce5a6b   postgres:12.6   "docker-entrypoint.s…"   12 hours ago   Up About a minute   5432/tcp             postgres01
e6ef2b15454e   tomcat:8.5.43   "catalina.sh run"        12 hours ago   Up About a minute   8080/tcp             tomcat01
fcc15ce57a0b   httpd:2.4.37    "httpd-foreground"       12 hours ago   Up About a minute   0.0.0.0:80->80/tcp   apache01
# docker stop apache01 tomcat01 postgres01
# docker commit apache01 teratour-web:v1.0
# docker commit tomcat01 teratour-ap:v1.0
# docker commit postgres01 teratour-db:v1.0

# docker images | grep teratour
teratour-db    v1.0      f181410d256c   13 seconds ago   562MB
teratour-ap    v1.0      243a17b935e3   15 seconds ago   1.03GB
teratour-web   v1.0      c7f74cb13aed   22 seconds ago   150MB

# docker save teratour-web > /root/teratour-web.tar
# docker save teratour-ap > /root/teratour-ap.tar
# docker save teratour-db > /root/teratour-db.tar
# ls -1tr /root/*tar
/root/teratour-web.tar
/root/teratour-ap.tar
/root/teratour-db.tar

コンテナ起動定義作成

docker-composeファイル作成

コンテナの起動方法を定義するdocker-compose.ymlファイルを作成する。Docker Composeは複数のコンテナの起動方法を定義することができるため、ファイルはひとつ準備すれば問題ない。

# cd /root/teratour_template
# cat <<EOF > docker-compose.yml
version: "3"
services:
  web:
    image: teratour-web:v1.0
    depends_on:
      - ap
    ports:
      - '8800:80'
    volumes:
      - ./web/conf:/usr/local/apache2/conf
  ap:
    image: teratour-ap:v1.0
    depends_on:
      - db
    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:
    image: teratour-db:v1.0
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "P0stgres"
      POSTGRES_DB: "tourreserve"
    volumes:
      - ./db/sql:/docker-entrypoint-initdb.d
EOF
# cat docker-compose.yml

ファイル記載内容のポイントは以下の通りである。

  • サービス名をweb、ap、dbで定義する
  • WEBアプリ接続時に指定するポートは8800とする
  • volumesオプションで各コンテナの環境依存ファイルを参照させる
  • depends_onで依存関係を指定し、db、ap、webの順番でコンテナが起動するように制御する
  • ap/init配下のcontainer-init.shを実行し、APコンテナで初回のみビルド処理を行う(後述)
  • DBコンテナにはPostgreSQL関連で必要な環境変数を定義する

Tomcatコンテナ起動スクリプト作成

Tomcatコンテナについてはソースファイルを修正しているので、アプリケーションを再度ビルドする必要がある。APコンテナ起動時に実行する処理を記載したスクリプトを作成し、自動的にアプリケーションのビルドを実行できるようにする。なお、コンテナの起動時に毎回実行されるため、初回のみ実行されるようにフラグファイルで制御するロジックを実装している。なお、Tomcatサービスの起動状態を保つため、catalina.shをフォアグラウンドで実行している。

# cat <<EOF > 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
# cat ap/init/container-init.sh

環境依存ファイル修正

環境依存ファイルに記載されたホスト名を修正する。Dockerで作成されたコンテナはサービス名を指定して名前解決が可能であるため、ホスト名がymlファイルで指定したサービス名となるように修正する。

# sed -i -e 's/tomcat01/ap/' web/conf/httpd.conf
# tail -1 web/conf/httpd.conf
ProxyPass / ajp://ap:8009/

# sed -i -e 's/db/' ap/src/terasoluna-tourreservation-infra.properties
# cat ap/src/terasoluna-tourreservation-infra.properties | grep database.url
database.url=jdbc:postgresql://db/tourreserve

コンテナWEBアプリ起動

コンテナ起動コマンド実行

テンプレートファイル群を使用してコンテナサービスを起動する。docker-compose upコマンドを実行すると自動的にカレントディレクトリのdocker-compose.ymlファイルを読み込み、ファイルに記載されているサービスを起動する。バックグラウンドで実行する場合は-dオプションを指定する。なお、APコンテナはアプリケーションのビルドが実行されるため、セットアップ完了まで30秒程度かかります。docker logsコマンドでコンテナのログを表示できるため、APコンテナのアプリケーションのビルド状況を確認する。

# docker-compose up -d
# docker network ls | grep teratour
6abc56932f7b   teratour_template_default   bridge    local
# docker ps -a | grep teratour
777226a047ef   teratour-web:v1.0   "httpd-foreground"       45 seconds ago   Up 43 seconds                0.0.0.0:8800->80/tcp   teratour_template_web_1
b836cc898a33   teratour-ap:v1.0    "sh -c 'sh /tmp/cont…"   45 seconds ago   Up 44 seconds                8080/tcp               teratour_template_ap_1
e4e12c0b1d24   teratour-db:v1.0    "docker-entrypoint.s…"   46 seconds ago   Up 45 seconds                5432/tcp               teratour_template_db_1
# docker logs -f teratour_template_ap_1

WEBアプリ接続確認

ブラウザを起動し、http://<SERVER_IP_ADDRESS>:8800/terasoluna-tourreservation-web にアクセスする。WEBアプリのトップ画面が表示されていれば問題なくデプロイが完了している。

コンテナ環境クローン

テンプレート用のファイル群でコンテナWEBサービスをデプロイできることが確認できたので、テンプレート用のファイル群を別の環境用にコピーして、ポート番号が重複しないように修正し、コンテナ環境をクローンする。以下の例ではクローン先の環境名を"teratour_dev01"、WEBアプリ接続用ポート番号を"8801"としている。

# cd /root
# cp -pr teratour_template teratour_dev01
# sed -i -e "s/8800:80/8001:80/" teratour_dev01/docker-compose.yml
# docker-compose -f teratour_dev01/docker-compose.yml up -d

# docker network ls | grep teratour
1a619471a326   teratour_dev01_default      bridge    local
6abc56932f7b   teratour_template_default   bridge    local

# docker ps -a | grep teratour_dev
bac45bafde4d   teratour-web:v1.0   "httpd-foreground"       45 seconds ago   Up 43 seconds                0.0.0.0:8801->80/tcp   teratour_dev01_web_1
1066bbbbd091   teratour-ap:v1.0    "sh -c 'sh /tmp/cont…"   45 seconds ago   Up 44 seconds                8080/tcp               teratour_dev01_ap_1
ed30ff6326fa   teratour-db:v1.0    "docker-entrypoint.s…"   46 seconds ago   Up 45 seconds                5432/tcp               teratour_dev01_db_1

WEBアプリ接続確認

ブラウザを起動し、http://<SERVER_IP_ADDRESS>:8801/terasoluna-tourreservation-web にアクセスする。先ほどと同様にWEBアプリのトップページにアクセスできていれば問題なくデプロイが完了している。Docker Composeは環境ごとに専用のプライベートネットワークを自動作成するため、AJPJDBCなどDockerネットワーク内で閉じている通信ポートについては競合しないためそのまま使用が可能である。ApacheコンテナのHTTPポートにリダイレクトするポート番号を変更するだけで複数環境を共存させられることができる。本記事で完成した構成は以下のようになっています。

f:id:chakkyNote:20210318130408p:plain


おわりに

本記事では既存のコンテナ環境を流用し、より効果的なコンテナ管理を実現する手法としてDocker Composeを採用してみました。複数のコンテナ起動定義や依存関係を制御でき、クローンも容易なため、開発環境など多くの環境面が必要なケースで非常に有用だと感じました。次回はコンテナオーケストレーションツールを活用して、より高可用性なコンテナWEBアプリを構成してみたいと思います。


3つの公式Dockerイメージを使用してコンテナWEBアプリを構築する

はじめに

本記事では、ApacheTomcatPostgreSQLが組み込まれた公式のDockerイメージを使ってWEBアプリを実装する。デプロイするWEBアプリは『CentOS VM上に標準OSSでサンプルWEBアプリを実装する』で取り扱った『TERASOLUNAツアー予約システム』を採用します。CentOS上に作成したDockerネットワークに3つのコンテナを作成し、それぞれのコンテナを連携させてWEBアプリを構築します。完成イメージは以下になります。

f:id:chakkyNote:20210317014509p:plain


想定環境

本記事で想定している環境構成を記載します。

カテゴリ 製品・バージョン
仮想化製品 VMware Workstation 15 Player
ゲストOS Cent OS 8.1
(CPU : 2 / Memory : 4GB / Disk : 20GB / NW : NAT)
コンテナ製品 Docker 20.10.5
WEBサーバ Apache 2.4.37
APサーバ Apache Tomcat 8.5.43
https://tomcat.apache.org/download-80.cgi
データベース psql (PostgreSQL) 12.6-1
https://yum.postgresql.org/repopackages
アプリケーション terasoluna-tourreservation 5.6.1
https://github.com/terasolunaorg/terasoluna-tourreservation

留意事項

表記ルール

・コードブロックにおける"#"はrootユーザでの実行です。

・コードブロックにおける"$"は非rootユーザでの実行です。

・コンテナログイン時のプロンプトは"Container>"で表示します。

お作法

・ファイルのバックアップなどは各自要否を判断してください。

・想定外動作の原因にもなるので、バージョンはできるだけ揃えることを推奨します。


OS基本設定

CentOS 8.1インストール初期状態から基本的なOS設定を行います。インストール後にifconfigコマンドでIPアドレスを確認し、ホストOSからTeratermなどで接続して下さい。

ホスト名の設定

任意のホスト名を設定する。ここでは"docker"に設定します。

$ su -
# echo "docker" > /etc/hostname
# cat /etc/hostname
docker

タイムゾーンの設定

タイムゾーンを"Asia/Tokyo"に設定します。

# timedatectl set-timezone Asia/Tokyo
# timedatectl status | grep "Time zone"
                Time zone: Asia/Tokyo (JST, +0900)

ファイアウォールの無効化

検証用の環境のため、ファイアウォールは無効化しておく。

# systemctl stop firewalld
# systemctl disable firewalld
# systemctl status firewalld

SELinuxの無効化

同様の理由で、SELinuxも無効化しておく。

# sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
# cat /etc/selinux/config | grep -v ^# | grep "SELINUX="
SELINUX=disabled

OS再起動

設定反映を確認するため、OS再起動する。

# shutdown -r now

Dockerインストール

コンテナ環境を構築するため、Dockerをインストールする。

レポジトリ最新化

まずパッケージレポジトリ情報を最新化する。

$ su -
# dnf -y update

Dockerレポジトリ追加

Docker関連レポジトリを追加する。

# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

Dockerインストール

Dockerを動作させる必須コンポーネントであるcontainerd.ioをインストールし、Dockerをインストールする。インストールしたDockerのバージョンを確認した上でサービスを起動し、自動起動を有効化する。

# wget https://download.docker.com/linux/centos/8/x86_64/stable/Packages/containerd.io-1.4.3-3.1.el8.x86_64.rpm
# dnf install -y containerd.io-1.4.3-3.1.el8.x86_64.rpm
# dnf install -y docker-ce docker-ce-cli
# docker --version
Docker version 20.10.5, build d3cb89e

# systemctl start docker
# systemctl enable docker
# systemctl status docker

Dockerコンテナ作成

インストールしたDockerを使ってDockerネットワークを作成し、公式Dockerイメージからコンテナを作成、起動する。

Dockerネットワーク作成

Dockerは異なるコンテナ間での通信を可能にするため、仮想プライベートネットワークをホスト上に作成することができる。ここではWEBアプリ用のネットワークとして"terasoluna-tourreserve"という名前のネットワークを新規で作成する。サーバと独立したネットワークとして構築するため、ネットワーク種別はbridgeを指定する。

※Dockerのネットワークにはいくつかの種類があるが、ここでの説明は割愛する。

# docker network create -d bridge terasoluna-tourreserve
# docker network ls

Dockerイメージダウンロード

ApacheTomcatPostgreSQLそれぞれの用途に合わせたDockerイメージをダウンロードする。ダウンロード後にDockerイメージの一覧を表示する。

# docker pull httpd:2.4.37
# docker pull tomcat:8.5.43
# docker pull postgres:12.6
# docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
postgres     12.6      fd94a7538179   4 days ago      314MB
tomcat       8.5.43    6e30b06a90d3   19 months ago   506MB
httpd        2.4.37    ef1dc54703e2   2 years ago     132MB

Dockerコンテナ作成

ダウンロードしたDockerイメージから3つのDockerコンテナを作成する。それぞれのコンテナ名はapache01、tomcat01、postgres01とする。コンテナを作成後にコンテナの一覧とコンテナの起動状態を確認する。docker runコマンドで指定するオプションについて、ポイントを以下に記載しておきます。

  • コンテナ内での操作を可能とするため、-itオプションを指定

  • コンテナがバックグラウンドで動かすため、-dオプションを指定

  • HTTPアクセスをApacheコンテナに転送するため、HTTPポートに対してポートバインディングを設定(apache01のみ)

  • パスワードを設定しないとコンテナが起動しないため、PostgreSQLの特権ユーザとパスワードを設定(postgres01のみ)

  • WEBアプリのデータベース要件を満たすため、エンコーディングロケールを設定(postgres01のみ)

# docker run -it -d --network terasoluna-tourreserve -p 80:80 --name apache01 httpd:2.4.37
# docker run -it -d --network terasoluna-tourreserve --name tomcat01 tomcat:8.5.43
# docker run -it -d --network terasoluna-tourreserve -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=P0stgres -e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --locale=C" --name postgres01 postgres:12.6
# docker ps -a --no-trunc
# docker inspect --format='{{.State.Status}}' apache01 tomcat01 postgres01
running
running
running

コンテナIPアドレス確認

作成したコンテナに割り当てられているIPアドレスを確認するため、docker network inspectコマンドを実行する。なお、同じDockerネットワークに属しているDockerコンテナはコンテナ名で名前解決ができるため、通常IPアドレスを意識する必要はない。

# docker network inspect terasoluna-tourreserve

ポート疎通確認

製品のインストール状況、およびコンテナ間連携に必要なポートが開放されていることを確認する。コンテナにdocker execコマンドでログインし、本記事で必要となるパッケージをインストールした上で疎通確認を行う。なお、今回ダウンロードしたDockerイメージはDebian系のためyumやdnfなどRed Hat系のコマンドが使えないので注意して下さい。疎通確認における確認対象ポートは以下の通りです。

# docker exec -it apache01 /bin/bash
Container> # httpd -version | grep version
Server version: Apache/2.4.37 (Unix)
Container> # apt-get update
Container> # apt-get install -y iputils-ping netcat
Container> # ping tomcat01
Container> # nc -zv tomcat01 8009
tomcat01 [172.19.0.3] 8009 (?) open
Container> # exit

# docker exec -it tomcat01 /bin/bash
Container> # version.sh | grep version
Server version: Apache Tomcat/8.5.43
Container> # apt-get update
Container> # apt-get install -y netcat wget maven
Container> # ping postgres01
Container> # nc -zv postgres01 5432
postgres01 [172.19.0.4] 5432 (postgresql) open
Container> # exit

WEBアプリ構築

PostgreSQLコンテナ、Tomcatコンテナ、Apacheコンテナの順にWEBアプリ環境をセッティングし、WEBアプリが動作することを確認する。

パッケージインストール(postgres01)

PostgreSQLコンテナにログインし、本記事で必要なパッケージをインストールする。

# docker exec -it postgres01 /bin/bash
Container> # apt-get update
Container> # apt-get install -y wget maven
Container> # exit

データベース作成(postgres01)

PostgreSQLのバージョンを確認し、WEBアプリ用のデータベースを作成する。

# docker exec -it postgres01 /bin/bash
Container> # su - postgres
Container> $ psql --version
psql (PostgreSQL) 12.6 (Debian 12.6-1.pgdg100+1)
Container> $ psql -U postgres
Container> postgres=# CREATE DATABASE tourreserve;
Container> postgres=# \l
Container> postgres=# \q
Container> $ exit
Container> # exit

サンプルデータのセッティング(postgres01)

WEBアプリ資材をURLを指定してダウンロード&解凍し、Mavenユーティリティを使用してSQLを実行し、サンプルデータをセッティングする。mvnコマンド実行前に実行されるSQLのファイルセットを確認しておく。SQL実行後はデータベースに接続し、アプリケーション用のテーブルが作成されていることを確認する。

# docker exec -it postgres01 /bin/bash
Container> # wget https://github.com/terasolunaorg/terasoluna-tourreservation/archive/5.6.1.RELEASE.tar.gz
Container> # tar zxvf 5.6.1.RELEASE.tar.gz
Container> # ls -ld terasoluna-tourreservation-5.6.1.RELEASE/

Container> # cd terasoluna-tourreservation-5.6.1.RELEASE
Container> # ls -l terasoluna-tourreservation-initdb/src/main/sqls/postgres
Container> # mvn -f terasoluna-tourreservation-initdb/pom.xml sql:execute

Container> # su - postgres
Container> $ psql -d tourreserve -U postgres
Container> tourreserve=# \dp
Container> tourreserve=# \q
Container> $ exit
Container> # exit

DB接続先の設定(tomcat01)

WEBアプリ資材をURLからダウンロード&解凍し、データベース接続先を"localhost"から"postgres01"に修正する。

# docker exec -it tomcat01 /bin/bash
Container> # wget https://github.com/terasolunaorg/terasoluna-tourreservation/archive/5.6.1.RELEASE.tar.gz
Container> # tar zxvf 5.6.1.RELEASE.tar.gz
Container> # ls -ld terasoluna-tourreservation-5.6.1.RELEASE/

Container> # cd terasoluna-tourreservation-5.6.1.RELEASE/terasoluna-tourreservation-env/src/main/resources/META-INF/spring/
Container> # sed -i -e 's/localhost/postgres01/' terasoluna-tourreservation-infra.properties
Container> # cat terasoluna-tourreservation-infra.properties | grep database.url
database.url=jdbc:postgresql://postgres01/tourreserve
Container> # exit

アプリケーションのデプロイ(tomcat01)

Mavenユーティリティを使用してアプリケーションのビルドを行う。ビルド処理で生成されたwarファイルをコンテキストルートにコピーし、アプリケーションをデプロイする。

# docker exec -it tomcat01 /bin/bash
Container> # cd terasoluna-tourreservation-5.6.1.RELEASE/
Container> # mvn clean install

Container> # ls -l terasoluna-tourreservation-web/target/terasoluna-tourreservation-web.war
Container> # cp -p terasoluna-tourreservation-web/target/terasoluna-tourreservation-web.war /usr/local/tomcat/webapps/
Container> # ls -l /usr/local/tomcat/webapps/
Container> # exit

リバースプロキシ設定(apache01)

WEBアプリへのアクセスをApache経由で行うため、AJP通信の設定を行う。ApacheTomcatの双方で適切な設定が必要だが、Tomcatの公式イメージはデフォルトでAJP通信を許可しているため、今回はApache側のみ設定を修正する。修正後に設定を反映させるためApacheコンテナを再起動する。

# docker exec -it apache01 /bin/bash
Container> # ls -l conf/httpd.conf
Container> # sed -i -e 's/#LoadModule proxy_module modules\/mod_proxy.so/LoadModule proxy_module modules\/mod_proxy.so/' conf/httpd.conf
Container> # sed -i -e 's/#LoadModule proxy_ajp_module modules\/mod_proxy_ajp.so/LoadModule proxy_ajp_module modules\/mod_proxy_ajp.so/' conf/httpd.conf
Container> # cat conf/httpd.conf | grep -e mod_proxy.so -e mod_proxy_ajp.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
Container> # echo "ProxyPass / ajp://tomcat01:8009/" >> conf/httpd.conf
Container> # tail -1 conf/httpd.conf
ProxyPass / ajp://tomcat01:8009/
Container> # exit

# docker restart apache01

WEBアプリ接続確認

ブラウザを起動し、http://<SERVER_IP_ADDRESS>/terasoluna-tourreservation-web にアクセスする。<SERVER_IP_ADDRESS>にはApacheコンテナではなくホスト(CentOS)のIPアドレスを指定する。以下のサンプルWEBアプリの画面が表示されていれば 構築完了です。

f:id:chakkyNote:20210317014529p:plain


おわりに

本記事ではDockerコンテナの公式イメージを使ってWEBアプリを構築してみました。コンテナ技術によって、製品のインストールや設定が非常に簡素化されることが実感できました。各コンポーネント連携に必要な設定も最小限であり、疎結合アーキテクチャを構成しやすいと感じました。次のステップとしては今回構築したコンテナWEBアプリをdocker-composeでパッケージ化し、環境複製することを視野に入れたより実践的なシナリオに挑戦してみたいと思います。

CentOS VM上に標準OSSでサンプルWEBアプリを実装する

はじめに

本記事では、ローカルなCentOS VM上にApacheTomcatPostgreSQLの標準的なOSSをインストールし、NTTデータ社がGitHubで公開しているサンプルWEBアプリ『TERASOLUNAツアー予約システム』デプロイします。コンテナ技術を扱った記事を今後書いていく上での出発点として投稿します。


想定環境

本記事で想定している環境構成を記載します。

カテゴリ 製品・バージョン
仮想化製品 VMware Workstation 15 Player
ゲストOS Cent OS 8.1
(CPU : 2 / Memory : 4GB / Disk : 20GB / NW : NAT)
WEBサーバ Apache 2.4.37
APサーバ Apache Tomcat 8.5.63
https://tomcat.apache.org/download-80.cgi
データベース psql (PostgreSQL) 12-12.5
https://yum.postgresql.org/repopackages
アプリケーション terasoluna-tourreservation 5.6.1
https://github.com/terasolunaorg/terasoluna-tourreservation

留意事項

本記事の表記ルール、お作法について記載します。

表記ルール

・コードブロックにおける"#"はrootユーザでの実行です。

・コードブロックにおける"$"は非rootユーザでの実行です。

・"#”や""$"などプロンプトの表示がないものは標準出力です。

お作法

・ファイルのバックアップなどは各自要否を判断してください。

・想定外動作の原因にもなるので、バージョンはできるだけ揃えることを推奨します。


OS基本設定

CentOS 8.1インストール初期状態から基本的なOS設定を行います。インストール後にifconfigコマンドでIPアドレスを確認し、ホストOSからTeratermなどで接続して下さい。

ホスト名の設定

任意のホスト名を設定する。ここでは"terasolunaTour"に設定します。

$ su -
# echo "terasolunaTour" > /etc/hostname
# cat /etc/hostname
terasolunaTour

タイムゾーンの設定

タイムゾーンを"Asia/Tokyo"に設定します。

# timedatectl set-timezone Asia/Tokyo
# timedatectl status | grep "Time zone"
                 Time zone: Asia/Tokyo (JST, +0900)

ファイアウォールの無効化

検証用の環境のため、ファイアウォールは無効化しておく。

# systemctl stop firewalld
# systemctl disable firewalld
# systemctl status firewalld

SELinuxの無効化

同様の理由で、SELinuxも無効化しておく。

# sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
# cat /etc/selinux/config | grep -v ^# | grep "SELINUX="
SELINUX=disabled

OS再起動

設定反映を確認するため、OS再起動する。

# shutdown -r now

Apacheインストール・設定

WEBサーバにあたるApacheをインストールし、サービス起動後に正常性を確認する。

Apacheインストール

yumコマンドでApacheをインストールする。インストール後にバージョンを確認し、サービス起動と自動起動有効化を行う。

$ su -
# yum -y install httpd
# httpd -version | grep version
Server version: Apache/2.4.37 (centos)

# systemctl start httpd.service
# systemctl enable httpd.service
# systemctl status httpd.service

Apacheテストページ接続

ブラウザを起動し、http://<SERVER_IP_ADDRESS> にアクセスする。正常に起動していれば以下画面が表示される。

IPアドレスはゲストOSに割り当てられているものに置き換えて下さい。

f:id:chakkyNote:20210317010100p:plain


Tomcatインストール・設定

APサーバにあたるTomcatをインストールし、基本的な設定を行う。

専用ユーザ作成・バイナリイメージダウンロード

Tomcat専用ユーザを作成し、バイナリイメージをダウンロードする。ダウンロードしてファイル解凍後、/optディレクトリ配下にイメージを移動し、バージョン差異を吸収するためのシンボリックリンクを作成する。

# useradd -s /sbin/nologin tomcat
# id tomcat
uid=1001(tomcat) gid=1001(tomcat) groups=1001(tomcat)
# mkdir /usr/local/src/tomcat
# cd /usr/local/src/tomcat

# wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.61/bin/apache-tomcat-8.5.61.tar.gz
# tar zxvf apache-tomcat-8.5.61.tar.gz
# ls -ltr

# mv apache-tomcat-8.5.61 /opt/
# chown -R tomcat. /opt/apache-tomcat-8.5.61/
# ln -s /opt/apache-tomcat-8.5.61/ /opt/tomcat
# ls -ld /opt/tomcat

環境変数設定

Tomcat環境変数CATALINA_HOMEを設定する。

# cat <<EOF >> /etc/profile
# for Tomcat Setting
export CATALINA_HOME=/opt/tomcat
EOF
# source /etc/profile
# env | grep CATALINA
CATALINA_HOME=/opt/tomcat

systemdサービス化

Tomcatはモジュールをダウンロードしただけのため、このままだとsystemdユーティリティによるサービス起動制御ができないため、systemdのサービス定義ファイルを新規作成し、systemctlコマンドでサービス制御ができるようにする。サービス定義ファイル作成後、実行権限を付けてTomcatサービスを起動し、自動起動を有効化する。

# cat <<EOF > /etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat 8
After=syslog.target network.target

[Service]
User=tomcat
Group=tomcat
Type=oneshot
PIDFile=/opt/tomcat/tomcat.pid
RemainAfterExit=yes

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
ExecReStart=/opt/tomcat/bin/shutdown.sh;/opt/tomcat/bin/startup.sh

[Install]

WantedBy=multi-user.target
EOF
# chmod +x /etc/systemd/system/tomcat.service
# systemctl start tomcat.service
# systemctl enable tomcat.service
# systemctl status tomcat.service

Tomcatテストページ接続

ブラウザを起動し、http://<SERVER_IP_ADDRESS>:8080 にアクセスする。正常に起動していれば以下画面が表示される。

f:id:chakkyNote:20210317010137p:plain


PostgreSQLインストール・設定

データベースにあたるPostgreSQLをインストールする。

PostgreSQLのインストール

dnfコマンドでPostgreSQLの最新レポジトリを登録し、インストールを行う。インストールが完了すると/usr/pgsql-12ディレクトリとpostgresユーザが自動的に作成される。また、サンプルWEBアプリはpostgresユーザのパスワードがP0stgresであることを前提に実装されているため、その通りに設定する。

# dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
# dnf -y module disable postgresql
# dnf -y install postgresql12-server
# ls -ld /usr/pgsql-12
# id postgres

# echo P0stgres | passwd --stdin postgres

データベース初期化

データベースを使用可能にするため、データベースの初期化を行う。サンプルWEBアプリの仕様に合わせ、エンコードはUTF8、localeはC、認証方式はSHA-256とする。初期化処理により/var/lib/pgsql/12/dataディレクトリに必要なファイル群が自動的に作成される。初期化完了後、rootユーザでPostgreSQLのサービスを起動し、自動起動を有効化する。

# su - postgres

$ /usr/pgsql-12/bin/initdb -E UTF8 --locale=C -A scram-sha-256 -W
Enter new superuser password:P0stgres
Enter it again:P0stgres
$ ls -l /var/lib/pgsql/12/data
$ exit

# systemctl start postgresql-12.service
# systemctl enable postgresql-12.service
# systemctl status postgresql-12.service

PostgreSQL環境変数設定

.psql_profileファイルにPostgreSQL環境変数を設定する。postgresユーザのbash_profileに.psql_profileが存在すれば読み込む処理があるため、.pgsql_profileの記載内容はpostgresユーザログイン時に自動的に反映されるようになっている。ファイル作成後、.bash_profileを読み込み、環境変数が設定されていることを確認する。

# su - postgres
$ cat <<"EOF" > ~/.pgsql_profile
PATH=/usr/pgsql-12/bin:$PATH
MANPATH=/usr/pgsql-12/share/man:$MANPATH
PGDATA=/var/lib/pgsql/12/data
export PATH MANPATH PGDATA
EOF
$ . ~/.bash_profile
$ env | grep -e PATH -e PGDATA

データベース接続確認

基本的な設定が完了したので、psqlコマンドでデータベースに接続してみる。接続するとデフォルトで作成される3つのデータベースの一覧情報が表示される。データベースが作成されていることを確認できたらrootユーザに戻る。

$ psql -l
Password for user postgres:P0stgres

 List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+---------+-------+-----------------------
postgres   | postgres | UTF8     | C       | C     |
template0  | postgres | UTF8     | C       | C     | =c/postgres +
           |          |          |         |       | postgres=CTc/postgres
template1  | postgres | UTF8     | C       | C     | =c/postgres +
           |          |          |         |       | postgres=CTc/postgres
(3 rows)

$ exit

サンプルWEBアプリ実装

Mavenインストール

GitHubではMavenを使用してアプリケーションの構築を行う手順が公開されている。手順に沿って構築するため、まずはMavenのインストールを行う。Mavenは設定ファイル(pom.xml)の内容を元にプログラムを実行することで、開発者に依存しない環境構築を可能にするJavaベースの管理ツールである。

# id -un
root
# cd ~
# dnf -y install maven

資材ダウンロード

GitHubページからサンプルWEBアプリの資材をダウンロードし、解凍する。

# wget https://github.com/terasolunaorg/terasoluna-tourreservation/archive/5.6.1.RELEASE.tar.gz
# tar zxvf 5.6.1.RELEASE.tar.gz
# ls -ld terasoluna-tourreservation-5.6.1.RELEASE/

データベース作成

サンプルWEBアプリ用のデータベースを作成する。サンプルWEBアプリのプログラムは、データベース名がtourreserveであることを前提に実装されているため、この名前で作成する。データベース作成後にtourreserveデータベースが作成されていることを確認する。

# su - postgres
$ psql -U postgres
Password for user postgres:P0stgres
postgres=# CREATE DATABASE tourreserve;
postgres=# \l
postgres=# \q
$ exit

サンプルデータINSERT

Mavenをインストールしたらオブジェクトの作成とサンプルデータをINSERTする。なお、Maven実行前に読み込むpom.xmlファイルと実行するSQLファイルセットの内容を確認している。mvnコマンドを実行し、最後に"BUILD SUCCESS"と表示されていればデータセット成功している。処理実行後、データベースにサンプルWEBアプリ用のテーブルが作成されていることを確認する。

# cd ~/terasoluna-tourreservation-5.6.1.RELEASE
# ls -l terasoluna-tourreservation-initdb/src/main/sqls/postgres
# cat terasoluna-tourreservation-initdb/pom.xml

# mvn -f terasoluna-tourreservation-initdb/pom.xml sql:execute

# su - postgres
$ psql -d tourreserve -U postgres
Password for user postgres:P0stgres
tourreserve=# \dp
tourreserve=# \q
$ exit

アプリケーションのビルド

アプリケーションのビルドもMavenを使用して行う。pom.xmlファイルがあるディレクトリに移動し、mvnコマンドを実行する。mvnコマンド実行後に"BUILD SUCCESS"と表示されていればビルド成功です。なお、通常であれば事前にJavaJDBCドライバをインストールする必要がありますが、Mavenの処理の中でJavaJDBCドライバが自動でインストールされるため、個別でのインストールは不要です。処理実行後、targetディレクトリにwarファイルが作成されます。

# cd ~/terasoluna-tourreservation-5.6.1.RELEASE/
# cat pom.xml

# mvn clean install

# ls -l terasoluna-tourreservation-web/target/terasoluna-tourreservation-web.war

アプリケーションのデプロイ

作成されたwarファイルをコンテキストルートにコピーすると自動的にwarファイルの展開が行われ、アプリケーションがデプロイされる。

# cp -p terasoluna-tourreservation-web/target/terasoluna-tourreservation-web.war /opt/tomcat/webapps/
# ls -l /opt/tomcat/webapps/

AJPモジュール確認

Apacheに対するアクセスをTomcatにリダイレクトするためのAJP連携設定を行う。AJP連携には以下2つのモジュールがロードされている必要がある。

  • LoadModule proxy_module modules/mod_proxy.so
  • LoadModule proxy_ajp_module modules/mod_proxy_ajp.so

ロードモジュールを定義する00-proxy.confファイルにて、該当モジュールがコメントインされていることを確認する。

# cat /etc/httpd/conf.modules.d/00-proxy.conf | grep -e mod_proxy.so -e mod_proxy_ajp.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so

AJP連携設定(Apache)

まずはApache側の設定を行う。サンプルWEBアプリのURLにアクセスできるように、ProxyPassの設定を行う。

# cat <<EOF > /etc/httpd/conf.d/proxy-ajp.conf
<Location /terasoluna-tourreservation-web >
  ProxyPass ajp://localhost:8009/terasoluna-tourreservation-web
  Order allow,deny
  Allow from all
</Location>
EOF

AJP連携設定(Tomcat)

次に、TomcatApacheからのAJP通信を許可するようにserver.xmlファイルの設定を修正する。AJP通信の待ち受け設定はデフォルトでコメントアウトされているため、コメントインする。また、Tomcat 8ではAJPコネクタを信頼できないサイトと接続する際に必須となるsecretRequiedオプションも追加する。今回の構成では同じサーバ上にあるApacheからのAJP通信しかないためfalseを設定する。

# sed -i -e '/Define an AJP 1.3 Connector on port 8009/{n;d;}' /opt/apache-tomcat-8.5.61/conf/server.xml
# sed -i -e '/Define an AJP 1.3 Connector on port 8009/{n;n;n;n;n;d;}' /opt/apache-tomcat-8.5.61/conf/server.xml
# sed -i -e '/port="8009"/a \\t       secretRequired="false"' /opt/apache-tomcat-8.5.61/conf/server.xml
# grep -A 6 "Define an AJP 1.3 Connector on port 8009" /opt/apache-tomcat-8.5.61/conf/server.xml
    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               secretRequired="false"
               redirectPort="8443" />

設定ファイル修正後、設定を反映させるためにApacheTomcatのサービスを再起動する。

# systemctl restart httpd.service
# systemctl status httpd.service
# systemctl restart tomcat.service
# systemctl status tomcat.service

サンプルWEBアプリ接続

WEBブラウザを起動し、http://<SERVER_IP_ADDRESS>/terasoluna-tourreservation-web/ にアクセスする。先ほどと同様にサンプルWEBアプリの画面が表示されていれば構築完了です。

f:id:chakkyNote:20210317010338p:plain


おわりに

本記事ではOSに直接WEB/AP、DB製品をインストール・設定する昔ながらの構成でWEBアプリを構築しました。採用したサンプルWEBアプリは会員制のツアー予約サイトを提供するものであり、ログイン機能や検索機能など、WEBアプリとしてよく使われる機能が最低限備わっているので、検証題材として有用なサンプルWEBアプリだと思います。今後はこのサンプルWEBアプリを題材に、コンテナ技術などを取り入れたネタを 扱いたいと思います。