Swarm と組み合わせて Compose(Fig) をマルチホスト環境で使う


## 概要
Swarm と組み合わせることで、Compose(Fig) がマルチホスト環境下で使えるようになるという話。

Sinatra コンテナと Redis コンテナからなるアプリケーションを複数の Docker ホストにまたがって配置する。

## マルチホスト環境での Compose
### Swarm クラスタの構築

* [Docker Swarm リファレンス](http://qiita.com/spesnova/items/f4b5f7ee57c9271c9dbd)
* [Docker Swarm を使ってマルチホスト環境下で Hello World コンテナを動かす](http://qiita.com/spesnova/items/47fa9ba3e8aa3721384c)

を参考に　3 台の Docker ホストを用意する。

```bash
$ docker -H core-01:2377 info
Containers: 4
Nodes: 3
 core-01: core-01:2375
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 998 MiB
 core-03: core-03:2375
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 998 MiB
 core-02: core-02:2375
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 998 MiB
```

それぞれ `2375` ポートから Docker Remote API が使える状態。

* core-01: `tcp://core-01:2375`
* core-02: `tcp://core-02:2375`
* core-03: `tcp://core-03:2375`


```bash
$ docker -H core-01:2377 ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS                         NAMES
8a6d378fd434        swarm:latest        "/swarm join --addr=   7 seconds ago        Up About a minute   2375/tcp                      core-02/swarm-agent-core-02
0cd3fbda1f31        swarm:latest        "/swarm join --addr=   About a minute ago   Up About a minute   2375/tcp                      core-03/swarm-agent-core-03
62b93d0e9f7c        swarm:latest        "/swarm join --addr=   7 minutes ago        Up 7 minutes        2375/tcp                      core-01/swarm-agent-core-01
df7b39a2eeba        swarm:latest        "/swarm manage --tls   8 minutes ago        Up 8 minutes        172.17.8.101:2377->2375/tcp   core-01/swarm-manager
```

Swarm マネージャは core-01 に立っていて、`core-01:2377` がエンドポイント。（だから `$ docker -H core-01:2377` としている）
Swarm エージェントは各ノードにそれぞれいる。

* core-01
 * swarm manager
 * swarm agent 01
* core-02
 * swarm agent 02
* core-03
 * swarm agent 03


### Compose をシングルホスト環境で使う
まずはシングルホスト環境下で Compose を使ってみる。


* [Docker Compose リファレンス](http://qiita.com/spesnova/items/6e8919b34e763f27c34e)

を参考に compose をインストールし、

* [Docker Compose を使って Sinatra と Redis コンテナを 1 コマンドで立ち上げる](http://qiita.com/spesnova/items/40b49b9faa5faa1f98bf)

で使った構成をベースに使う

```yaml
# docker-compose.yml
web:
  image: quay.io/spesnova/sinatra-hello-world
  command: bundle exec ruby app.rb
  ports:
    - 4567
  links:
    - redis
  environment:
    - REDIS_HOST=redis
    - REDIS_PORT=6379
redis:
  image: redis:latest
```

ポイント 3 つ

* コンテナのスケールをする際のポートの衝突を避けるために `web` サービスの `ports` は `80:4567` から `4567` に変える
* どの Docker ホストからでもイメージが取得できるよう `build` の代わりに `image` を指定する（イメージも push しておく）
* 開発時は手元のコードをコンテナと共有したいので VOLUME を使っていたが、今回はできたイメージを使うので `web` サービスコンテナの `volumes` を外す


起動してみる

```bash
# at core-01
$ cd sample
$ docker-compose up -d
Creating sample_redis_1...
Creating sample_web_1...
```

こんな状態

```bash
$ docker-compose ps
     Name                  Command             State              Ports
-----------------------------------------------------------------------------------
sample_redis_1   /entrypoint.sh redis-server   Up      6379/tcp
sample_web_1     bundle exec ruby app.rb       Up      172.17.8.101:49153->4567/tcp
```

スケールしてみる

```bash
$ docker-compose scale web=2
Creating sample_web_2...
Starting sample_web_2...

$ docker-compose ps
     Name                  Command             State            Ports
------------------------------------------------------------------------------
sample_redis_1   /entrypoint.sh redis-server   Up      6379/tcp
sample_web_1     bundle exec ruby app.rb       Up      0.0.0.0:49155->4567/tcp
sample_web_2     bundle exec ruby app.rb       Up      0.0.0.0:49156->4567/tcp
```

当たり前だけど core-01 に `web` サービスコンテナが 2 つ起動している。

一旦コンテナは停止、削除しておく

```bash
$ docker-compose kill && docker-compose rm
Killing sample_web_2...
Killing sample_web_1...
Killing sample_redis_1...
Going to remove sample_web_2, sample_web_1, sample_redis_1
Are you sure? [yN] y
Removing sample_redis_1...
Removing sample_web_2...
Removing sample_web_1...
```

### Swarm を利用して Compose(Fig) をマルチホスト環境で使う
Swarm API は既存の Docker API と互換性があるように作られているので、core-01 の Docker API の代わりに Swarm API を使うことができる。
Swarm API を使うと、たとえば、`docker run busybox echo hello world` するとマルチホストのどこかで busybox コンテナが実行される。同じように Compose から使えば、 Sinatra コンテナ＋ Redis コンテナの組み合わせがマルチホストのどこかで起動するということ。

図にするとこういうこと

```bash
# シングルホスト環境での Compose の利用
Compose ---> Docker Remote API (Docker Host, core-01)


# マルチホスト環境での Compose の利用
                           ---> Docker Remote API(Docker Host, core-01)
                          /
Compose ---> Swarm API --- ---> Docker Remote API(Docker Host, core-02)

                          \---> Docker Remote API(Docker Host, core-03)
```

### Swarm 用に docker-compose.yml の編集

デフォルトで Swarm API を使うように設定する

```bash
$ export DOCKER_HOST=tcp://core-01:2377
```

さらに `docker-compose.yml` をいじる

```yaml
web:
  image: quay.io/spesnova/sinatra-hello-world
  command: bundle exec ruby app.rb
  ports:
    - 4567
  environment:
    - REDIS_HOST=172.17.8.101 # 追加
    - REDIS_PORT=6379
    - affinity:container!=sample_web_* # 追加
redis:
  image: redis:latest
  ports:
    - 6379:6379 # 追加
  environment:
    - constraint:node==core-01 # 追加
```

マルチホストで `web` サービスコンテナを動かす場合 LINK で連携はできないので、

* `web` サービスコンテナから `link` を外す
* `web` サービスコンテナの Redis ホストを `core-01` に向ける
* `redis` サービスコンテナのポートを外部にさらす
* `redis` コンテナが core-01 で動くように Swarm 用に `contraint` を設定

さらに `web` サービスコンテナ同士は別々の Docker ホストで起動するような設定を付けた。`affinity:container!=sample_web_*` は `sample_web_*` という名前のコンテナがいたらそこでは `web` サービスコンテナは立てないという条件。


### Swarm を使ってスケール

`docker-compose up -d` してみる

```bash
$ docker-compose up -d
Creating sample_redis_1...
Creating sample_web_1...
```

`docker-compose ps` と `docker ps` してみる

```bash
$ docker-compose ps
     Name                  Command             State             Ports
--------------------------------------------------------------------------------
sample_redis_1   /entrypoint.sh redis-server   Up      6379/tcp
sample_web_1     bundle exec ruby app.rb       Up      172.17.8.101:80->4567/tcp

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                  PORTS                       NAMES
0fa8b600d765        redis:latest        "/entrypoint.sh redi   9 seconds ago       Up Less than a second   6379/tcp                    core-01/sample_redis_1
599a99f51659        sample_web:latest   "bundle exec ruby ap   9 seconds ago       Up Less than a second   172.17.8.101:80->4567/tcp   core-01/sample_web_1
```

docker-compose コマンドを実行しているホストと同じ core-01 でコンテナが立ったもよう。ここまでは普通。

`web` サービスコンテナをスケールしてみる

```bash
$ docker-compose scale web=2
Creating sample_web_2...
Starting sample_web_2...
```

`web_2` が core-03 に立った

```bash
$ docker ps
CONTAINER ID        IMAGE                                         COMMAND                CREATED             STATUS                  PORTS                          NAMES
23d5276ef844        quay.io/spesnova/sinatra-hello-world:latest   "bundle exec ruby ap   3 seconds ago       Up Less than a second   172.17.8.103:49155->4567/tcp   core-03/sample_web_2
f94b9cf27ba3        redis:latest                                  "/entrypoint.sh redi   43 seconds ago      Up 16 seconds           172.17.8.101:6379->6379/tcp    core-01/sample_redis_1
8469bdd81f5a        quay.io/spesnova/sinatra-hello-world:latest   "bundle exec ruby ap   43 seconds ago      Up 17 seconds           172.17.8.101:49165->4567/tcp   core-01/sample_web_1

$ docker-compose ps
     Name                  Command             State              Ports
-----------------------------------------------------------------------------------
sample_redis_1   /entrypoint.sh redis-server   Up      172.17.8.101:6379->6379/tcp
sample_web_1     bundle exec ruby app.rb       Up      172.17.8.101:49165->4567/tcp
sample_web_2     bundle exec ruby app.rb       Up      172.17.8.103:49155->4567/tcp
```

* http://172.17.8.101:49165/ 
* http://172.17.8.103:49155/ 

で Hello World 及びアクセス数のインクリメントができる

さらにスケールする

```bash
$ docker-compose scale web=3
Creating sample_web_3...
Starting sample_web_3...
```

今度は core-02 に立った

```bash
$ docker ps
CONTAINER ID        IMAGE                                         COMMAND                CREATED                  STATUS                  PORTS                          NAMES
737f31bf1d0a        quay.io/spesnova/sinatra-hello-world:latest   "bundle exec ruby ap   Less than a second ago   Up Less than a second   172.17.8.102:49153->4567/tcp   core-02/sample_web_3
23d5276ef844        quay.io/spesnova/sinatra-hello-world:latest   "bundle exec ruby ap   2 minutes ago            Up 2 minutes            172.17.8.103:49155->4567/tcp   core-03/sample_web_2
f94b9cf27ba3        redis:latest                                  "/entrypoint.sh redi   3 minutes ago            Up 2 minutes            172.17.8.101:6379->6379/tcp    core-01/sample_redis_1
8469bdd81f5a        quay.io/spesnova/sinatra-hello-world:latest   "bundle exec ruby ap   3 minutes ago            Up 2 minutes            172.17.8.101:49165->4567/tcp   core-01/sample_web_1

$ docker-compose ps
     Name                  Command             State              Ports
-----------------------------------------------------------------------------------
sample_redis_1   /entrypoint.sh redis-server   Up      172.17.8.101:6379->6379/tcp
sample_web_1     bundle exec ruby app.rb       Up      172.17.8.101:49165->4567/tcp
sample_web_2     bundle exec ruby app.rb       Up      172.17.8.103:49155->4567/tcp
sample_web_3     bundle exec ruby app.rb       Up      172.17.8.102:49153->4567/tcp
```

![172_17_8_103_49155_と_172_17_8_102_49153_と_172_17_8_101_49165_と_Compose_yml_-_Docker_Documentation.png](https://qiita-image-store.s3.amazonaws.com/0/20833/50daa49b-2cd4-0ffa-5d4d-4f24a6235466.png "172_17_8_103_49155_と_172_17_8_102_49153_と_172_17_8_101_49165_と_Compose_yml_-_Docker_Documentation.png")


さらにスケールすると

```bash
$ docker-compose scale web=4
Creating sample_web_4...
unable to find a node that satisfies container!=sample_web_*
```

`web` コンテナがいない Docker ホストが見つからないので立てられないよというメッセージ。`affinity` がちゃんと動いている

マルチホスト環境でもまとめてログが見られる

```bash
$ docker-compose logs
```

![1__core_core-01___sample__ssh_.png](https://qiita-image-store.s3.amazonaws.com/0/20833/a241671f-ba1b-56a8-243c-78b3fec35205.png "1__core_core-01___sample__ssh_.png")


## REF

* [Docker Swarm リファレンス](http://qiita.com/spesnova/items/f4b5f7ee57c9271c9dbd)
* [Docker Compose リファレンス](http://qiita.com/spesnova/items/6e8919b34e763f27c34e)