はじめに
本記事はDocker Swarmでクラスタを構築し、サービスメッシュについて学びます。
環境はAWSのELB(Elastic Load Balancing)と、EC2(Amazon EC2)インスタンス2台を使用し、Docker Swarmのクラスタを構築します。
Kubernetesと比較した際にDocker Swarmを導入するメリットは、導入コストが低いことです。Docker SwarmはKubernetesに比べてハードルが低いため、本番環境にコンテナ技術を導入しようとしているシステムには最適だと考えます。
Docker Swarmを通してサービスメッシュの意義を体感しましょう。
Docker Swarm
Docker Swarmは、Docker社が提供するオーケストレーションツールです。
Docker Swarmを使用することで複数のホストを集約し、簡単にコンテナのデプロイとスケールが実現できます。
Docker Swarmはマネージャとノードで構成され、Swarmは群れを意味します。
Docker Swarのクラスタを構築するためには、以下の作業が必要です。
-
マネージャとノードにDockerをインストールする。
-
マネージャとノード間で通信ができるように、ファイアウォールで必要な通信を許可する。
-
ファイアウォールで許可するルール
ポート番号 | 用途 |
---|---|
2377/tcp | クラスタ管理用の通信 |
4789/udp | オーバーレイ・ネットワーク |
7946/tcp | ノード間通信 |
7946/udp | ノード間通信 |
サービスメッシュ
サービスメッシュはマイクロサービスアーキテクチャを前提とし、アプリケーション間で通信を制御する仕組みを提供します。この制御された通信はメッシュの様に編みがけになっているため、信頼性を向上します。
例として2台構成のホストの場合、通常のアクセスパスは以下になります。
ブラウザでロードバランサーのDNS名にアクセスすると、HTTPリクエストを受け付けたロードバランサーは、ラウンドロビンでバックエンドであるホストの公開ポートにHTTPリクエストを振り分けます。
例えば、片方のホストで何らかの障害が発生し、コンテナがダウンした場合でも以下の経路により、サービスを継続することができます。
要約すると、単純にホストレベルでアプリケーションを分ける構成の場合は、片方のホストに何らかの障害が発生したときにサービスの提供ができなくなります。よって、コンテナ技術を活用し、サービスメッシュにすることで単一障害点をなくすことができます。
Docker Swarm構築
Docker Swarmの構築手順について記載します。
Docker Swarmの構築は、マネージャ、ノードの順に作業を行います。
本記事の例ではweb1がマネージャ、web2がノードになり、Nginxのイメージを使用してデプロイします。
前提条件としてDockerは既にインストールされた状態で、上記で解説したファイアウォールも許可されていることとします。また、本記事では最低限の構成としているため、マネージャ1台、ノード1台になります。
マネージャ
まずはじめに、Docker Swarmの初期化を行います。docker swarm init
コマンドを実行することでSwarmモードが有効になります。
複数IPアドレスを持つ場合は、他のノードとの通信で使用するインターフェースのIPアドレスをadvertise-addr
の引数に指定します。
- Docker Swarmの初期化
# docker swarm init --advertise-addr <IPアドレス>
Swarm initialized: current node (zirc78nsch77ox8di6021ux4n) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-459p0pkyqgxgkhaggjhavd419ldujenqttm1mqmwup0qz9m5qv-1kj3jy6ozwrr2fkj1qvas294a <マネージャのIPアドレス>:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
コマンド実行後、出力結果のdocker swarm join --token SWMTKN-1-(略) <マネージャのIPアドレス>:2377
を控えます。
- node確認
# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
zirc78nsch77ox8di6021ux4n * web1 Ready Active Leader 18.09.9-ce
マネージャの場合は、MANAGER STATUSにLeaderと出力されます。
- ネットワーク確認
# docker network ls
NETWORK ID NAME DRIVER SCOPE
53703efd3d07 bridge bridge local
aacf6f5e0eb4 docker_gwbridge bridge local
1f0d0e4ae3e7 host host local
xip5tlqmokpb ingress overlay swarm
2d36f1c8c80f none null local
Swarmの初期化を行うと新たに2つのネットワーク(docker_gwbridge、ingress)が作成されます。
ノード
次にノードをDocker Swarmのクラスタに参加させるため、ノード側で以下のコマンドを実行します。
--token
の引数にしている値は例になります。上記docker swarm init
コマンドの出力結果をコピーしてペーストすれば大丈夫です。
なお、マネージャ側でdocker swarm join-token worker
コマンドを実行することで、トークンの再表示もできます。
- クラスタ参加
# docker swarm join --token SWMTKN-1-(略) <マネージャのIPアドレス>:2377
This node joined a swarm as a worker.
マネージャ側で確認のため、再度、docker node ls
コマンドを実行すると、nodeが認識されていることが確認できます。
- node確認
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
zirc78nsch77ox8di6021ux4n * web1 Ready Active Leader 18.09.9-ce
n2o22ptdmyhan8qg0ijmo0qn5 web2 Ready Active 18.09.9-ce
Docker Swarmのデプロイ
本記事では管理しやすいdocker-composeでデプロイします。
また、docker service create
コマンドでもデプロイできます。serviceやstackの説明については割愛します。
デプロイ作業はマネージャ側で行います。
任意のディレクトリに移動し、以下のdocker-compose.yml
ファイルを作成します。
- docker-compose.yml
version: "3"
services:
web:
image: nginx
deploy:
replicas: 2
#resources:
# limits:
# cpus: "0.1"
# memory: 100M
restart_policy:
condition: on-failure
ports:
- "80:80"
networks:
- webnet
networks:
webnet:
docker-compose.yml
ファイル作成後、以下のコマンドを実行し、デプロイします。
test
は例となるため、任意の名前を指定します。
# docker stack deploy -c docker-copose.yml test
Updating service test_web (id: egubeieuri00rzmm9imsna93o)
デプロイ後、以下のコマンドでサービスの状態が確認できます。
REPLICASは2となっているので、2つのコンテナが起動しています。
# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
r04mfg1se3nh test_web replicated 2/2 nginx:latest *:80->80/tcp
マネージャとノード側でdocker container ps -a
コマンドを実行すると、コンテナが起動しているのが確認できます。
- マネージャ側
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4a26c3ca6df7 nginx:latest "nginx -g 'daemon of…" 3 seconds ago Up 2 seconds 80/tcp test_web.1.mnnz40tdykzd2intwz5hf68bs
- ノード側
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
614c19349bf0 nginx:latest "nginx -g 'daemon of…" 2 minutes ago Up 2 minutes 80/tcp test_web.2.6om5oazbavassohd4akucbum2
Docker Swarmの動作確認
Docker Swarmの動作確認を行います。
ブラウザからロードバランサーのDNS名にアクセスを行い、正常に負荷分散されることを確認します。
- ロードバランサーのDNS名にアクセス
コンテナのログを確認すると、ラウンドロビンで負荷分散されているのが確認できます。
CONTAINER IDはdocker container ps -a
のコマンドで確認します。
# docker logs -f <CONTAINER ID>
10.255.0.3 - - [06/Feb/2020:14:20:51 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" "<アクセス元のグローバルIP>"
試しにweb2上(ノード側)で稼働しているコンテナを停止します。
# docker stop <CONTAINER ID>
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
423f673b0513 nginx:latest "nginx -g 'daemon of…" 19 hours ago Exited (0) 5 seconds ago test_web.2.kc7yypyasgvjtolsb1zwmhjoy
このときweb2上でコンテナは稼働していません。
ロードバランサーのDNS名にアクセスできることを確認します。
例としてブラウザから停止したweb2の公開IPにアクセスします。
Web1(マネージャ)上のコンテナのアクセスログに、停止したweb2(ノード)の公開IPに対するアクセスが確認できます。
10.255.0.3 - - [07/Feb/2020:02:19:01 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" "-"
停止したweb2(ノード側)のローカルからも、以下のコマンドを実行することでコンテナにアクセスができます。
# curl localhost 80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
curl: (7) Couldn't connect to server
web1(マネージャ)のログでは以下の様に出力されます。
10.255.0.2 - - [07/Feb/2020:02:41:47 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.61.1" "-"
web1(マネージャ)で4789ポートをダンプして見ていると、オーバーレイ・ネットワークの通信を見ることができます。
# tcpdump -nli eth0 port 4789 -Av
Docker Swarmのスケール
既にサービスが起動している状態で、以下のコマンドを実行することでスケールができます。以下のコマンドはコンテナの数を4コンテナに変更しています。
# docker service scale test_web=4
test_web scaled to 4
overall progress: 4 out of 4 tasks
1/4: running [==================================================>]
2/4: running [==================================================>]
3/4: running [==================================================>]
4/4: running [==================================================>]
verify: Service converged
スケール後、docker container ps -a
コマンドを実行すると、コンテナが増えていのが分かります。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
41962946aa48 nginx:latest "nginx -g 'daemon of…" 3 minutes ago Up 3 minutes 80/tcp test_web.4.pytl36ejcd81ybthisxfo47br
423f673b0513 nginx:latest "nginx -g 'daemon of…" 7 hours ago Up 7 hours 80/tcp test_web.2.kc7yypyasgvjtolsb1zwmhjoy
デプロイ時はサーバ1台ずつに対してアプリケーション資産の入れ替えを行う必要がなく、マネージャ1台で済みます。
Docker Swarmの性質
Docker Swarmは冪等性を持っています。
具体的には、Docker Swarmは指定したreplicas数のコンテナを維持するため、クラスタ化している片方のホストがダウンした場合は、片方のホストでコンテナを起動します。
以下はdocker container ps -a
コマンドの出力になります。
例としてweb1とweb2でそれぞれコンテナが起動しています。
- web1
e5ccbfa9739b nginx:latest "nginx -g 'daemon of…" 3 minutes ago Up 3 minutes 80/tcp test_web.1.mrgkhbd7juer72v6bv0l42fxq
- web2
4820c7bbe9c1 nginx:latest "nginx -g 'daemon of…" 3 minutes ago Up 3 minutes 80/tcp test_web.2.wfe1n11s8940rdl8r1c47o6nc
web2のホストを停止しました。web2のダウンを検知すると、web1上でコンテナを作成します。
0a88f53039a3 nginx:latest "nginx -g 'daemon of…" 5 seconds ago Created test_web.2.p06zas3c3kt9ekjojhhfnl3co
e5ccbfa9739b nginx:latest "nginx -g 'daemon of…" 4 minutes ago Up 4 minutes 80/tcp test_web.1.mrgkhbd7juer72v6bv0l42fxq
web1上でコンテナが2台起動しています。
0a88f53039a3 nginx:latest "nginx -g 'daemon of…" 37 seconds ago Up 31 seconds 80/tcp test_web.2.p06zas3c3kt9ekjojhhfnl3co
e5ccbfa9739b nginx:latest "nginx -g 'daemon of…" 5 minutes ago Up 5 minutes 80/tcp test_web.1.mrgkhbd7juer72v6bv0l42fxq
Docker Swarm解除
Docker Swarmの解除方法について記載します。
先にマネージャ側で以下のコマンドを実行し、サービスの削除を行いす。
# docker service rm test_web
test_web
次にノード、マネージャの順に作業を行います。
ノード
- ノードの切り離し
# docker swarm leave
Node left the swarm.
マネージャ
ノードのSTATUSがDOWNになったことを確認します。
- node確認
# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
zirc78nsch77ox8di6021ux4n * web1 Ready Active Leader 18.09.9-ce
n2o22ptdmyhan8qg0ijmo0qn5 web2 Down Active 18.09.9-ce
ノードを削除します。オプションの引数には、ノード名を指定します。
- node削除
# docker node rm --force web2
web2
マネージャからノードが認識されなくなったことを確認します。
- node確認
# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
zirc78nsch77ox8di6021ux4n * web1 Ready Active Leader 18.09.9-ce
最後にマネージャ自身を切り離します。
- マネージャの切り離し
# docker swarm leave --force
Node left the swarm.
Docker Swarm解体
Docker Swarmを使用しない場合は、マネージャ側で以下の作業を行います。
ノードが存在しないことを確認します。
- node確認
# docker node ls
Error response from daemon: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.
Docker Swarmで作成されたネットワークが削除されたことを確認します。docker_gwbridgeのネットワークは残っています。
# docker network ls
NETWORK ID NAME DRIVER SCOPE
987cfc73d87c bridge bridge local
aacf6f5e0eb4 docker_gwbridge bridge local
1f0d0e4ae3e7 host host local
2d36f1c8c80f none null local
以下のコマンドを実行し、使用していない全てのリソース削除します。
- リソース削除
# docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all dangling build cache
Are you sure you want to continue? [y/N] y
Deleted Networks:
docker_gwbridge
Deleted Images:
untagged: nginx@sha256:ad5552c786f128e389a0263104ae39f3d3c7895579d45ae716f528185b36bc6f
deleted: sha256:2073e0bcb60ee98548d313ead5eacbfe16d9054f8800a32bedd859922a99a6e1
deleted: sha256:a3136fbf38691346715cac8360bcdfca0fff812cede416469653670f04e2cab0
deleted: sha256:99360ffcb2da18fd9ede194efaf5d4b90e7aee99f45737e918113e6833dcf278
deleted: sha256:488dfecc21b1bc607e09368d2791cb784cf8c4ec5c05d2952b045b3e0f8cc01e
untagged: nginx@sha256:70821e443be75ea38bdf52a974fd2271babd5875b2b1964f05025981c75a6717
deleted: sha256:5ad3bd0e67a9c542210a21a3c72f56ef6387cf9b7f4c2506d2398d55a2593ed0
deleted: sha256:b69e2ed46519bc33e7c887967e4f61a2ee53aef165b70f75e208937fb42e7b4c
deleted: sha256:4cb7f732537bf0f65cd9f8f7b63bbe71abcf9d0df396f58621ef3be0b2487b27
deleted: sha256:556c5fb0d91b726083a8ce42e2faaed99f11bc68d3f70e2c7bbce87e7e0b3e10
Total reclaimed space: 253.4MB
ナレッジ
docker-composeのバージョン
Docker Swarmを使用する場合に、docker-composeで使用できるバージョンは3になります。
docker-composeでビルドする場合の留意事項
docker-composeでビルド(stack)する場合、イメージが必要になります。
Compose file version 3 referenceより
注:( バージョン3)Composeファイルを使用してSwarmモードでスタックをデプロイする場合、このオプションは無視され ます。このdocker stackコマンドは、ビルド済みのイメージのみを受け入れます。
そのため、Docker registryの環境が必要になります。
ローカルでDockerレジストリをセットアップする方法については、公式の以下URLが参考になります。
例として、以下の様にビルドを指定してデプロイを実行した場合、
version: '3'
services:
app:
build: ./src
以下のエラーメッセージが出力されて、デプロイをすることはできません。
failed to create service stackdemo_backend: Error response from daemon: rpc error: code = InvalidArgument desc = ContainerSpec: image reference must be provided
デプロイ時のエラー
例として2台構成のホストで、docker-compose.ymlファイルのreplicasの値を2にしてデプロイした場合は、基本的に1ホストに対して1コンテナで分散されます。
もし、デプロイ時に片方のホストで2台のコンテナが起動した場合は、何らかのエラーが発生し、片方のホストでコンテナが起動できなかったことが考えられます。エラーはLinuxの場合、シスログから確認することができます。
本記事の構成(Master1台とNode1台)で検証した結果について
本記事の構成(Master1台とNode1台)で検証した結果について、備忘として記載します。
端的に言うと、Master1台とNode1台では真の冗長化は実現できません。
更に信頼性を上げる場合は、Master2台で冗長化を行う必要があります。Masterの冗長化については、公式に記載があります。
-
本記事の構成(Master1台とNode1台)で片方のコンテナを停止または削除したときの挙動
Master及びNodeの片方のコンテナを停止または削除した場合は、docker-compose.yml
ファイルのreplicasで定義した数になるまで自己再生します。また、この瞬断が発生している間に公開IPにアクセスしてもサービスメッシュ経由でアクセスできます。しかし、localだと、一瞬瞬断が発生します。 -
本記事の構成(Master1台とNode1台)でNodeのホスト自体を停止したときの挙動
Master側にdocker-compose.yml
ファイルのreplicasで定義した数になるまで、Nodeで稼働していたコンテナが作成されます。(動きとしては、VmwareのVmotionに似ていますが、実態は違います。)
その後、Nodeを起動してもNodeで稼働していたコンテナは自動復旧しないため、手動の対応が必要です。ちなみにMasterで先ほど作成されたコンテナを停止または削除すると、Node側でまたコンテナが作成されます。 -
本記事の構成(Master1台とNode1台)でMasterのホスト自体を停止したときの挙動
Docker SwarmのMasterは停止していますが、サービスメッシュによりアクセスはできるのでアプリケーションとして継続することができます。
おわりに
不確実性があり変化の速さが求めれられる現代のアプリケーション開発には、オーケストレーションツールは必須な技術です。
応用としてサーバレスの技術を使用し、CPUやメモリリソース等のしきい値等を設定して、しきい値を超えたらサーバをスケールアウトなどの運用も行うことができます。