Docker swarm のクラスタを network overlay 付きで構築する

前回の[「Docker swarm のクラスタを手で構築してみる」](http://qiita.com/rerofumi/items/32ad59f4fa4a553055aa)という記事の続編となります。

# 記述時の環境

+ docker engine - 1.11
+ docker swarm - 1.2.0
+ consul - 0.6.0
+ host - Debian 8.4 
	+ docker engine は get.docker.com を使ってのインストール

# ベースとなる VM イメージ

クラウドで Docker コンテナを利用するために、予め環境を整えておいた Docker ホスト VM イメージを用意して使っています。swarm クラスタを作るために、この Docker ホストイメージからインスタンスを複数作成して swarm master と swarm node に仕上げます。

ベースイメージは以下の様な構成となっています。

+ Linux OS Debian 8.4
+ Docker engine
+ consul (ゾーン内に1台サーバーがいてそこにjoinすることで consul クラスタを構成しています)
+ docker engine
+ docker volume plugin
  + docker-volume-netshare
  + docker-voume-glusterfs

その他に docker-compose とかも入れておきます。

consul はサーバー名の名前解決が目的なのですが、これが今回のポイントになります。
consul に付いての過去記事は[「consul でローカルマシンの名前解決をしたい」](http://qiita.com/rerofumi/items/237c971ed100db6385d2)を参照してください。

volume plugin を利用することで docker のボリューム問題を NFS/GlusterFS といったネットワークファイルシステムで解決することができます。swarm の場合、どのノードでコンテナが実行されるか分からないので、ボリュームの永続化が重要な要素となります。
volume plugin に付いての過去記事は[「Docker の Volume plugin で NFS や Glusterfs を利用する」](http://qiita.com/rerofumi/items/579571c1453e7e6f2bd8)を参照してください。

クラスタリングというともうひとつネットワーク透過性が重要になりますが、これは今回 Docker のオーバーレイネットワークを設定することで解決されます。


# 構成

前回の記事で書いた様に、docker swarm を構築するには master を含む各 swarm コンテナからアクセスできる共通の KVS を用意します。前回はその KVS として consul を使っていました。今回も KVS に consul を使いますが、若干使いかたが変わります。

![DockerSwarm構成図.png](https://qiita-image-store.s3.amazonaws.com/0/50737/9dbbf92d-bd89-f8d8-b9c2-2171f5094d04.png)

swarm 同士のクラスタリング用の KVS ですが、これは各マシン上の Docker コンテナの中からアクセスできる IP アドレス上に設置する必要があります。これが案外面倒でして、今回悩んだ結果 swarm master 上に consul コンテナを動作させ master のホストにポートフォワードにて公開することにしました。各 swarm からはこの consul kvs のポートとアドレスでアクセスさせることにします。

もうひとつオーバーレイネットワークのクラスタ構成にも KVS を使うのですが、これは各ホストの docker daemon が情報交換するためのものなので daemon つまりはホスト側のネットワークに要る必要があります。また、daemon が起動するときに KVS が存在していなければなりませんので先の swarm 用 consul kvs コンテナでは(まだ起動しておらず)都合が悪かったりします。
ということで swarm 用 KVS とは分けてもうひとつ consul kvs を用意してやることにしました。
幸い用意していた base system image に DNS 用 consul が入っているのでこれを使用します。localhost の consul にアクセスするように設定すれば完了です。consul がクラスタを構成しているという前提が必要なので、consul サーバーがいてそこに join しているものとします。

swarm 用 KVS もこのシステム側にできないか悩んだのですが、ある程度ネットワークを解放してセキュリティ的に緩めないといけないので諦めました。


# swarm master の設定
master 側では consul のコンテナと、swarm のコンテナの2つを実行します。
docker のリモートアクセスする port 2357 を swarm master 側に割り当てたい場合は docker daemon でポート開放をしないでおきます。通常、インストール直後は指定してないとはずなのでなにもしないで大丈夫です。
port をずらすのであれば気にすることはありません。

2つのコンテナを実行するための docker-compose ファイルは以下の様になります。

```yaml:docker-compose.yml
consul-kvs:
  image: consul-kvs
  ports:
    - "8501:8500"

swarm-master:
  image: swarm
  command: manage consul://kvs:8500/docker-swarm
  links:
    - consul-kvs:kvs
  ports:
    - "2375:2375"
```
 
consul-kvs は consul を起動するためのコンテナです。
consul はバイナリで配布されていますので入手しそれがあるディレクトリで以下の Dockerfile にてイメージを作成します。

```Dockerfile:Dockerfile
FROM busybox

ADD consul /usr/local/sbin/consul
RUN mkdir -p /etc/consul.d
RUN mkdir -p /var/lib/consul

EXPOSE 8500
CMD ["/usr/local/sbin/consul","agent","-server","-bootstrap","-data-dir=/var/lib/consul","-client=0.0.0.0","-dc","docker-swarm1"]
```

swarm は起動コマンドラインで kvs 位置を指定します。
上記 docker-compose ファイルでは links で kvs へのヒントを与えて ```consul://kvs:8500/docker-swarm``` で kvs パスとしています。docker-swarm は kvs のプレフィクスですから任意で付けて下さい。

swarm master は上記の docker-compose.yml を使ってコンテナを2つ起動するだけです。


# swarm node の構築

node 側は swarm コンテナを1つ起動するだけなので docker-compose ファイルもシンプルです。

```yaml:docker-compose.yml
swarm-node:
  image: swarm
  command: join --addr=192.168.0.44:2375 consul://swarm-master.node.consul:8501/docker-swarm
  restart: always
```

起動オプションとして node 自身がもつ IP アドレスと swarm-master の KVS アドレスを指定する必要があります。
ここでは swarm-master.node.consul:8501 と指定していますが、consul による DNS 解決をしていない場合は swarm-master のIPアドレスを直接指定することになります。
192.168.0.44 とIPアドレスになっているところは仮置きで後で差し替えるので無視してください。

また、オーバーレイネットワークのアドレスアドバタイズで自身のIPアドレスを指定したり、docker daemon が master からアクセスできるようにネットワーク指定する必要があります。
この設定は docker engine の起動用 systemd ファイルに書き込みます。

```
/usr/bin/docker daemon -H fd:// -H tcp://0.0.0.0:2375 --cluster-store=consul://localhost:8500/docker-overlay --cluster-advertise=(自分のIPアドレス):2375'
```

swarm master からアクセスできるように ```tcp://0.0.0.0:2375``` を追加して、```--cluster-store``` でオーバーレイネットワーク構築用の kvs を指定、```--cluster-advertise``` で自分のIPアドレスを指定してクラスタに参加します。

これら設定を自動化するためにシェルスクリプトにまとめた物が以下のものになります。

```bash:docker-daemon.sh
#!/bin/bash

IPADDR=`ip addr show eth0 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1 | tr -d '\n'`
sed -i '/^ExecStart/c ExecStart=\/usr\/bin\/docker daemon -H fd:\/\/ -H tcp:\/\/0.0.0.0:2375 --cluster-store=consul:\/\/localhost:8500\/docker-overlay --cluster-advertise='$IPADDR':2375' /lib/systemd/system/docker.service
systemctl daemon-reload
systemctl restart docker.service

sed -i 's/[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+/'$IPADDR'/g' docker-compose.yml
docker-compose pull
docker-compose up -d
```

docker daemon  の systemd ファイルを書き換えて再実行、その後 docker-compose の 192.168.0.44 となっている部分を自身のIPアドレスに置き換えて up します。(Debian8 の場合)
このシェルスクリプトを実行すると docker-compose の実行も行いますので、先ほどの docker-compose.yml と一緒に置いておきます。

# VM環境での swarm クラスタ構築
実際にVM上にswarmクラスタを構築する場合、上記の docker-compose ファイルやシェルスクリプトを転送して実行するだけではあるのですがその前に幾つか設定を行う必要があります。

1. docker 環境を整えた base イメージから VM インスタンスを起動
2. /etc/hostname を書き換えてホスト名を指定(consulで名前がぶつからないように)
3. /etc/docker/key.json を削除(node間で異なるkeyが必要なため、次回起動時に新しいものが生成されて設定されます)
4. 再起動
5. スクリプトを転送
6. 実行

これで swarm-master/node が構築できるようになりました。

# オーバーレイネットワークの作成
オーバーレイネットワークを作成すると、コンテナがどの swarm-node で動作していたとしても共通のネットワークを持たせることが可能になります。
具体的には - link で結合する際、node マシンが分かれていても疎通できる様になります。

オーバーレイネットワークの作成は swarm-master に対し network create することになります。

```bash
docker -H swarm-maser.node.consul:2357 network create -d overlay (new-overlay-name)
```

コンテナの起動時に --net (new-overlay-name) を指定するとそのオーバーレイネットワークをアタッチします。

# docker クラスタリングを楽しみましょう

swarm クラスタの操作には master へリモートアクセスします。
```docker -H swarm-maser.node.consul:2357 ``` でホスト指定しますが、良く使うので bashrc に alias 書いて登録するなどしましょう。
これで swarm クラスタが構築できました。
WEBサーバーの様に起動しっぱなしのサーバー用途よりも cron 処理やビルドといったワンショット処理をコンテナプールに投げ込んで実行、分散化させるといった方が楽しいです。

皆さんもぜひコンテナ分散化を楽しんでください。