※ 2021/11/29 更新
環境を新しくしたため、説明を加筆、修正しました。
前回の記事 の続きです。
今回は、コンテナオーケストレータの一つである Docker Swarm を使い、アプリケーション構築を実施します。
Dockerの説明に注力するため、GitHubにて今回説明に使用するコードを公開しています。
こちらをダウンロードしてご確認いただければと思います。
前提条件
今回は、以下の環境で構築します。
- Windows 11 Pro バージョン 21H2
- VirtualBox 6.1.28
- Vagrant 2.2.18
Vagrant で構築した AlmaLinux8.4 に docker をインストールして docker swarm の環境を構築します。
構築の流れ
大まかに、以下の流れで構築していきます。
- Vagrant で、 VM を 3 台分構築
- それぞれの VM にログインし、それぞれの構築用に準備したシェルスクリプトを実行
- 2 台目の docker 環境を 1 台目の docker swarm へ join させる
実際に手を動かすのはこれだけです。
シェルスクリプトで行われるのは AlmaLinux の準備と、 Chef Infra Client での自動構築です。
最終的に以下のような形に構築されます。
アプリケーションは、 Python のフレームワークである Flask-RESTful を使用して REST API を構築しています。
Web サーバとして Nginx を使用しています。
AlmaLinux の 80 番ポートで受けた HTTP 通信を Docker 上の Nginx に流し、そこから Flask へプロキシし、結果を Nginx 経由で返します。
Docker のネットワーク的には以下のイメージになります。
この図で言いたいのは、 Docker Swarm で構築される Network はホスト(ここでは AlmaLinux のこと)を意識しなくていい、ということです。
Nginx はそれぞれのホストとポートを共有する関係上、ホストと 1:1 の関係になりますが、その先の Flask のコンテナはホストを意識せずにロードバランスされます。
構築されるものがどういった構成か、イメージがついたでしょうか。
ここからは実際に手を動かしていきます。
VM の準備
前提条件に記載した環境が整っている前提で進めます。
バージョンは必ずしも合っていなくても問題ないかと思いますが、 VirtualBox のバージョンが新しくなると Vagrant がバージョンアップするまで対応していないこともあるので、 Vagrant の最新バージョンがどの VirtualBox のバージョンまで対応しているかを確認してからインストールすることをお勧めします。
フォルダの準備
VM は 3 台必要なので、フォルダを 3 つ用意しましょう。
自分の場合は、以下のようにしました。
- D:\VirtualMachines\Vagrant\DockerTest\primary
- D:\VirtualMachines\Vagrant\DockerTest\secondary
- D:\VirtualMachines\Vagrant\DockerTest\mysql
フォルダ名は自身が分かればなんでもいいと思いますが、上記のフォルダ構成と仮定して進めます。
上記と異なる場合、適宜読み替えてください。
Vagrantfile の準備
先に box を追加しておきます。
コマンドプロンプトを立ち上げ、以下のコマンドを実行します。
vagrant box add bento/almalinux-8.4
次に、コマンドプロンプトで以下のコマンドを実行していきます。
cd /d D:\VirtualMachines\Vagrant\DockerTest\primary
vagrant init bento/almalinux-8.4
cd ..\secondary
vagrant init bento/almalinux-8.4
cd ..\mysql
vagrant init bento/almalinux-8.4
それぞれのフォルダ内に作成された Vagrantfile
を修正します。
アプリケーションの都合上、 IP アドレスは指定があります。
すでに使用済みの IP アドレスの場合、任意の IP アドレスを指定し、適宜読み替えてください。
アプリケーション上でも IP アドレスを記載している箇所があるので、その点の修正も忘れずに。
メモ帳やサクラエディタなどのテキストエディタで Vagrantfile を開き、修正します。
primary の場合
- box バージョンの指定
-
config.vm.box = "bento/almalinux-8.4"
という記載の下にconfig.vm.box_version = "202109.10.0"
を追記します。これは、ほかのバージョンで動くか分からないために固定しています。
-
- IP アドレスの設定
-
config.vm.network
のコメントアウトを外し、config.vm.network "private_network", ip: "192.168.33.10"
とします
-
- ホスト名を付ける
-
config.vm.network
の下に、config.vm.hostname = "flask-primary"
を追加します
-
- 同期フォルダの設定
- GitHub から取得いただいたソースを VM 上にマウントするため、
config.vm.synced_folder
の部分をconfig.vm.synced_folder "D:/program_src/flask-restful", "/vagrant_data"
のように指定します(ソース配置場所は適宜読み替えてください)- バックスラッシュではなくスラッシュに変更する必要あり
- GitHub から取得いただいたソースを VM 上にマウントするため、
secondary の場合
基本的に primary と同じです。
- box バージョンの指定
-
config.vm.box = "bento/almalinux-8.4"
という記載の下にconfig.vm.box_version = "202109.10.0"
を追記します。これは、ほかのバージョンで動くか分からないために固定しています。
-
- IP アドレスの設定
-
config.vm.network
のコメントアウトを外し、config.vm.network "private_network", ip: "192.168.33.11"
とします(primary との相違点)
-
- ホスト名を付ける
-
config.vm.network
の下に、config.vm.hostname = "flask-secondary"
を追加します(primary との相違点)
-
- 同期フォルダの設定
- GitHub から取得いただいたソースを VM 上にマウントするため、
config.vm.synced_folder
の部分をconfig.vm.synced_folder "D:/program_src/flask-restful", "/vagrant_data"
のように指定します(ソース配置場所は適宜読み替えてください)- バックスラッシュではなくスラッシュに変更する必要あり
- GitHub から取得いただいたソースを VM 上にマウントするため、
mysql-server の場合
こちらもほぼ primary と同じです。
- box バージョンの指定
-
config.vm.box = "bento/almalinux-8.4"
という記載の下にconfig.vm.box_version = "202109.10.0"
を追記します。これは、ほかのバージョンで動くか分からないために固定しています。
-
- IP アドレスの設定
-
config.vm.network
のコメントアウトを外し、config.vm.network "private_network", ip: "192.168.33.20"
とします(primary との相違点)
-
- ホスト名を付ける
-
config.vm.network
の下に、config.vm.hostname = "mysql-server"
を記載します(primary との相違点)
-
- 同期フォルダの設定
- GitHub から取得いただいたソースを VM 上にマウントするため、
config.vm.synced_folder
の部分をconfig.vm.synced_folder "D:/program_src/flask-restful", "/vagrant_data"
のように指定します(ソース配置場所は適宜読み替えてください)- バックスラッシュではなくスラッシュに変更する必要あり
- GitHub から取得いただいたソースを VM 上にマウントするため、
VM の起動
Vagrantfile の修正が終わったので、順次 vagrant up
していきます。
コマンドプロンプトで以下のようにするだけです。
cd /d D:\VirtualMachines\Vagrant\DockerTest\primary
vagrant up
cd ..\secondary
vagrant up
cd ..\mysql
vagrant up
環境構築
立ち上がったそれぞれの VM を構築していきます。
全て説明するのはかなり厳しいので、 Chef Infra Client により自動化しています。
また、 Chef Infra Client のインストール自体も手動ではなくシェルスクリプトで一括してやるようにしています。
primary の場合
VM にログインするためにコマンドプロンプトを使います。
cd /d D:\VirtualMachines\Vagrant\DockerTest\primary
vagrant ssh
VM にログイン出来たら、以下のコマンドを実行します。
sudo /vagrant_data/provisioning_primary.sh
諸々インストールし、 Chef Infra Client で Docker をインストールし docker build で docker イメージを作成、その後 Docker Swarm の構築までやってくれちゃうので何が起こっているのか分かりづらいですかね。
docker のインストール自体は、公式の CentOS へのインストール手順に則って実施しています。
docker build は今回用意しているアプリケーション用に Dockerfile を用意してイメージを作成しています。
Docker Swarm は、管理サーバとなるホストでコマンドを実行して初期化をします。
今回では、 flask-primary のホストで以下のコマンドを実行します。
docker swarm init --advertise-addr 192.168.33.10
Docker Swarm による管理対象に自分自身も入るので、以下のように primary がリーダーとして登録されていることが分かるかと思います。
[vagrant@flask-primary ~]$ sudo docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
3gytnmzhmcw4y3gc9c68w9qdt * flask-primary Ready Active Leader 20.10.11
Docker Swarm の初期化ができたので、以下のコマンドを実行して Docker Swarm によるアプリケーション環境を立ち上げます。
docker stack deploy --with-registry-auth -c /var/app/docker/docker-local.yml test
どのようにコンテナを起動するかを docker-local.yml
で定義し、指定しています。
この YAML ファイルは、 Docker Compose のものと同一です。
Docker Swarm では、 Docker Compose を複数台のサーバーにまたいで管理できるコンテナオーケストレータ、というイメージです。(間違ってたらすみません)
なお、最後の test
は、 Docker Swarm で立ち上げた際のサービス名です。
これがコンテナ名の先頭につきます。
secondary の場合
primary と同じように、コマンドプロンプトで VM にログインします。
cd /d D:\VirtualMachines\Vagrant\DockerTest\secondary
vagrant ssh
VM にログイン出来たら、以下のコマンドを実行します。
sudo /vagrant_data/provisioning_secondary.sh
すでに Docker Swarm のリーダーはいるので、こちらは join する側になります。
join するためには、リーダーでトークンを発行する必要があります。
そのため、 primary の方で以下のコマンドを実行します。
docker swarm join-token worker
実は primary でシェルスクリプトを実行した際の最後に実行しているので、コマンドが表示されていたのですが、同じものが上記コマンドで表示されるかと思います。
表示されるトークンは違うかと思いますが、以下のようなコマンドが表示されていると思います。
docker swarm join --token SWMTKN-1-68nxudsy4hq3p1ztiuvzuujzg8l20eur6b7q2k62jitnavd8cn-447u5cqaifqj9iq06o1g3r53f 192.168.33.10:2377
表示されているコマンドをそのままコピペして secondary で実行すると、 Docker Swarm に正常に参加できていれば自動で Docker コンテナが起動されます。(root ユーザにスイッチしていないと思うので、 sudo
を先頭につける必要があります)
docker ps
コマンドで確認できるかと思います。(こちらも sudo
が必要です)
ただし、 Docker イメージはローカルにある必要があるため、シェルスクリプト内でこちらも docker build
をしています。
Docker Hub のように外部リポジトリにイメージがあり、そこから docker pull
できる形であれば、ローカルで docker build
しておく必要はないはずですが、今回はリポジトリを作成していないため、ローカルのイメージを用いています。
なお、外部リポジトリから取得する場合は YAML ファイルでの image の指定を変更します。
Docker Swarm の状態を確認
すでに実施済みですが、 primary の方で以下のコマンドを実行してみます。(sudo
が必要なため root
ユーザにスイッチしてます)
sudo su
docker node ls
すると、今度は secondary のサーバーが Docker Swarm の node として追加されているのが確認できます。
以下のようなイメージです。
[root@flask-primary vagrant]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
3gytnmzhmcw4y3gc9c68w9qdt * flask-primary Ready Active Leader 20.10.11
rkxzyhty0iivmfdwqhq6z0o9s flask-secondary Ready Active 20.10.11
また、以下のコマンドでサービスの状態を確認可能です。
docker service ls
以下のような結果が返ってくると思います。
[root@flask-primary vagrant]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
lv2hz1xinv8m test_flask replicated 4/4 (max 2 per node) flask:latest
2psamemsshnh test_nginx global 2/2 mynginx:latest *:80->80/tcp
コンテナが片肺になっているかどうかなどを確認する時に使ったりします。
docker stack deploy
したのにコンテナがうまく起動できないな、という時は、以下のコマンドで確認することができます。
docker stack ps test --no-trunc
これは、 test
という名称で立ち上げた Docker Swarm のサービスのコンテナの状況を確認するコマンドです。
起動に失敗しているログが見えたりしますが、 Docker Swarm は起動できるまでコンテナの再起動を繰り返すので、多少のエラーは気にしなくても大丈夫です。
Docker Swarm で構築されたネットワークを確認するためには、まずネットワークの一覧を取得してみましょう。
docker network ls
デフォルトでは、 サービス名_default
という名称でネットワークが作成されます。
今回の例では、 test_default
となります。
では、このネットワークの詳細を確認しています。
docker network inspect test_default
ネットワークに所属するコンテナが見れます。
なお、 Docker Swarm で構築されたネットワークはデフォルトではアタッチ不可のため、 docker stack deploy
した時に起動されたコンテナ以外はネットワークに入れません。
後でネットワークにコンテナを追加したい場合は、 docker-local.yml ではすでに指定しているのですが、 attachable
の設定をする必要があります。
ただし、アタッチ可能にした場合、 docker run
でネットワークを指定してコンテナを起動する時、アタッチした際にネットワークの再構築が行われるらしく、瞬断が発生することがあるようです。
そのため、アタッチ可能にすることは非推奨かもしれません。(最新のバージョンでは検証していないので分かりません)
今度は、コンテナの詳細情報を見てみましょう。
以下のようなコマンドで確認可能です。
docker service inspect test_flask
簡略表示したい場合は、 --pretty
オプションを付けます。
コンテナのログを見る場合は、以下のコマンドで確認します。
docker service logs -f test_flask
docker-local.yml で log-driver の指定をすれば、上記コマンドでなくてもログを指定した方法で転送可能ですが、ここでは取り扱いません。
よく使われるのは、 fluentd とかですかね。
まだ MySQL の VM を構築してないので次に進みます。
mysql-server の場合
これまでと同様に、コマンドプロンプトで VM にログインしてシェルスクリプトを実行します。
cd /d D:\VirtualMachines\Vagrant\DockerTest\secondary
vagrant ssh
sudo /vagrant_data/provisioning_db.sh
MySQL をインストールし、 mydb
という DB を構築し、あとはソース上にある init.sql
を実行するだけです。
ユーザー追加とテーブル追加をするだけですね。
これで構築完了です。
動作確認
REST API なので、動作確認をしてみましょう。
mysql-server で、 curl コマンドで確認可能です。
例えば、以下のコマンドをそれぞれ実行してみてください。
curl http://192.168.33.10
curl http://192.168.33.10/v1/groups
curl http://192.168.33.10/v1/users
JSON 形式でデータが返ってきたと思います。
チープな機能しか実装していないため、ユーザーとグループの CRUD しかできません。
さらに、ユーザーとグループは n:1 で紐づくようなテーブル構成になっていますが、 REST API の戻り値では特にそこを意識していません。
そこまでは手が回りませんでした……
REST API は、以下のように操作します。
# 全グループを取得
curl http://192.168.33.10/v1/groups
# 指定したグループIDのデータを取得
curl http://192.168.33.10/v1/groups/1
# グループを追加
curl http://192.168.33.10/v1/groups -XPUT -H"content-type:application/json" -d'{"group_name": "group1"}'
# グループを更新
curl http://192.168.33.10/v1/groups/1 -XPOST -H"content-type:application/json" -d'{"group_name": "test_group1"}'
# グループを削除
curl http://192.168.33.10/v1/groups/1 -XDELETE
# 全ユーザーを取得
curl http://192.168.33.10/v1/users
# 指定したユーザーIDのデータを取得
curl http://192.168.33.10/v1/users/1
# ユーザーを追加
curl http://192.168.33.10/v1/users -XPUT -H"content-type:application/json" -d'{"user_name": "user1", "group_id": 1}'
# ユーザーを更新
curl http://192.168.33.10/v1/users/1 -XPOST -H"content-type:application/json" -d'{"user_name": "test_user1"}'
# ユーザーを削除
curl http://192.168.33.10/v1/users/1 -XDELETE
それぞれ動作確認してみてください。
終わりに
動作確認できたでしょうか。
ちなみに、 primary を vagrant halt
で落としても、 192.168.33.11
の方で動作はできます。
primary を立ち上げなおすと、元通りになるみたいです。
なお、 secondary を worker ではなく manager として join すると、 primary を停止して立ち上げなおすとリーダーと manager が入れ替わりました。
この挙動の違いがどう影響するのかは調べていないので、余裕のある時にでも更新します……
余談
ようやく Docker Swarm まで記事にできました。
実際にこれを試してみようと思われた方、ありがとうございます。
記事にした甲斐がありました。
ただ、職場で試される方は要注意です。
プロキシ環境下で試される方は、 Docker のプロキシ設定が必要です。
以下のディレクトリを作成し、コンフィグファイルを配置する必要があります。
/etc/systemd/system/docker.service.d
[Service]
Environment="HTTP_PROXY={proxy url}"
ファイル名は適当、 url は適宜設定してください。
また、 docker build 時にも proxy 設定が必要です。
docker build --build-arg HTTP_PROXY={proxy url} --build-arg HTTPS_PROXY={proxy url} -t hoge .
シェルスクリプトを書き換えて実行しましょう。
また、場合によっては SELinux や firewalld も無効にしないといけないかも。
今回使用した bento の box は、 firewalld があらかじめ無効になっているためファイアウォールを意識せずに構築できています。
ということで、ひとまず Docker の記事はこれにて終了です!
お疲れさまでした👍