Dockerのswarmモードでクラスタリング環境作り

[第2のドワンゴ Advent Calendar 2016](http://qiita.com/advent-calendar/2016/dwango2)の9日目の記事です。

こっちのアドベントカレンダーで数少ない現役社員のビビるです。普段は主にニコニコ生放送周りでWebpackやTypeScriptとかを触るフロントエンドエンジニアをやっています。

最近気になっていたDockerとswarmを使って、クラスタリングと監視をやってみたいと思います。

### そもそもSwarmとは

Dockerのバージョン1.12で追加された、Docker Engine内蔵のクラスタ管理機能です。
[以前からあった Docker Swarm](https://docs.docker.com/swarm/overview/)とは別物で、`swarm mode`と呼ばれたりします。詳細は[公式ページ](https://docs.docker.com/engine/swarm/)を見て下さい。


## 今回やりたいこと

- DockerのサービスとしてWebサーバー、APIサーバーを建てる
- それらのログを収集して、よしなに表示してくれる環境を作る

## サーバーを準備する

Dockerのv1.12では、64-bitのLinuxのカーネルバージョン3.10以上が要求されます。その為、今回のサーバーOSはCentos 7系を選択しました。
丁度良いところに[Conoha VPS](https://www.conoha.jp/)に 64bit Centos 7.2のテンプレートが準備されているようなので、それを利用します。

今回は管理用のマネージャーインスタンスを1台、ワーカーとなるインスタンスを2台準備します。[準備フェーズのAnsible Playbook](https://github.com/viviljp/ansible-docker-initialize)

<img width="477" alt="サーバーが建った図" src="https://qiita-image-store.s3.amazonaws.com/0/44913/846996a7-31ab-3c18-daa9-0481f347856e.png">

今回使いませんでしたが、Conoha VPSには[プライベートネットワーク機能](https://www.conoha.jp/guide/privatenetwork.php)があり、とても便利です。実際に何かを作って本運用する際には、是非利用していきたいですね。

## クラスタリングを準備する

### Docker HubにイメージをPush
DockerHubに予め自分の動かしたいイメージをアップロードしておきます。プライベートなものは[Google Container Registry](https://cloud.google.com/container-registry/)などを利用するのも良いかと思います。今回はテスト用なので、単純に[APIサーバー](https://hub.docker.com/r/viviljp/test-api/)と[Webサーバー](https://hub.docker.com/r/viviljp/test-web/)の2種類のイメージをDocker Hubにアップロードしました。

### Dockerのswarmをセットアップする

#### マネージャーのセットアップ
swarmのマネージャー（管理インスタンス）となるサーバーを1台決めておき、そのインスタンスで以下のコマンドを打ちます。

```shell-session
docker swarm init --advertise-addr XXX.XXX.XXX.XXX
```
ここで表示されるトークンは後々使うので、コピーしておきます。

また、合わせてWebインスタンスとApiインスタンス間の通信をやるためのネットワークを作ります。今回はサービス用のネットワークと、状態監視用のネットワークの2つを作ります。サブネットは他のシステムに影響がないように注意して設定して下さい。

```shell-session
docker network create --driver overlay --subnet 10.0.2.0/24 service
docker network create --driver overlay --subnet 10.0.3.0/24 monitoring
```

#### ワーカーのセットアップ

同様にワーカーも設定します。先程マネージャーのセットアップで表示されたトークンを利用します

```shell-session
docker swarm join --token YYYYYYY XX.XX.XX.XX:2377
```

マネージャーに戻って、以下のコマンドで正常にクラスタに組み込まれているのを確認できます

```shell-session
docker node ls
```

<img width="842" alt="swarm" src="https://qiita-image-store.s3.amazonaws.com/0/44913/0cabd5ce-7409-1930-ee40-eb25775254a8.png">


### Api, Webのサービスを立ち上げる

```shell-session
docker service create --replicas 1 --env NODE_ENV=production --env PORT=80 --name api --network service --constraint 'node.role == worker' viviljp/test-api
docker service create --replicas 1 --env NODE_ENV=production --env PORT=3000 --publish 3000:3000  --name web --network service --constraint 'node.role == worker' viviljp/test-web
```

暫くすると、サービスが立ち上がっているのがわかります

```shell-session
docker service ls 
```

<img width="604" alt="sevice" src="https://qiita-image-store.s3.amazonaws.com/0/44913/c6c92a26-9eb8-c6cc-8e0b-3046df085009.png">


### スケーリング
折角クラスタを組んだので、スケーリングも試してみます。
マネージャーで以下のコマンドを打つことで、スケーリングを指示できます。

```shell-session
docker service scale web=3
```
<img width="619" alt="3台に増やした" src="https://qiita-image-store.s3.amazonaws.com/0/44913/934a0ea9-4637-d6a7-78f5-4c9f3a6b39c9.png">

どのマシンに何台インスタンスが割当たっているかは以下のコマンドで調べられます。

<img width="709" alt="ノードの割当" src="https://qiita-image-store.s3.amazonaws.com/0/44913/fd6300c3-81a7-e9ef-d131-73e097f12248.png">

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

> 既に死んだインスタンスもShutdown状態で表示されています。ここでは（意図的にweb2サーバーを2回止めたので）Web.1のインスタンスが2回再起動しているのが見て取れます。


dockerのswarmには[Ingressネットワーク](https://docs.docker.com/engine/swarm/ingress/)という内部オーバーレイネットワークがあり、一つのサーバーにアクセスしても自動的に内部ルーティングをしてくれる便利な機能が備わっています。（今回の構成のうちweb, apiインスタンスが接続している `service` もオーバーレイネットワークです。）

その為、複数のインスタンスを建てておいても勝手に負荷分散をやってくれます。便利な時代になったなあと思います。

## 監視をする

サービスを建てただけじゃ、ちゃんと動いているか不安になりますよね。ここでは各コンテナに対する監視もやっていきたいと思います。

### 各ノードにリソース確認用のツールを設定します
リソース監視ツールには、[cAdvisor](https://github.com/google/cadvisor)を利用しました。

```shell-session
docker service create --network=monitoring --mode global --name cadvisor \
  --mount type=bind,source=/,target=/rootfs,readonly=true \
  --mount type=bind,source=/var/run,target=/var/run,readonly=false \
  --mount type=bind,source=/sys,target=/sys,readonly=true \
  --mount type=bind,source=/var/lib/docker/,target=/var/lib/docker,readonly=true \
  google/cadvisor
```

この場合、各ノードに1台ずつ立っていて欲しいので、[グローバルモード](https://docs.docker.com/engine/reference/commandline/service_create/)で建てています。スケールをしなくなる代わりに、必ず1台1インスタンスずつ配置されるモードです。


これだけでも監視は可能なのですが、各サーバーに入ってチェックするのは面倒なので、一箇所でまとめて監視しておきたいですよね。


### マネージャーノードに監視用ツールを設定する
リソース監視ツールを監視するためのツールをマネージャーに建てます。

今回は [Prometheus](prometheus.io)を使ってみます。

```shell-session
docker service create --publish 9090:9090 --network=monitoring --name prometheus \
  --mount type=bind,source=/tmp/prometheus.yml,target=/etc/prometheus/prometheus.yml,readonly=false \
  --constraint 'node.role == manager' prom/prometheus
```

```prometheus.yml
global:
  external_labels:
      monitor: 'service'
scrape_configs:
  - job_name: 'docker'
    scrape_interval: 5s
    dns_sd_configs:
      - names: ['tasks.cadvisor']
        type: 'A'
        port: 8080  
```

<img width="1270" alt="prometheus.png" src="https://qiita-image-store.s3.amazonaws.com/0/44913/6e526b79-cacb-e17b-f7f7-39a0189b2559.png">

このような感じでCPU使用率・メモリ使用率・ネットワーク状況など、様々な情報が取得できます。

### より見やすく可視化する
prometheusで取得した情報は1項目ずつ確認する形になるのですが、まとめて確認するために、[Grafana](http://grafana.org/)などのツールを使って可視化するのも良さそうです。

```shell-session
docker service create --constraint 'node.role == manager' --publish 3001:3000 grafana/grafana
```
> デフォルトでは3000番ポートを使うのですが、webインスタンスとポートが衝突してしまったので、ずらしました…。ちゃんと計画的にポートを振ればよかったと後悔。

Grafanaは適切な設定をすれば、デフォルトでPrometheusをソースとして利用できます。

<img width="590" alt="datasource" src="https://qiita-image-store.s3.amazonaws.com/0/44913/3192d75d-401c-290e-d906-c7706c517767.png">

> localhostと書いているところは、マネージャーインスタンスのIPになります

あとは、ダッシュボードの見た目を自分で好きなように調整すれば、下の画像のような自分が一番見やすい監視ツールを作ることができます。

<img width="1255" alt="dashboard.png" src="https://qiita-image-store.s3.amazonaws.com/0/44913/e5c34540-adf8-82a4-c54e-d14a5aebce70.png">

> dockerはサービス名指定しないと適当な名前をつけてくれます

GrafanaやPrometheusにはアラートを発する機能があるようなので、エラーログがいっぱい出たらSlackに通知！とかエンジニアなら誰でも欲しがるシステムを容易に構築できます。便利ですね。

他にも、elasticsearchとかfluentdを使ってログ収集などやってみるのも面白いかもしれません。

## おわりに

Dockerは[ストレージサービスのInfinitをちょうど買収した](https://blog.docker.com/2016/12/docker-acquires-infinit/)との発表もあり、今後ますます目に掛かる機会は増えると思います。フロントエンジニアだから触らない！ということなく、どんどん新しい分野に挑戦していきたいです。

明日は[D3vel0pper](http://qiita.com/D3vel0pper)さんです。