Docker swarm のクラスタを手で構築してみる

そろそろ docker swarm を使ってのマシンプール(クラスタリング)作成を試してみようかとチャレンジしてみました。

世間にある記事を見ると docker machine でクラスタ作成して swarm も machine 経由で利用する例が多いのですが、敢えて machine を使わず直接 swarm の構築を行いたいというのが今回の目的となります。既に docker engine が稼働済みの環境を数台用意していて、そこに swarm を仕込む事で 1つの docker pool にします。

docker swarm の使い方については下記の記事が良くまとまっていました。

「Docker Swarmでクラスタを構築する」
http://qiita.com/atsaki/items/16c50dfd579a9339c333

それでも自分で試してみたら躓いた所とか、consul を使ったらどうなるのかといった差違がありましたので主にそれを書き残していきたいと思います。

# 記述時の環境

* docker engine - 1.9.1
* docker swarm - 1.0.1
* consul - 0.5.2 (0.6.0 混在)
* host - Debian 8.2
 * docker engine は get.docker.com を使ってのインストール

## 構成要求

discovery backend として consul を使用します。

swarm の discovery において ```swarm create``` で生成したトークンを ```token://(トークン)``` と指定する場面が良く見られますが、これはトークンをキーとして DockerHub にノード情報のストアと取得を行うものです。
ノード情報を一旦外部に出すことになるのですが、DockerHub というどこからでもアクセスできるエントリポイントを使うことで情報共有が行える仕組みになっています。

discovery backend としては etcd, consul, zookeeper といった分散KVSが別に指定できるので、今回はローカルにインストールしてあり、すでにクラスタ化している consul を選択しました。

# 基礎知識

## docker のリモートAPI

docker の CLI は ```docker``` コマンドをつかってイメージやコンテナを操作しますが、通常はコマンドと同じマシンで docker 環境が動作しています。
正確には docker daemon がシステムで動作しており、CLI からはその daemon を操作することでコンテナの実行やイメージの操作を行っています。

![2015-12-29_044239.png](https://qiita-image-store.s3.amazonaws.com/0/50737/07adb40b-c076-f442-799a-0ca981ac4543.png)

こういった構成になっているため、docker daemon があるマシンとは別のマシンからリモートで daemon を操作して docker を利用することもできます。これがリモートAPIです。Windows や mac の docker toolbox はこのリモートAPIを利用して VirtualBox 上の Linux ＆ docker daemon にアクセスしています。

通常、インストール直後の docker daemon はローカルマシンからのリクエストのみ受け付けるように設定してあり外部からのリモート操作はできなくしてあると思います。
リモートAPI を有効にするためには、docker daemon の起動オプションを変更して再起動してやる必要があります。

## swarm

docker swarm はリモートAPIが有効になっている docker engine インストール済みのマシン(ノードと呼びます)を swarm master と呼ばれるマシンが管理し、リモート操作することでひとかたまりの大きな docker 実行マシンとして利用できる仕組みです。
docker 操作コマンドは swarm master が一元的に受け付けるので、ノードが何台に増えても操作するのは一カ所で済みます。また、どのノードで動作するというのも基本自動的なのでコンテナの実行を命令すると、ノードのどこかで実行されます。もちろんノードの指定もできます。

![2015-12-29_050611.png](https://qiita-image-store.s3.amazonaws.com/0/50737/bb4c375a-c640-557a-6f11-36aa812a7e1c.png)

docker swarm 自体は docker container なので、ホストと各ノードの docker 環境の上で実行します。



# 設定と実行

マスターとノード、どっちから準備しても構わない様です。
通常の例だとノードの設定から行う記事が多いですが、そっちのほうがわかりやすいのでノードからの方が良いでしょう。起動順は特に気にしなくても大丈夫です。

## 設定例におけるシステム構成

ここでの各master と node の IP adress は以下の様になっていると思って読んで下さい。

* master - 192.168.0.38
* node1 - 192.168.0.39
* node2 - 192.168.0.40

## ノードの設定

ノードはマスターからリモートAPIでフル操作されるため、docker daemon が少なくともマスターからのAPI操作を受け付ける様にしなくてはいけません。
daemon 起動時のオプションスイッチを指定するのですが、Debian8 だと systemd なので起動時の設定は ```/liv/systemd/system/docker.service``` にあります。get.docker.com からインストールしたパッケージでは残念ながら ```/etc/default/docker``` を全く見ていなかったので docker.service を直接編集する必要があります。

```
ExecStart=/usr/bin/docker daemon -H fd://
   ↓ 以下の様に修正
ExecStart=/usr/bin/docker daemon -H fd:// -H tcp://0.0.0.0:2375
```

systemd ファイルを変更したので systemd の daemon を再起動してから docker.service を再起動します。

```
# systemctl daemon-reload
# systemctl restart docker.service
```

swarm のイメージを ```docker pull swarm``` で pull してから起動します。
ノード側は以下の様な起動コマンドになります。

```console
# docker run -d swarm join --addr=192.168.0.39:2375 consul://192.168.0.39:8500/docker-swarm
```

```--addr``` は **ノード自身** のIPアドレスと docker リモートAPI ポートを指定します。
「私のAPIはここですよ、ここにアクセスして下さい」と master に伝えるための項目となります。

後ろの ```consul://～``` は discovery backend の設定です、swarm 用に使う KVS へのアクセス経路を指定します。consul の場合は KV にアクセスする HTTP API ポートを指定します。

```
consul://(consul の HTTP API アドレスとポート)/(KVのトッププレフィックス、識別子)
```

テスト時の環境では consul は各マシンにインストールしてクラスタ構築済みだったので、ローカルな consul にアクセスしています。

ここで問題が発生しました。
これまで consul はローカルアドレスからしかアクセスできないように設定していました。consul が利用できるとノードの全マシンがリモート操作できるといった危険性があるためです。ですが、docker コンテナとして動作する swarm はブリッジ上にいますので 127.0.0.1 以外からのアクセスとなります。
consul のアクセス制限を緩めて docker 上の swarm から利用できるように設定する必要があります。

discovery backend は単なる KVS として使っているので、外部に 1つ KVS サーバーとしてたててしまってそこに向かうようにしても動作します。ですが、ここまでの「master や他のノード情報を設定せず全く知らなくても動作する」というメリットがオミットされてしまうので悩ましいところです。


## マスターの設定

マスター側の docker daemon はリモート操作されることは無いのでリモートAPIの設定をする必要はありません。
もちろん、マスターとなるマシンに swarm master と node の両方を設定してノードとして利用することもできるので、その時はリモートAPIを設定する必要があります。

swarm のイメージを ```docker pull swarm``` で pull してから起動します。
マスター側は以下の様な起動コマンドになります。

```console
# docker run -d -p 2377:2375 swarm manage consul://192.168.0.38:8500/docker-swarm
```

主な設定値は 2カ所で、swarm へアクセスするポートのバインドと、consul 等の discovery backends へのアクセス指定です。consul へのアクセスと注意点はノードと同じなので省略します。

```-p 2377:2375``` は swarm コンテナへのアクセスポートです、2375 がコンテナ内のポートで、これをマスターマシンの 2377 に forward しているという表記になります。マスター側の docker daemon でリモートAPIを無効にしているときは 2375 が空いているのでそこに割り振っても構いません。通常のリモートAPIと同じポートになるので利用しやすくなるでしょう。
もし、マスターマシンをもノードにしたいのであれば docker daemon に 2375 を振りますのでずらす必要があるというだけのお話です。

ノード側のリモートAPIポートを 2375 以外に変えるというのも良い手かもしれません。


## 動作確認

これで docker swarm によるクラスタ構築が完了しました。

どこか適当なマシンからリモートで swarm にアクセスしてみましょう。

```console
$ docker -H 192.168.0.38:2377 info
```

現在の状態としてノード情報が表示されていたら成功です。

swarm 経由で KV のノード情報を確認することができます。

```console
$ docker run --rm swarm list consul://192.168.0.38:8500/docker-swarm
```

もっとも consul 等を使っている場合は、その KV に直接アクセスしてしまった方が早いかもしれません。

```
curl http://localhost:8500/v1/kv/?recurse
```

# ボリュームはプラグインで解決

swarm のようなクラスタ構成ではノード間のボリューム共有をどうするかが問題となります。
コンテナはノードのどこで実行されるかわかりませんから、保存したいデータが各ノードのローカルストレージに保存してしまうと他のノードとの間で一貫性が保たれません。

docker ではそういった問題を解決するためにボリュームプラグインを採用しました。
前回の記事をご参照ください。
http://qiita.com/rerofumi/items/579571c1453e7e6f2bd8

ネットワークストレージをボリュームプラグインでボリュームとしてマウントするので、ローカルストレージに依存せずノードで透過性を持たせることができるようになります。

このようなボリュームプラグインがそのまま swarm クラスタでも利用することができました。
すべてのノードにボリュームプラグインをインストールして同じように動作する様に準備は必要ですが、ボリューム問題が解決したことはかなり大きなことではないかと思います。


# ノードが１つしか登録されないぞ？

割と躓きました。
一見動作しているように見えるのですがマスターにノードが１つしか登録されていないという状況に陥りました。

master のコンソールログを確認したところ原因が分かりました。docker daemon の KEY が confrict しているというものです。
VMベースで環境構築していたので、元となるイメージのコピーで各ノードを作成していました。これが原因で ```/etc/docker/key.json``` もコピーされて同じ鍵の daemon となっていたのです。
マシン内完結のスタンドアロンで使っている分には問題がないので気がつきませんでした。

docker daemon を停止して ```/etc/docker/key.json``` を削除後、docker daemon を再起動することで新しい鍵が作成されます。

```console
# systemctl stop docker.service
# rm /etc/docker/key.json
# systemctl start docker.service
```

これでばっちり。

# セキュリティが心配だ

今回は docker swarm の勉強ということで一通り動かすところまでやってみました。
それ故セキュリティ的な部分は置き去りになっています。

具体的にはノードのリモートAPIが、どこからでもアクセスOKなままになっています。
```-H tcp://0.0.0.0:2375``` の部分ですね。
アクセス可能なアドレスを絞るか、TLS鍵認証を付けるべきだとは思います。鍵認証を各ノードに施工してまわるのはちょっと大変そうですね。

いつか対応したいので参考文献メモ

"Protect the Docker daemon socket"
https://docs.docker.com/engine/articles/https/

"Create a swarm for development" 一番下にある TLS の項
https://docs.docker.com/swarm/install-manual/


あと consul の API アクセス制限も適度な設定ができなくてやはり ```0.0.0.0``` で誤魔化してしまっています。
これも解決せねば。


# おわりに

普通は docker machine を使う物で、swarm はパーツなのかなあとは思います。
ですが、自分の手でシステム構築しているところに組み込むとなると machine が使いにくい場面もあったりするので、swarm を直接触ることに慣れておくに越したことはないとも思っています。

取りあえず手元の環境で docker リソースプールが作れたので色々使い倒してみたいと思います。