Docker ComposeをDockerのswarm modeで使えるようになったので試してみた


先日リリースされたDocker 1.13で、swarm mode上でのdocker composeが使えるようになったのでさっそく試しました。

## 何が嬉しいのか

まず、1.12でdockerにビルトインのswarm modeが追加されました。
その時はクラスタをサクッと作れて嬉しいぞ！と歓喜したものです。
しかし、使ってるとservice作るのがめんどくせえ…、とすぐになりました。
そうです。
docker-composeだと各サービスを宣言的に書けるのですが、これがswarm modeのクラスタを対象としていませんでした。

**／(^o^)＼ﾅﾝﾃｺｯﾀｲ**

serviceをコンスタントに再現可能にするためには職人によるシェルスクリプトを残すなどの工夫が必要になります。
それはそれでまあ別にいいんですが…。
まあでもツラいです。

そこで1.13ですよ。
docker-composeによる宣言的なサービスオプションの定義をswarm modeのクラスタ上で展開できます。

これで必要な定義をdocker-composeで管理できます。
スクリプトを管理するよりか差分など見やすくなるはずです。

## ちょっと用語説明

ざっくりこんな感じ

- スタック: docker-composeで管理するサービスのまとまり
- マネージャノード: クラスタをコントロールするノード。通常だとワーカーノードも兼ねてます。
- ワーカーノード: コンテナを実行するノード。
- サービス: コンテナによって実現されるあるプロセス
- タスク: 動いてるコンテナ

## Swarm ModeでDocker Composeを使ってみる

### docker-compose.yml

まずはcomposeファイルを用意しましょう。
swarm modeで使うにはスキーマバージョンを`3`にする必要があります。

```yml
version: '3' # ←コレ
services:
  hogehoge:...
```

[公式リファレンス](https://docs.docker.com/compose/compose-file/#version-3)にもバージョン3についての記述があるので読んどくといいです。
基本は`2`を`3`にすれば動きます。

### クラスタ作成

適当に`docker swarm init`と`docker swarm join`で複数ノードのクラスタ構成してください。
[以前に書いた記事](http://qiita.com/tora470/items/a7e044844dfee21d2648)を参考にしてもいいかも。

今回は以下の構成にしています。

- manager node: 1
- worker node: 3

### composeファイル

こんな感じにしました。
公式のRedmineイメージを参考（パクった）にしました。
[portainer](http://portainer.io/)はswarm modeにも対応したコンテナ管理画面です。
今回はついでに入れてみました。

Redmine2台、MariaDB1台、Fluentdでログ収集コンテナを各ワーカーに1台、Portainerを特定のマネージャノードに1台という構成です。
見てもらえばわかるかもしれませんが、この設定だとFluentdはまともにログ収集してくれないので適当に設定してください。
DBも永続化とか全く考えてないのでそこら辺は適当に設定してください。

```yml
version: '3'
services:
  redmine:
    image: redmine
    ports:
      - 3000:3000
    environment:
      - REDMINE_DB_MYSQL=db
      - REDMINE_DB_PASSWORD=example
    depends_on:
      - db
    restart: always
    deploy:
      replicas: 2
      placement:
        constraints:
          - node.role == worker
  db:
    image: mariadb
    environment:
      - MYSQL_ROOT_PASSWORD=example
      - MYSQL_DATABASE=redmine
    restart: always
    deploy:
      placement:
        constraints:
          - node.role == worker

  logger:
    image: fluent/fluentd
    deploy:
      mode: global
    restart: always

  portainer_master:
    image: portainer/portainer
    ports:
      - "9000:9000"
    privileged: true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: -H unix:///var/run/docker.sock
    deploy:
      mode: global
      placement:
        constraints:
          - node.name == manager-node

```

docker composeの記述を説明していきます。

#### deploy

ここがswarm modeで動かす時のキモです。
バージョン3から`service`セクションの下に`deploy`が追加されています。
詳細は[公式](https://docs.docker.com/compose/compose-file/#deploy)見ましょう。

よく使うのはここら辺です。

```yml
services:
  hoge:
    deploy:
      mode: global
      replicas: 10
      placement:
        constraints:
          - node.role == worker
      update_config:
        parallelism:
```

- `mode`: 
	- `global` いわゆるサイドカーと言うやつです。
	- `replicated` 普通のクラスタ内のノードにデプロイされるやつ。指定しないとコレ
- `placement`
	- `constraints` [docker service create](https://docs.docker.com/engine/reference/commandline/service_create/#specify-service-constraints-constraint)の時に指定できるタスクを実行させるノードを制限するオプション
		- workerノードでだけ動かしたいとか、ノードのラベル別に実行するタスクを分けたいとかの時に使うのでわりと使います
- `update_config` docker service createとかで指定するオプションと同じです。
	- `parallelism` デプロイ済みのスタックをUpdateするときに同時に何個ずつ実行するか


その他、`restart_policy`とか`resources`とか`label`があります。

### デプロイする

```
$ docker stack deploy redmine --compose-file docker-compose.yml
```

これでいけます。
実行してしばらくしたら全部起動すると思うので、確認してみましょう。
`redmine`がスタック名です。

#### stack list

```
$ docker stack ls 
NAME     SERVICES
redmine  4
```

#### service list

```
$ docker service list

ID            NAME                      MODE        REPLICAS  IMAGE
0hvtsyxf9w0u  redmine_db                replicated  1/1       mariadb:latest
5ms7e2obo0rt  redmine_logger            global      4/4       fluent/fluentd:latest
fqqumdfkrhmn  redmine_portainer_master  global      1/1       portainer/portainer:latest
uem99z2hr2km  redmine_redmine           replicated  2/2       redmine:latest
```

こっちのコマンドでも結果は同じです。

```
$ docker stack services redmine 
ID            NAME                      MODE        REPLICAS  IMAGE
0hvtsyxf9w0u  redmine_db                replicated  1/1       mariadb:latest
5ms7e2obo0rt  redmine_logger            global      4/4       fluent/fluentd:latest
fqqumdfkrhmn  redmine_portainer_master  global      1/1       portainer/portainer:latest
uem99z2hr2km  redmine_redmine           replicated  2/2       redmine:latest
```

#### stack ps

ここまで来るとどのノードでどのコンテナが動いてるのか気になるのが人情ってもんです。

```
docker stack ps redmine
```

これだとUpdateとか失敗を繰り返してるとわけのわからないことになるのでフィルターをしましょう。

```
$ docker stack ps redmine --filter desired-state=Running
ID            NAME                                                IMAGE                       NODE                   DESIRED STATE  CURRENT STATE              ERROR  PORTS
aylrotce96e7  redmine_portainer_master.xkugj8va2mj7zvhy0h4405ken  portainer/portainer:latest  instance-group-1-1f68  Running        Running 49 minutes ago            
jf0v0z827qy3  redmine_logger.xkugj8va2mj7zvhy0h4405ken            fluent/fluentd:latest       instance-group-1-1f68  Running        Running about an hour ago         
iadivx4e7m7x  redmine_logger.nc1inf0awkwscvqt3s4plvjy6            fluent/fluentd:latest       instance-group-1-c6k3  Running        Running about an hour ago         
imata0rft454  redmine_logger.v9h0jiug1yxrm4jc2b2sxua7e            fluent/fluentd:latest       instance-group-1-x511  Running        Running about an hour ago         
kgyuz26ptxim  redmine_logger.l73tho1n0stdz5swgu76n3dlp            fluent/fluentd:latest       instance-group-1-vt0t  Running        Running about an hour ago         
w7idyw298gz3  redmine_redmine.1                                   redmine:latest              instance-group-1-vt0t  Running        Running about an hour ago         
kutu765y02xc  redmine_db.1                                        mariadb:latest              instance-group-1-c6k3  Running        Running about an hour ago         
u7xmk4ouxku8  redmine_redmine.2                                   redmine:latest              instance-group-1-vt0t  Running        Running about an hour ago 
```

これで稼働中のコンテナとどこのノードで動いてるのかが見れます。

#### Portainer

ちなみに、ブラウザで確認したい場合は、今回はportainerを動かしてるのでこちらでも見れます。

http://{manager node}:9000/

Swarmのノード一覧見たり

![スクリーンショット 2017-01-24 16.06.32.png](https://qiita-image-store.s3.amazonaws.com/0/8637/da2c04f8-6863-ea56-bde9-546f2bc6a777.png "スクリーンショット 2017-01-24 16.06.32.png")

サービスのリストを見たり、この画面でスケールさせたりできます。
![スクリーンショット 2017-01-24 16.40.24.png](https://qiita-image-store.s3.amazonaws.com/0/8637/c3572c2c-d08e-b6df-0f82-c60cb132f4d3.png "スクリーンショット 2017-01-24 16.40.24.png")

サービスの詳細も見れます。
Environment variablesとかLabelsとかはこの画面でもいじくれます。
Update Configの値もここでいじくれますね。
下の方にタスクのリストがあるんですが、Runningのやつだけ出したりフィルタできないのでちょっと不便。
![スクリーンショット 2017-01-24 16.44.03.png](https://qiita-image-store.s3.amazonaws.com/0/8637/315aa026-0a04-639d-7787-9d6097eddbfb.png "スクリーンショット 2017-01-24 16.44.03.png")


### サービスの内容を変更する

再度`stack deploy`すればUpdateされます。

## まとめ

そんなわけで、1.13からはComposeで定義してそれを使ってサクッとクラスタ上でコンテナを動かせるようになりました。
ワンタイムのコンテナバッチの実行とかはSwarm Modeが対応してないのでまだできませんが、たしか[Issues](https://github.com/docker/docker/issues/23880)あがってたと思うのでそのうちできるようになるかな、と思います。
やったね！