ハトネコエ Web がくしゅうちょう

プログラミングとかAndroid

Docker Swarm モードはわかりやすい!

最近はDockerの勉強をしています。

以下の本(おすすめです!)の内容に沿う形で、Docker Swarm モード の動きを試してみたいと思います。

コンテナ・ベース・オーケストレーション Docker/Kubernetesで作るクラウド時代のシステム基盤

コンテナ・ベース・オーケストレーション Docker/Kubernetesで作るクラウド時代のシステム基盤

Swarm モードってなんじゃい? という方は、こちらのドキュメントを読んでくださるのが早いと思います。

Docker Swarm 概要 — Docker-docs-ja 17.06.Beta ドキュメント

かんたんに言うなら、複数のホスト(サーバー)で起動しているDockerを、
1つの司令塔となるサーバーから指示を出すことにより管理できる仕組みです。

1. VirtualBox を利用して複数ホストの構築

最初、「サーバー複数立てるとなるとお金が……」って思いましたが、
本の中で VirtualBox に複数ホストを立てる方法が案内されていました。
なるほど、それならゼロ円!

参考記事 : Docker Machine を使って VirtualBox に Dockerホストを立てる

docker-machine create -d virtualbox <machine-name> のコマンドで、
Docker がインストール済みの Linux イメージを持つ仮想マシンが、 Virtual Box 上に作られるそうです。便利!

しかし、やってみると boot2docker.iso のダウンロードがなぜかなかなか終わらず進めなかったので、
あきらめて https://github.com/boot2docker/boot2docker/releases から最新版をダウンロードしてきて
~/.docker/machine/cache ディレクトリ下に置き、再度コマンドを実行しました。

docker-machine create -d virtualbox manager1
docker-machine create -d virtualbox worker1
docker-machine create -d virtualbox worker2

に成功した後、 docker-machine ls

docker-machine ls

NAME       ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
manager1   -        virtualbox   Running   tcp://192.168.99.100:2376           v18.06.1-ce
worker1    -        virtualbox   Running   tcp://192.168.99.101:2376           v18.06.1-ce
worker2    -        virtualbox   Running   tcp://192.168.99.102:2376           v18.06.1-ce

それぞれのホストのプライベートIPアドレスが確認できますね。

2. manager1 を Swarm マネージャーに

それぞれのホストにて、Docker の Swarm モードを有効にするため、 swarm init をやりに行きます。

docker-machine ssh manager1

SSHログインした後、

docker@manager1:~$ docker swarm init

Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (12.34.56.78 on eth0 and 192.168.99.100 on eth1) - specify one with --advertise-addr

単純に docker swarm init をおこなうとエラーが出てしまいました。

ifconfig コマンドで確認するとわかるのですが、
このマシンには複数のネットワークインターフェイスが接続されており、
どのIPアドレスを外部との通信に使うのか確定ができないのです。

それを解決するため、 advertise-addr オプションを使います。

プライベートIPアドレスでホスト間通信をおこなってほしいので、
さっき docker-machine ls で見たこのマシンのプライベートIPアドレス 192.168.99.100 をここでは指定します。

docker@manager1:~$ docker swarm init --advertise-addr 192.168.99.100

Swarm initialized: current node (hkixhjon23s2xd1pvx57csa7q) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-57m3j6npg9ip54992xdo8pg9pt2yekmh3io92nji2nhysa5b6d-74ml40yfeahd2htj6rpk2hbx2 192.168.99.100:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

これで Swarm モードが有効になりました。

docker@manager1:~$ docker node ls

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
xxkdyatgiikov50oxy0qh1ir9 *   manager1            Ready               Active              Leader              18.06.1-ce

なお、逆に Swarm モードを無効にしたい場合は、 swarm leave をおこないます。
現在はマネージャーが 1 ノードのみですので、 force オプションを使う必要があります。

docker@manager1:~$ docker swarm leave --force

Node left the swarm.

docker node ls は、 Swarm マネージャーがクラスタ一覧を見るためのコマンドですので、
このホストが Swarm モードを無効にしマネージャーでもなくなった今、以下のようにエラーが出るようになります。

docker@manager1:~$ 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.

もう一度 Swarm モードを有効にするため docker swarm init --advertise-addr 192.168.99.100 をしておきます。

3. worker1, worker2 でも swarm init

worker1 および worker2 でも Swarm モードを有効にしたいのですが、
先ほどと同じようなコマンドをすると、新たな独立した Swarm マネージャーが出来てしまうだけです。
今回はもうマネージャーは必要ないので、別のコマンドを用います。

manager1 にて docker swarm join-token worker コマンドを使います。

docker@manager1:~$ docker swarm join-token worker

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-57m3j6npg9ip54992xdo8pg9pt2yekmh3io92nji2nhysa5b6d-74ml40yfeahd2htj6rpk2hbx2 192.168.99.100:2377

最終行にコマンドがありますね。これを使います。

まずはターミナルの別タブを開いて worker1 へログイン。

docker-machine ssh worker1

さっきのコマンドを使ってみます。

docker@worker1:~$ docker swarm join --token SWMTKN-1-57m3j6npg9ip54992xdo8pg9pt2yekmh3io92nji2nhysa5b6d-74ml40yfeahd2htj6rpk2hbx2 192.168.99.100:2377

This node joined a swarm as a worker.

無事に Swarm worker として加わってくれたようです。同じことを worker2 にもおこないます。

4. manager が docker node ls で確認

3. の手順が終わったら、 Swarm manager である manager1 が、
Swarm worker である worker1, worker2 を監視できる状態になっているかを確認します。

manager1 で docker node ls をおこないます。

docker@manager1:~$ docker node ls

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
xxkdyatgiikov50oxy0qh1ir9 *   manager1            Ready               Active              Leader              18.06.1-ce
ra1nagb73mbusbpyxralb4fzj     worker1             Ready               Active                                  18.06.1-ce
ics08k65tcmwj1soe8y8c4887     worker2             Ready               Active                                  18.06.1-ce

逆に worker1 で docker node ls をおこなっても、 Swarm manager でないのでエラーが出るだけで終わります。

docker@worker1:~$ docker node ls

Error response from daemon: This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.

Swarm は日本語で「群れ」の意味です。
その群れに参加している manager1, worker1, worker2 はそれぞれ Node と呼びます。日本語で「ひとつの点」を意味します。

Swarm manager となる Node を manager node
Swarm worker となる Node を worker node と言うので覚えておくと有利です。

docker node ls コマンドは Node をすべて表示するコマンドだと理解すると覚えやすいでしょう。

5. Docker image を展開する

manager, worker それぞれの準備が整ったので Docker image を展開させてみましょう。

以下の2記事を参考にしました、ありがとうございます。

Docker Hub に存在する nginx のイメージを今回は利用します。

docker@manager1:~$ docker service create --name nginx_sample --replicas 2 --publish 8080:80 nginx:1.14

om5gg86083bfiwsi88ugcenm8
overall progress: 2 out of 2 tasks
1/2: running   [==================================================>]
2/2: running   [==================================================>]
verify: Service converged

どこのホストに作成されたか確認します。

docker@manager1:~$ docker service ps nginx_sample

ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
0afwga4oks2q        nginx_sample.1      nginx:1.14          worker1             Running             Running 44 seconds ago
dcs7l9e3cdxq        nginx_sample.2      nginx:1.14          manager1            Running             Running 44 seconds ago

worker1, manager1 に作成されていました。
worker に優先的に作られると思っていたので、これは意外な結果でした。

6. Swarm Load Balancer

さて、nginx が動いていると思いますので、Webブラウザで確認してみましょう。

1. の手順で docker-machine ls をして見たプライベートIPアドレスを用います。
今回は8080番ポートで動かしてるので、 192.168.99.100:8080 にアクセスしてみます。

f:id:nekonenene:20180909125215p:plain

良いですね。

192.168.99.101:8080 , 192.168.99.102:8080 にも同様にアクセスできます。

worker2 では動いていないはずなのに 192.168.99.102:8080 でアクセスできるのは不思議ですが、
これは routing mesh のおかげです。

Swarmモードにした全てのノードは、リクエストがあると
現在 Active であるノードに、その要求を横流しし処理してもらいます。
いわば Swarm Node 間の Load Balancer です。

f:id:nekonenene:20180909131446p:plain

この図を見ると「内部で網目状(=mesh)につながるから routing mesh と呼ぶのか〜」とわかりやすいですね。

7. コンテナを増やす

さて、せっかくサーバーが3つあるのにDockerプロセスを2つのサーバーだけで動かしているようではもったいありません。
コンテナ数を増やしてみましょう。 docker service scale を使います。
縁起良く 7 つにしてみます。

docker@manager1:~$ docker service scale nginx_sample=7

nginx_sample scaled to 7
overall progress: 7 out of 7 tasks
1/7: running   [==================================================>]
2/7: running   [==================================================>]
3/7: running   [==================================================>]
4/7: running   [==================================================>]
5/7: running   [==================================================>]
6/7: running   [==================================================>]
7/7: running   [==================================================>]
verify: Service converged

どのホストで動いてるか確認します。

docker@manager1:~$ docker service ps nginx_sample

ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
0afwga4oks2q        nginx_sample.1      nginx:1.14          worker1             Running             Running 44 minutes ago
dcs7l9e3cdxq        nginx_sample.2      nginx:1.14          manager1            Running             Running 44 minutes ago
tpxmli6n5xcy        nginx_sample.3      nginx:1.14          worker1             Running             Running 8 seconds ago
01x8uej3mabt        nginx_sample.4      nginx:1.14          manager1            Running             Running 8 seconds ago
pxn03gq3d2hu        nginx_sample.5      nginx:1.14          worker2             Running             Running 7 seconds ago
25kpj1yw9gsz        nginx_sample.6      nginx:1.14          worker2             Running             Running 7 seconds ago
bz5648esiqg7        nginx_sample.7      nginx:1.14          worker2             Running             Running 7 seconds ago

manager1 に2つ, worker1に2つ, worker2に3つ, おおむね均等になるように割り振られてますね。

8. プロセスを殺してみる

試しに worker1 での nginx を殺してみます。

docker@worker1:~$ ps ax | grep nginx
12902 ?        Ss     0:00 nginx: master process nginx -g daemon off;
13031 ?        S      0:00 nginx: worker process
13285 ?        Ss     0:00 nginx: master process nginx -g daemon off;
13363 ?        S      0:00 nginx: worker process
13619 pts/0    S+     0:00 grep nginx

docker@worker1:~$ sudo killall nginx

docker@worker1:~$ ps ax | grep nginx
13985 pts/0    S+     0:00 grep nginx

10秒程度待ってもう一度見ると、

docker@worker1:~$ ps ax | grep nginx
14006 ?        Ss     0:00 nginx: master process nginx -g daemon off;
14069 ?        Ss     0:00 nginx: master process nginx -g daemon off;
14143 ?        S      0:00 nginx: worker process
14188 ?        S      0:00 nginx: worker process
14274 pts/0    S+     0:00 grep nginx

勝手に復活していました。

manager1 で docker service ps nginx_sample コマンドから確認しましょう。

docker@manager1:~$ docker service ps nginx_sample

ID                  NAME                 IMAGE               NODE                DESIRED STATE       CURRENT STATE             ERROR                              PORTS
tfb84ly07nvb        nginx_sample.1       nginx:1.14          worker1             Running             Running 10 seconds ago
17slmexy1lry         \_ nginx_sample.1   nginx:1.14          worker1             Shutdown            Complete 16 seconds ago
〜中略〜
8eq2cnxa2mcz        nginx_sample.3       nginx:1.14          worker1             Running             Running 10 seconds ago
v80g7g7jq6k3         \_ nginx_sample.3   nginx:1.14          worker1             Shutdown            Complete 16 seconds ago
〜以下略〜

manager が、heartbeat の機能で worker1 でタスクの終了を確認したので、
期待状態(DESIRED STATE)を維持するために、worker1 に対し新しいタスクの開始を依頼しているのです。

9. nginx のバージョンを上げる

manager node で docker service update を用いて、nginx_sample service のアップデートをおこないたいと思います。

今回は、イメージを nginx:1.14 から nginx:1.15 にアップデートしたいと思います。

docker@manager1:~$ docker service update --image nginx:1.15 nginx_sample

nginx_sample
overall progress: 7 out of 7 tasks
1/7: running   [==================================================>]
2/7: running   [==================================================>]
3/7: running   [==================================================>]
4/7: running   [==================================================>]
5/7: running   [==================================================>]
6/7: running   [==================================================>]
7/7: running   [==================================================>]
verify: Service converged

このアップデート中も、 192.168.99.100:8080 などへのアクセスは変わらず可能です。

6. で説明したロードバランサーが上手く機能してくれているおかげです。

ドキュメントによれば

  1. タスクの停止
  2. アップデート
  3. タスクの再開
  4. タスクの開始が確認できるのであれば、少し時間を置いたのち、次のコンテナに対しこの一連の流れをおこなう

という流れでアップデート作業をしているようです。

この挙動(rolling update)であるなら、一定の時間はバージョンの混同が発生すると思いますので、
その点は留意が必要です。

10. service update を戻す

「あ、やべ、バージョンアップしたら挙動おかしくなったわ」という場合に1世代前に戻すことが出来ます。 docker service rollback です。

docker@manager1:~$ docker service rollback nginx_sample

nginx_sample
rollback: manually requested rollback
overall progress: rolling back update: 7 out of 7 tasks
1/7: running   [>                                                  ]
2/7: running   [>                                                  ]
3/7: running   [>                                                  ]
4/7: running   [>                                                  ]
5/7: running   [>                                                  ]
6/7: running   [>                                                  ]
7/7: running   [>                                                  ]
verify: Service converged

11. 使うポート番号を変えたい場合

8080 を指定していましたが、「やっぱり普通に 80 番ポートを使うようにしようかな〜」というときも docker service update で対応可能です。
publish-add オプションや publish-rm オプションを使います。

docker@manager1:~$ docker service update --publish-rm 8080:80 --publish-add 80:80 nginx_sample

nginx_sample
overall progress: 7 out of 7 tasks
1/7: running   [==================================================>]
2/7: running   [==================================================>]
3/7: running   [==================================================>]
4/7: running   [==================================================>]
5/7: running   [==================================================>]
6/7: running   [==================================================>]
7/7: running   [==================================================>]
verify: Service converged

すべてが running になった段階で
8080 番ポートへのアクセスはできなくなり、80 番ポートへのアクセスができるようになります。

192.168.99.100:8080 でなく、 192.168.99.100 (もしくは 192.168.99.100:80 )でアクセスできるようになっていることでしょう。

12. service を終了させる

ひととおり実験したので service を終了させます。 docker service rm でおこなえます。

docker@manager1:~$ docker service rm nginx_sample

nginx_sample

びっくりするほど一瞬で終わり、もう 192.168.99.100 などにはアクセスできなくなっています。

ついでに、VirtualBox を3台動かし続けているのもメモリの無駄遣いなので、
manager1 からログアウトしたら、以下のコマンドで仮想マシンを停止させちゃいましょう。

docker-machine stop manager1 worker1 worker2

Stopping "worker2"...
Stopping "worker1"...
Stopping "manager1"...
Machine "worker2" was stopped.
Machine "worker1" was stopped.
Machine "manager1" was stopped.

スッキリですね。

13. Docker Swarm vs. Kubernetes

Docker Swarm が非常に手軽に、死活監視および自動再起動や、無停止更新をおこなってくれることを確認しました。

では、 Kubernetes より優れているのでしょうか?

Docker Swarm を推す記事もあれば、
Kubernetes を推す記事もあります。

Kubernetes は Docker で正式サポートされ
AWSでも EKS こと Amazon Elastic Container Service for Kubernetes が登場したくらいですから、
Kubernetes がますますデファクトスタンダードになっていくでしょう。
ですので、業務で使うなら、長い目で考えて Kubernetes の方が良さそうです。

一方、特に新たな設定も必要なく、dockerコマンドだけで全てを終わらせることが出来る Docker Swarm は直感的でわかりやすく学習コストが大変少なく魅力的です。
個人プロジェクトなどでDocker管理をおこなう場合には、十分に選択肢のひとつとなり得るかと思います。

詳細な比較は「Docker Swarm vs. Kubernetes」で検索するとたくさん見つかるかと思います。
だいたいどれも、「簡単だけどカスタマイズ性の低いDocker Swarm、学習曲線が急だがカスタマイズ性が高く多機能なKubernetesという結論に落ち着いています。