Docker swarm チュートリアル
Docker swarm チュートリアル
この記事について
Docker swarm を公式ドキュメントのチュートリアルに沿って試してみた内容をまとめています.
使うもの
Set up に書いてある通り, Docker が動く互いにネットワーク接続できるマシン (仮想でもよい) が 3 台必要になります.
僕は macOS 上で docker-machine コマンドと VirtualBox を利用して……
$ docker-machine create manager1 $ docker-machine create worker1 $ docker-machine create worker2
仮想マシンを用意しました.
Docker swarm is 何?
Docker swarm は,複数のマシンからなる Docker のクラスターを構築するための仕組みです.
swarm の基本概念は Swarm mode key concepts にまとめられています. 大まかにかいつまむと,次のようなキーワードが登場します.
- Node: swarm のクラスターを構成する各マシン (Docker engine) のことを Node と呼びます.Node には manager と worker の 2 通りがあり,
- Service と Task:
- Task は Docker コンテナとそこで実行するコマンドのことです.
- Service はクラスターで実行するタスクの定義で,レプリカ数の設定に応じた数の Task が実行されるように指定すること等ができます.
- Load Balancing: クラスターのノード外へサービスを公開する際に,クラスター内で負荷分散が行われるようになります.クラスターを構成しているいずれのノードにリクエストしても (そのノードでは Task が実行されていなくても),サービスにアクセスすることが可能です.
コンテナのオーケストレーションといえば Kubernetes が有名ですが,swarm はもともと Docker の一部として提供されているのもあり,より簡易にトライできそうな雰囲気です.
swarm を構成する
manager のセットアップ
まず,manager として稼働させるマシンに接続します
$ docker-machine ssh manager1
manager として Docker swarm を初期化します.
docker@manager1:~$ docker swarm init --advertise-addr 192.168.99.100 Swarm initialized: current node (tul5yq5nqs72u8mkvvd947w9m) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-20egj6uc7b1q03t93updrwlfi2m7082nsddxvc9sgeywqvqd4l-14kopj72ycqvysdv2lsy6epm5 192.168.99.100:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
出力されるメッセージの中に,今初期化した swarm に対して新たに他の Node を参加させるコマンドも記述されています.
- worker を追加する:
docker swarm join --token SWMTKN-1-20egj6uc7b1q03t93updrwlfi2m7082nsddxvc9sgeywqvqd4l-14kopj72ycqvysdv2lsy6epm5 192.168.99.100:2377
- manager を追加する:
docker swarm join-token manager
を実行し,案内に従う.
swarm に参加している Node を確認するには,docker node
コマンドを使用します.
docker@manager1:~$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION tul5yq5nqs72u8mkvvd947w9m * manager1 Ready Active Leader 18.03.0-ce
ID の隣に * 印がついているのは,現在コマンドを実行している Node 自身を表しています.
worker の追加
先ほど manager を初期化した際に出力されたコマンドを使って,worker1 を swarm に参加させます.
ssh 接続をしてから……
$ docker-machine ssh worker1
docker swarm join
コマンドを実行します
docker@worker1 $ docker swarm join --token SWMTKN-1-20egj6uc7b1q03t93updrwlfi2m7082nsddxvc9sgeywqvqd4l-14kopj72ycqvysdv2lsy6epm5 192.168.99.100:2377
同じことを worker2 でも行います.
そして,manager1 から docker node ls
を実行すると 3 台の Node が swarm に参加できたことがわかるでしょう.
docker@manager1:~$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION tul5yq5nqs72u8mkvvd947w9m * manager1 Ready Active Leader 18.03.0-ce py07mcf6fk9ino9hctipnwpyj worker1 Ready Active 18.03.0-ce mf9qqqgr8uk212lz5a837zccf worker2 Ready Active 18.03.0-ce
docker node ls
は,manager からしか実行できないので注意が必要です.
swarm にサービスをデプロイする
Node 3 台からなる swarm のクラスターを構成できたので,サービスをデプロイしてみます.
サービスをデプロイするには,manager からdocker service create
コマンドを実行します.
docker@manager1:~$ docker service create --replicas 1 --name helloworld alpine ping docker.com
docker run
とかなり似ています.
見慣れないオプションの "--replicas 1" は,読んで字のごとく docker プロセスを (レプリカを) 1 つ走らせるということです.
起動しているサービスは,docker service ls
コマンドで確認します.
docker@manager1:~$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS lljs26w9vi5x helloworld replicated 1/1 alpine:latest
docker service inspect
コマンドを使えば,サービスの使用するイメージや実行するコマンドといった詳細を得ることができます.
docker@manager1:~$ docker service inspect --pretty helloworld ID: lljs26w9vi5xhhy7jgr1kjx6w Name: helloworld Service Mode: Replicated Replicas: 1 Placement: UpdateConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Update order: stop-first RollbackConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Rollback order: stop-first ContainerSpec: Image: alpine:latest@sha256:7df6db5aa61ae9480f52f0b3a06a140ab98d427f86d8d5de0bedab9b8df6b1c0 Args: ping docker.com Resources: Endpoint Mode: vip
(--pretty
オプションを指定しない場合は,JSON 形式で出力されます)
サービスとして動いている docker プロセスは,docker service ps
コマンドで取得できます.
docker@manager1:~$ docker service ps helloworld ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS rtvl9wo07nwn helloworld.1 alpine:latest worker2 Running Running 7 minutes ago
どの Node で何のイメージが実行されているのかが分かります.
サービスをスケールする
サービスをデプロイできたので,今度はサービスをスケールしてみましょう. クラスターらしいですね.
サービスのスケールとは,サービスを構成するタスク (稼働するコンテナ) の数を増減することです.次のようなコマンドを実行します.
docker@manager1:~$ docker service scale helloworld=5 helloworld scaled to 5 overall progress: 5 out of 5 tasks 1/5: running [==================================================>] 2/5: running [==================================================>] 3/5: running [==================================================>] 4/5: running [==================================================>] 5/5: running [==================================================>] verify: Service converged
実際にタスクが増えていることを確認しましょう.
docker@manager1:~$ docker service ps helloworld ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS rtvl9wo07nwn helloworld.1 alpine:latest worker2 Running Running 14 minutes ago syl5rksdy779 helloworld.2 alpine:latest manager1 Running Running 34 seconds ago o3c3oa99a5te helloworld.3 alpine:latest manager1 Running Running 34 seconds ago szf1qy7flz8j helloworld.4 alpine:latest worker2 Running Running 39 seconds ago 5xyffmmtgahj helloworld.5 alpine:latest worker1 Running Running 34 seconds ago
タスクが 3 ノードに適当に分散されていることも分かります. なぜか manager1 が 2 タスク,worker1 が 1 タスクという割り振りになっていますが,まぁそういうこともあるのでしょう……
サービスを削除する
helloworld サービスの役目はここで終わりになります.サービスを停止するには docker service rm
を実行します.削除できたことは docker service ls
や,各ノードで docker ps
を実行すれば確認できます.
docker@manager1:~$ docker service rm helloworld helloworld docker@manager1:~$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS # 何もなし! docker@manager1:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 何もなし!すべてのコンテナが消えるのには何秒かかかります.
サービスをローリングアップデートする
サービスを構成するタスクを,全て一斉にではなく一部ずつアップデートしていくことをローリングアップデートといいます.
アップデートに伴って問題が発生した場合に,影響範囲を絞ることができるといったメリットがあります.
まずは redis:3.0.6 の 3 タスクからなるサービスを作成します.
docker@manager1:~$ docker service create --replicas 3 --name redis --update-delay 10s redis:3.0.6
--update-delay
は,タスクのアップデート同士の間隔を設定します.
デフォルトでは 1 度にアップデートされるタスクは 1 つですが,--update-parallelism
オプションにより同時にアップデートを行うタスクの最大数を設定することもできます.
もしもアップデートが失敗した場合の動作は,--update-failure-action
で指定が可能です.デフォルトでは,アップデートを中断します.
それでは,アップデートを実行しましょう.
docker@manager1:~$ docker service update --image redis:3.0.7 redis redis overall progress: 3 out of 3 tasks 1/3: running [==================================================>] 2/3: running [==================================================>] 3/3: running [==================================================>] verify: Service converged
無事にコマンドが完了したら,アップデートされていることを確認しましょう.
docker@manager1:~$ docker service inspect --pretty redis ID: ms5ooar390takwiill06y6ey8 Name: redis Service Mode: Replicated Replicas: 3 UpdateStatus: State: completed Started: 2 minutes ago Completed: About a minute ago Message: update completed Placement: UpdateConfig: Parallelism: 1 Delay: 10s On failure: pause Monitoring Period: 5s Max failure ratio: 0 Update order: stop-first RollbackConfig: Parallelism: 1 On failure: pause Monitoring Period: 5s Max failure ratio: 0 Rollback order: stop-first ContainerSpec: Image: redis:3.0.7@sha256:730b765df9fe96af414da64a2b67f3a5f70b8fd13a31e5096fee4807ed802e20 Resources: Endpoint Mode: vip
docker@manager1:~$ docker service ps redis ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ofy9j61ayidp redis.1 redis:3.0.7 worker1 Running Running 3 minutes ago 6nq293pf2jtz \_ redis.1 redis:3.0.6 worker1 Shutdown Shutdown 3 minutes ago y9kxv1cwsxpe redis.2 redis:3.0.7 worker2 Running Running 2 minutes ago ifuzzfy80igc \_ redis.2 redis:3.0.6 worker2 Shutdown Shutdown 2 minutes ago wmybv3j8w5jd redis.3 redis:3.0.7 manager1 Running Running 2 minutes ago yfmnr47k8wt0 \_ redis.3 redis:3.0.6 manager1 Shutdown Shutdown 2 minutes ago
イメージが redis:3.0.6 から redis:3.0.7 に切り替わっていることが分かります.
ノードを停止する
サービスを稼働中でも,計画メンテナンスなどでマシンを停止させる必要性は発生します. swarm のノードを "drain" ステータスにすることで,その間はタスクの割り当てなどを受け付けないようにするということができます.
(この "drain" というステータスはあくまでも swarm のステータスであり,docker run
コマンドなどで起動されたコンテナの管理については関知しません.)
いま,worker1 をメンテナンスのため停止したいものとします. 稼働しているタスクはこのようになっているとします.
docker@manager1:~$ docker service ps redis ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS uw92a8l1w1sq redis.1 redis:3.0.6 manager1 Running Running 3 minutes ago pv988a7ecmdt redis.2 redis:3.0.6 worker1 Running Running 3 minutes ago ec4z7huzr27h redis.3 redis:3.0.6 worker2 Running Running 3 minutes ago
manager1 から次のようなコマンドを実行することで,worker1 を停止させることができます.
docker@manager1:~$ docker node update --availability drain worker1
dokcer node
コマンドを確認してみましょう.
docker@manager1:~$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION o67397jwrzch9ccxkhre3sgrh * manager1 Ready Active Leader 18.03.0-ce xpeqk27zg8x2rnf1qpcail6de worker1 Ready Drain 18.03.0-ce 9i8v2ked45bm572ti34oyu3r5 worker2 Ready Active 18.03.0-ce
さらに docker node inspect
コマンドを使えば,worker1 のより詳細な情報を取得できます.
docker@manager1:~$ docker node inspect --pretty worker1 ID: xpeqk27zg8x2rnf1qpcail6de Hostname: worker1 Joined at: 2018-03-31 11:27:28.770241361 +0000 utc Status: State: Ready Availability: Drain ... (以下省略) ...
たしかに worker1 が Drain ステータスになっています.
このとき,稼働していたタスクはどうなっているのでしょうか?
docker service ps
コマンドで確認してみましょう.
docker@manager1:~$ docker service ps redis ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS uw92a8l1w1sq redis.1 redis:3.0.6 manager1 Running Running 10 hours ago 31vg14mao63n redis.2 redis:3.0.6 manager1 Running Running 10 hours ago pv988a7ecmdt \_ redis.2 redis:3.0.6 worker1 Shutdown Shutdown 10 hours ago ec4z7huzr27h redis.3 redis:3.0.6 worker2 Running Running 10 hours ago
worker1 でう退いていたタスク redis.2 が,manager1 に再割り当てされていることが分かります.
worker1 のメンテナンスが完了したら,元に戻すことができます.
$ docker node update --availability active worker1
ステータスを確認してみましょう.
docker@manager1:~$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION o67397jwrzch9ccxkhre3sgrh * manager1 Ready Active Leader 18.03.0-ce xpeqk27zg8x2rnf1qpcail6de worker1 Ready Active 18.03.0-ce 9i8v2ked45bm572ti34oyu3r5 worker2 Ready Active 18.03.0-ce
ステータスが active に戻っており,worker1 は再びタスクを実行できるようになります.
注意として,ステータスが active に戻ったとしても,drain 時に他ノードへ再割り当てされたタスクはそのままとなります. つまり,タスク redis.2 はまだ manager1 で実行されています.
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS uw92a8l1w1sq redis.1 redis:3.0.6 manager1 Running Running 10 hours ago 31vg14mao63n redis.2 redis:3.0.6 manager1 Running Running 10 hours ago pv988a7ecmdt \_ redis.2 redis:3.0.6 worker1 Shutdown Shutdown 10 hours ago ec4z7huzr27h redis.3 redis:3.0.6 worker2 Running Running 10 hours ago
サービスを公開する
swarm のスラスターにはルーティンメッシュが構築されており,外部から任意のノードに送られたリクエストは,swarm 内で動作する適切なタスクへと振り分けられます.
公式チュートリアルの図が分かり易いです.
サービスを外部に公開するには,--publish
オプションを指定します.
試しに,nginx をポート 8080 で公開するようにしてみましょう.
docker@manager1:~$ docker service create --name my-web --replicas 2 --publish published=8080,target=80 nginx
サービスの公開状況は,docker service inspect
コマンドで確認することができます.
docker@manager1:~$ docker service inspect --format="{{json .Endpoint.Spec.Ports}}" my-web [{"Protocol":"tcp","TargetPort":80,"PublishedPort":8080,"PublishMode":"ingress"}]
swarm の外部 (ここでは,swarm のノードを VirtualBox で動作させている macOS) からアクセスしてみます.
ポイントは,swarm の全てのノード (192.168.99.100, 192.168.99.101, 192.168.99.102) からサービスにアクセスできるということです.
このサービスは,--replicas 2
で起動しているため,実際にタスクが動作しているのは 2 ノードのみとなっています.
しかし,swarm 内部のロードバランサーによって,タスクを実行していないノードからでもサービスへのアクセスができるようになっているのです.
おわり
swarm のチュートリアルをさらっとやってみましたが,かなり面白い内容でした. ロードバランサーを勝手に立ち上げて良きに計らってくれるところとか,かなり良いですね.
いまのところ個人的には swarm を使って実際の製品・サービスを提供するというよりかは,クラスター化したテスト環境の作成などに利用してみたいと思っています.
いずれ Kubernetes とかより本格的 (?) なオーケストレーションも試してみたいです.