Docker Swarm (Swarm Mode) チュートリアルをしてみる


[Docker Swarm (Swarm Mode)] のチュートリアル [Getting started with swarm mode] をAWS上でやってみたので日本語でまとめです。


## 何がうれしいの？

* ノード (サーバー) を跨いで Dockerサービス のデプロイ/アップデートができます
* 各ノードへの公開アクセスを [Swarm Load Balancer] が適切なノードのコンテナにリクエストを振り分けてくれます
    * たとえ **コンテナが一つも動いていないノードにアクセス** しても、別のノードのコンテナがレスポンスを返してくれます
    * 内部のサービス間のオーケストレーションも [Swarm Load Balancer] がしてくれている様です
* サービスの [Rolling Update] が出来ます
* (他の似たOSSと比べて) 非常にシンプルで扱いやすいです


![ingress-routing-mesh.png](https://qiita-image-store.s3.amazonaws.com/0/60131/7cc67d8c-12da-006a-1f27-5d03298fd966.png "ingress-routing-mesh.png")

> ※ docker.com 公式より, [Swarm Load Balancer] の図。


## 分からなかったこと

Rollling Update は処理中のリクエストが全て完了するのを待ってからアップデートを開始してくれるか？

> AWSで言うところの ELB > [Connection Draining] の動きになるか？
> 誰か教えて下さい〜 m(_ _)m


## チュートリアルまとめ

[Getting started with swarm mode] を色々と端折って説明します。

* 全てAWS上 [Amazon Linux 2017.03.1 (ami-bbf2f9dc)] で確認しました
* sudoが面倒なので ``alias docker='sudo docker'`` してあります。

### ポートを開ける (Security Groups)

各サーバーのポートを開けます。AWSの Security Groups では以下の様になります。

|プロトコル   |ポート範囲   |ソース           |Remarks                             |
|:----------|:----------|:---------------|:-----------------------------------|
|TCP        |8080       |0.0.0.0/0       |Nginxコンテナ用の公開ポート             |
|TCP        |22         |0.0.0.0/0       |SSH接続                              |
|TCP        |2377       |{self-group-id} |Swarmクラスタ管理の通信に利用                |
|TCP        |7946       |{self-group-id} |Routing mesh ([Swarm Load Balancer])|
|UDP        |7946       |{self-group-id} |Routing mesh ([Swarm Load Balancer])|
|UDP        |4789       |{self-group-id} |Routing mesh ([Swarm Load Balancer])|

### ノード (サーバー) を起動します

AWS Console から3台のインスタンスを起動します。例えば以下のような感じです。
予めTerminalを3つ開いておき、SSHでログインを済ませておくと後の手順が楽です。

|役割        |Unix Hostname    |IP            |
|:----------|:----------------|:-------------|
|manager    |ip-172-30-1-100  |172.30.1.100  |
|worker1    |ip-172-30-1-200  |172.30.1.200  |
|worker2    |ip-172-30-1-201  |172.30.1.201  |

> Hostname は分かりやすさの為に書いています。手順上重要ではないです


### managerノードを起動する

managerノード ``172.30.1.100`` で以下のコマンドを実行します。

```shell-session
$ docker swarm init --advertise-addr 172.30.1.100

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

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

    docker swarm join \
    --token SWMTKN-1-67w211zr8eu6h86cfdy09liwlbc6id4nlxrwr0d8u8fcn1x95u-93n335bihesdnwz6650y2ynmy \
    172.30.1.100:2377

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


### workerノードをmanagerノードにjoinする

worker1ノード ``172.30.1.200``, worker2ノード ``172.30.1.201`` で以下のコマンドを実行します。

> ``--token ...`` が毎回違うと思いますので、前手順でConsole出力された物をコピペして下さい

```shell-session
$ docker swarm join \
  --token SWMTKN-1-67w211zr8eu6h86cfdy09liwlbc6id4nlxrwr0d8u8fcn1x95u-93n335bihesdnwz6650y2ynmy \
  172.30.1.100:2377

This node joined a swarm as a worker.
```

### Swarmのノード状態を確認する

以下のコマンドでSwarmのノード状態を確認できます。workerノードからも確認が可能で、今いるノードに ``*`` が付いています。

```shell-session
$ docker node ls

ID                           HOSTNAME         STATUS  AVAILABILITY  MANAGER STATUS
udhy6l2u7e2tjo3tstmnz6tiu *  ip-172-30-1-100  Ready   Active        Leader
zsj8brmyynj67bqlw4qhvpgn9    ip-172-30-1-200  Ready   Active
p2jfiueccxcqmtt1wqe4r5kvd    ip-172-30-1-201  Ready   Active
```

### サービスをデプロイする

コマンド ``docker service create {イメージ}`` でデプロイします。

```shell-session
$ docker service create \
  --replicas 3 \
  --name redis \
  --update-delay 10s \
  redis:3.0.6

q9b5a65fkms8oix50bt487j5c
```

コマンド ``docker service ps {サービス名}`` で確認できます。3つのコンテナがそれぞれ別のノードで動いている事が分かります。

```shell-session
$ docker service ps redis

ID            NAME     IMAGE        NODE             DESIRED STATE  CURRENT STATE               ERROR  PORTS
2jwzoi4i2r3d  redis.1  redis:3.0.6  ip-172-30-1-100  Running        Running about a minute ago         
1a402z122056  redis.2  redis:3.0.6  ip-172-30-1-200  Running        Running about a minute ago         
thhbs8870wnk  redis.3  redis:3.0.6  ip-172-30-1-201  Running        Running about a minute ago 
```

### サービスを Rolling Update する

コマンド ``docker service update --image {イメージ} {サービス名}`` で [Rolling Update] をします。

```shell-session
$ docker service update --image redis:3.0.7 redis

redis
```

古いコンテナが破棄され、新しいコンテナに交換された事が分かります。デフォルトでは1コンテナずつ順にアップデートされます。

```
$ docker service ps redis

ID            NAME         IMAGE        NODE             DESIRED STATE  CURRENT STATE            ERROR  PORTS
imw1jzgdshta  redis.1      redis:3.0.7  ip-172-30-1-100  Running        Running 24 seconds ago          
2jwzoi4i2r3d   \_ redis.1  redis:3.0.6  ip-172-30-1-100  Shutdown       Shutdown 25 seconds ago         
vr5u12jfpe07  redis.2      redis:3.0.7  ip-172-30-1-200  Running        Running 36 seconds ago          
1a402z122056   \_ redis.2  redis:3.0.6  ip-172-30-1-200  Shutdown       Shutdown 36 seconds ago         
py37p0ip7mgs  redis.3      redis:3.0.7  ip-172-30-1-201  Running        Running 12 seconds ago          
thhbs8870wnk   \_ redis.3  redis:3.0.6  ip-172-30-1-201  Shutdown       Shutdown 13 seconds ago 
```

### サービスを削除する

コマンド ``docker service rm {サービス名}`` でサービスを破棄します。

```shell-session
$ docker service rm redis

redis
```

なくなっています。

```shell-session
$ docker service ps redis

Error: No such service: redis
```

> 実際にコンテナを落とすのには数秒時間がかかるので、``docker ps`` でローカルノードの停止処理中のコンテナを確認できます

### Routing mesh (Swarm Load Balancer)

Swarmノード上 では [Swarm Load Balancer] が動いており、サービスの公開ポート (フォワードしたホストOSのポート) へのアクセスは適切なコンテナに自動的に割り振りされます。

![ingress-routing-mesh.png](https://qiita-image-store.s3.amazonaws.com/0/60131/7cc67d8c-12da-006a-1f27-5d03298fd966.png "ingress-routing-mesh.png")

> ※ docker.com 公式より。図中のIPと本記事中のIPは異なります。ごめんなさい

Nginxサービスを2コンテナで起動します。ホストOSの ``:8080`` を公開ポートに割り当てています。

```shell-session
$ docker service create \
  --name my-web \
  --publish 8080:80 \
  --replicas 2 \
  nginx

utj6pvxbcw7wjlqhlaylf2wyg
```

3つのノードのうち、2つのノード (worker1, worker2) にコンテナがデプロイされました。

```shell-session
$ docker service ps my-web

ID            NAME      IMAGE         NODE             DESIRED STATE  CURRENT STATE          ERROR  PORTS
rom8aei8b8ye  my-web.1  nginx:latest  ip-172-30-1-200  Running        Running 7 seconds ago         
5sd5szh49gvg  my-web.2  nginx:latest  ip-172-30-1-201  Running        Running 7 seconds ago 
```

コンテナがデプロイされたノード (worker1) のNginxに ``curl`` でアクセスしてみます。

```shell-session
$ curl 172.30.1.200:8080

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```

コンテナが一切動いていないノード (manager) も同じ様にアクセスしてちゃんとレスポンスが返ります！

```shell-session
$ curl 172.30.1.100:8080

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```


[Docker Swarm (Swarm Mode)]:https://docs.docker.com/engine/swarm/
[Getting started with swarm mode]:https://docs.docker.com/engine/swarm/swarm-tutorial/
[Rolling Update]:https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/
[Swarm Load Balancer]:https://docs.docker.com/engine/swarm/ingress/#publish-a-port-for-tcp-only-or-udp-only
[Connection Draining]:http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/config-conn-drain.html
[Amazon Linux 2017.03.1 (ami-bbf2f9dc)]:https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Images:visibility=public-images;search=ami-bbf2f9dc;sort=name