Docker 1.12RC の swarm mode チュートリアル

# 概要

Docker Engine 1.12 RC1 (リリース候補版)から Docker Swarm の機能が **swarm モード** として統合されました。このチュートリアルは3台のサーバを使い、swarm（クラスタ）の形成と、その上で Nginx サービスとタスク（コンテナ）を起動、スケールアップ、スケールダウン、削除を試します。

所要時間は15分程度です。

## 注意事項

Docker Engine 1.12 RC2 現在の情報に基づいています。将来的な仕様変更や、バグを含む可能性があります。そのため、用途は新しいモードの検証/体験用とお考えいただければと思います。

## 環境の準備

新しい3台のサーバを準備します。RC 版の Docker Engine は既存の環境と共存できません。そのため、既にお使いの環境ではなく、新しい検証用環境を作ることを強くお薦めします。

3台のサーバは、次の役割を持ちます。 **manager** （マネージャ）はクラスタのサービスの受け入れと、タスクの割り振りを行います。 **worker** （ワーカ）はマネージャからの命令を受け取り、タスク（としてのコンテナ）を実行します。

| 名前         | 役割            |
|-------------|----------------|
| ``node-01`` | manager, worker |
| ``node-02`` | worker          |
| ``node-03`` | worker          |

今回は manager が 1 台かつ 3 台ともコンテナを実行する worker です。設定により、3 台とも manager と worker を兼ねられますし、manager 専用にすることも可能です。

なお、環境は、ローカルの仮想化環境やリモートのどちらでも構いません。私は DigitalOcean 上の以下の環境で動作確認を行いました。

* 512MB / 1 CPU / 20 GB SSD の最小環境を三台
* Ubuntu 16.04 x64, CentOS 7.2 x64 
* Private Networking を有効化（クラスタ用通信のため）

ファイアウォールなど、パケットがフィルタされている環境では、次の接続を許可する必要があります。

* `2377(TCP)` クラスタ管理の通信用
* `7496(TCP,UDP)` ノード間の通信用
* `4789(TCP,UDP)` オーバレイ・ネットワークの通信

## RC 版の Docker Engine のセットアップ

サーバにログイン後、以下のコマンドを実行すると、自動的に RC 版に対応したリポジトリとパッケージをセットアップします。

```
# curl -fsSL https://test.docker.com/ | sh
```

CentOS 7.2 では Docker が自動起動しないため、次のコマンドで起動します。

```
# systemctl enable docker
# systemctl start docker
```

動作確認は ``docker version`` を実行し、クライアントとサーバ両方の情報を表示するのを確認します。

```
root@node-01:~# docker version
Client:
 Version:      1.12.0-rc2
 API version:  1.24
 Go version:   go1.6.2
 Git commit:   906eacd-unsupported
 Built:        Fri Jun 17 21:21:56 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.12.0-rc2
 API version:  1.24
 Go version:   go1.6.2
 Git commit:   906eacd-unsupported
 Built:        Fri Jun 17 21:21:56 2016
 OS/Arch:      linux/amd64
```

## swarm クラスタの初期化

Docker Engine の swarm mode を使うには、まず ``docker swarm init`` コマンドを使い、クラスタの初期化を行います。このコマンドを実行したサーバが manager となります。かつ、一番始めに実行したサーバが manager の Leader（リーダ）となります。

ここでは ``node-01`` にログインし、次の `watch` コマンドを実行します。

```
watch -n 1 'echo "$ docker node ls"; docker node ls; echo; echo "$ docker service ls"; docker service ls; echo ; echo "$ docker service tasks mynginx"; docker service tasks mynginx'
```

これは、毎秒画面上に次の情報を表示します。

* swarm クラスタのノード一覧（ ``docker node ls`` ）
* サービス一覧（ ``docker service ls`` ）
* ``mynginx`` サービスのタスク一覧（ ``docker service tasks mynginx`` ）

こうすると、以降のチュートリアルでノードの状態やサービスの状態を、リアルタイムで把握できます。

次に、クラスタを初期化（managerの起動）します。

```
root@node-01:~# docker swarm init --listen-addr <node-01のIPアドレス>:2377
Swarm initialized: current node (3cmtfzqo9vqysc9htzm1adepf) is now a manager.
```

画面上の ``docker node ls`` は、次のように1台のノードがクラスタに存在しているのが分かります。

```
$ docker node ls
ID                           NAME     MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
3cmtfzqo9vqysc9htzm1adepf *  node-01  Accepted    Ready   Active        Leader
```

また、 ``docker info`` コマンドからも、クラスタの情報を確認できます。

```
$ docker info
（省略）
Swarm: active
 NodeID: 3cmtfzqo9vqysc9htzm1adepf
 IsManager: Yes
 Managers: 1
 Nodes: 1
```
 
ここでは ``node-01`` 自身がマネージャ（ IsManager: Yes ）であり、現時点ではマネージャが１つ、ノードが１つと分かります。

次に、 ``node-02`` ``node-03`` をクラスタに追加します。

```
root@node-02:~# docker swarm join <node-01のIPアドレス>:2377
This node joined a Swarm as a worker.

root@node-03:~# docker swarm join <node-01のIPアドレス>:2377
This node joined a Swarm as a worker.
```

画面上にはワーカとして追加したと表示されます。 ``docker node ls`` の情報を見ると、次のように３つのノードが swarm クラスタに参加しているのが分かります。

```
$ docker node ls
ID                           NAME     MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
3cmtfzqo9vqysc9htzm1adepf *  node-01  Accepted    Ready   Active        Leader
6xzla3yzemtnx0iotqxvad3qn    node-02  Accepted    Ready   Active
8tn1a8u5qeh2ohcq3l9uxo2y0    node-03  Accepted    Ready   Active
```

以上で swarm モードを使ってサービスを実行する準備が整いました。

## サービスの作成

swarm モード上でコンテナを実行するには ``docker service create`` コマンドを使います。このコマンドを実行できるのはマネージャ・ノードの ``node-01`` 上でのみです。

```
$ docker service create --replicas <タスク数> \
    --name <サービス名> \
    -p <公開ポート>:<コンテナのポート> \
    <イメージ名>:<タグ>
```

ここでは Nginx コンテナでコンテナ ID を表示する [zembutsu/docker-sample-nginx:1.0](https://hub.docker.com/r/zembutsu/docker-sample-nginx/) イメージを使います。

```
$ docker service create --replicas 1 --name mynginx -p 80:80 zembutsu/docker-sample-nginx:1.0
```
 
次のようにサービスが追加され、タスクとしてのコンテナが実行されるのが分かります。

```
$ docker service ls
ID            NAME     REPLICAS  IMAGE                             COMMAND
dc52legcse3q  mynginx  1/1       zembutsu/docker-sample-nginx:1.0

$ docker service tasks mynginx
ID                         NAME       SERVICE  IMAGE                             LAST STATE
          DESIRED STATE  NODE
12kmzbiirvsq8rli1gqjhbgjz  mynginx.1  mynginx  zembutsu/docker-sample-nginx:1.0  Running 10
 seconds  Running        node-01
```

タスクはコンテナの実行単位であり、注目すべきは ``LAST STATE`` （現在の状態）と ``DESIRED STATE`` （期待状態）です。コマンド時刻時、 ``--replicas 1`` を指定したのは、このサービスは指定したイメージを常時１つ実行するという意味があります。しかし、実際にはイメージのダウンロードや起動に時間がかかるため、今どのような状況にあるかが ``LAST STATE`` に表示されます（ ``Preparing`` -> ``Starting`` --> ``Running`` と遷移）。

また、 ``docker servie ls`` の ``REPLICAS`` も同様です。タスク数が ``0/1`` （現在の数/期待数）から ``1/1`` に変わります。

この例では ``node-01`` でタスク（としてのコンテナ）を実行しました。このコンテナの情報は ``docker ps`` コマンドでも確認できます。

```
root@node-01:~# docker ps
CONTAINER ID        IMAGE                              COMMAND                  CREATED              STATUS              PORTS               NAMES
6095b5af72ba        zembutsu/docker-sample-nginx:1.0   "nginx -g 'daemon off"   About a minute ago   Up About a minute   80/tcp, 443/tcp     mynginx.1.12kmzbiirvsq8rli1gqjhbgjz
```

あとは ``curl`` やブラウザでこのサーバにアクセスします。実行すると、画面上にコンテナ ID を表示します。

```
$ curl http://<host_ip>
<html>
<body>
        <h1>Host: 6095b5af72ba</h1>
</body>
</html>
```

## サービスのスケールアップ・スケールダウン

サービスの状態を変更するには ``docker service update`` コマンドを使います。ここでは ``mynginx`` サービスの数を３つに増やします。

```
root@node-01:~# docker service update --replicas 3 mynginx
```

コマンドを実行して暫くすると、タスクが３つに増えるのが確認できます。

```
$ docker service ls
ID            NAME     REPLICAS  IMAGE                             COMMAND
dc52legcse3q  mynginx  3/3       zembutsu/docker-sample-nginx:1.0

$ docker service tasks mynginx
ID                         NAME       SERVICE  IMAGE                             LAST STATE
          DESIRED STATE  NODE
12kmzbiirvsq8rli1gqjhbgjz  mynginx.1  mynginx  zembutsu/docker-sample-nginx:1.0  Running 5
minutes   Running        node-01
3nyxsldx8wciwvu5r0o5nachl  mynginx.2  mynginx  zembutsu/docker-sample-nginx:1.0  Running 25
 seconds  Running        node-03
1miyrnvmg5i3czmq65sio6ped  mynginx.3  mynginx  zembutsu/docker-sample-nginx:1.0  Running 25
 seconds  Running        node-02
```

この例では `Preparing`  (準備中)に時間がかかりますが、各ノード上に Docker イメージをダウンロードしているからです。イメージのダウンロード後は迅速にコンテナを開始できるようになるため、開始までのタイムラグを短縮したい場合は、あらかじめ各ノード上にイメージをダウンロード（ ``docker pull`` ）しておくのがおすすめです。

また、swarm モードはデフォルトで spread（スプレッド；拡散の意味）ストラテジが適用されるため、各コンテナは全てのノードで均等に起動しようと（実行中コンテナの数が釣り合うように）します。

この状態で、再びサーバ側のホストに ``curl`` でアクセスすると、コンテナIDが毎回切り替わります。各ノード上のコンテナは ``ingress`` という名前のオーバレイ・ネットワークでつながっています。そして、サービスに対するアクセスは、各タスク（コンテナ）に対して自動的に負荷分散します。

``curl`` やブラウザの接続先を ``node-01`` だけでなく、 ``node-02`` ``node-03`` で実行しても、負荷分散されるのを確認します。

## 自動復旧

swarm モードのサービスは、常に期待状態(desire state)を維持し続けます。そのため、あるノードで障害が起これば、自動的に別ノードで追加タスクを実行しなおす仕組みが働きます。

ここでは ``node-03`` 上の Docker Engine を停止します。

```
root@node-03:~# systemctl stop docker
Warning: Stopping docker.service, but it can still be activated by:
  docker.socket
```

暫く待つと、 ``docker node ls`` で ``node-03`` の状態が ``Down`` になります。

```
$ docker node ls
ID                           NAME     MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
3cmtfzqo9vqysc9htzm1adepf *  node-01  Accepted    Ready   Active        Leader
6xzla3yzemtnx0iotqxvad3qn    node-02  Accepted    Ready   Active
8tn1a8u5qeh2ohcq3l9uxo2y0    node-03  Accepted    Down    Active
```

そして、その瞬間、別のノードでタスクが再開されるのが分かります。


```
$ docker service tasks mynginx
ID                         NAME       SERVICE  IMAGE                             LAST STATE
          DESIRED STATE  NODE
12kmzbiirvsq8rli1gqjhbgjz  mynginx.1  mynginx  zembutsu/docker-sample-nginx:1.0  Running 6
minutes   Running        node-01
68vay29xunr8cawwyuhoza2lm  mynginx.2  mynginx  zembutsu/docker-sample-nginx:1.0  Accepted 4
 seconds  Accepted       node-02
1miyrnvmg5i3czmq65sio6ped  mynginx.3  mynginx  zembutsu/docker-sample-nginx:1.0  Running 2
minutes   Running        node-02
```

swarm クラスタのノードは常にマネージャが監視していますので、 ``node-03`` の Docker Engine を再開したら、自動的にクラスタに復旧します。 

```
root@node-03:~# systemctl start docker
```

コマンドを実行すると、再び ``node-03`` は ``Active`` になります。

```
$ docker node ls
ID                           NAME     MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
3cmtfzqo9vqysc9htzm1adepf *  node-01  Accepted    Ready   Active        Leader
6xzla3yzemtnx0iotqxvad3qn    node-02  Accepted    Ready   Active
8tn1a8u5qeh2ohcq3l9uxo2y0    node-03  Accepted    Ready   Active
```

## ローリング・アップデート

``docker service update`` はタスク数の変更だけでなく、実行中のイメージの差し替えも可能です。具体的には起動中のコンテナを停止し、新しいイメージのダウンロードと起動を自動的に行います。

先ほどの ``mynginx`` サービスが使うイメージのタグを ``latest`` に切り替えます。

```
$ docker service update \
    --update-delay 5s \
    --update-parallelism 1 \
    --image zembutsu/docker-sample-nginx:latest 
    mynginx
```

* ``--update-delay`` ノードごとの更新タイミングの遅延指定
* ``--update-arallelism`` 並列に処理するノード数

暫く待つと、自動的にタグが ``latest`` に切り替わります。

```
$ docker service ls
ID            NAME     REPLICAS  IMAGE                                COMMAND
3y37oegkuv4l  mynginx  3/3       zembutsu/docker-sample-nginx:latest

$ docker service tasks mynginx
ID                         NAME       SERVICE  IMAGE                                LAST STATE          DESIRED STATE  NODE
0803qlg3o9cxmu2h5tp1od51t  mynginx.1  mynginx  zembutsu/docker-sample-nginx:latest  Running 11 seconds  Running        node-03
3tsojfojtzngpip1qbct64q8b  mynginx.2  mynginx  zembutsu/docker-sample-nginx:latest  Running 29 seconds  Running        node-01
8f2ch2bbuegbakzjegi8fp1gy  mynginx.3  mynginx  zembutsu/docker-sample-nginx:latest  Running 20 seconds  Running        node-03
```

``curl`` やブラウザで確認すると、新しく ``Version: 1.1`` の文字列が入っているのが分かります。

```
$ curl http://139.59.243.63
<html>
<body>
        <h1>Host: 87f7fec795da</h1>
        Version: 1.1
</body>
</html>
```

## サービスの削除

``docker service rm`` コマンドでサービスを削除します。


```
root@node-01:~# docker service rm mynginx
mynginx

$ docker node ls
ID                           NAME     MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
3cmtfzqo9vqysc9htzm1adepf *  node-01  Accepted    Ready   Active        Leader
6xzla3yzemtnx0iotqxvad3qn    node-02  Accepted    Ready   Active
8tn1a8u5qeh2ohcq3l9uxo2y0    node-03  Accepted    Ready   Active

$ docker service ls
ID  NAME  REPLICAS  IMAGE  COMMAND

$ docker service tasks mynginx
Error: No such service: mynginx
```

あとは、任意のイメージ実行などをお試しください。おつかれさまでした。

## おまけ：global モード

デフォルトではタスクはノードに分散されますが、global モードを指定すると、全ノード上でコンテナを均等に起動できます。ただし、スケールアップやスケールダウンはできません。

実行するには、 ``service create`` や ``service update`` で ``--mode`` オプションを使用します。

例：

```
docker service create --name mynginx -p 80:80 --mode global zembutsu/docker-sample-nginx
du6211e72ml4g3f744a389ui4
```

結果：

```
$ docker service ls
ID            NAME     REPLICAS  IMAGE                         COMMAND
du6211e72ml4  mynginx  global    zembutsu/docker-sample-nginx

$ docker service tasks mynginx
ID                         NAME     SERVICE  IMAGE                         LAST STATE         DESIRED STATE  NODE
a8lh89jjgf5icezfrgaduxwyk  mynginx  mynginx  zembutsu/docker-sample-nginx  Running 6 seconds  Running        node-03
3o29jqb02owmftfjb9hl4464n  mynginx  mynginx  zembutsu/docker-sample-nginx  Running 6 seconds  Running        node-02
9j3h6mx1mqa3j9v076aa7g4ip  mynginx  mynginx  zembutsu/docker-sample-nginx  Running 6 seconds  Running        node-01
```

  
## さらに詳しく

* Swarm モード概要 — Docker-docs-ja 1.12.RC ドキュメント
 * http://docs.docker.jp/engine/swarm/index.html
* Docker 1.12: swarm モードと Ingress Load Balancing 概要 · Pocketstudio Technology Log
 * https://pocketstudio.net/2016/06/23/docker-1-12-swarm-mode-and-ingress-load-balancing/
* Docker 1.12 & Swarm Mode Introduction ～ Docker の新しい技術と swarm モードの紹介
 * http://www.slideshare.net/zembutsu/docker-112-rc-introduction-to-swarm-mode