Consulを使ってDockerの名前解決を簡単に実現する

Dockerでサービス群をまとめて立ち上げる時はIPは毎回変わっちゃうので、名前解決が重要になるわけですが、これが結構面倒。
元々、figを使ってたので、linksに書いてればその名前でhost名を書き換えてくれてたので重宝してたんですが、これ起動順も制御してるっぽいので、双方向通信にすると循環参照でエラーに成って起動しません＞＜

なので、手軽にやる方法を色々調べてみたのですが、[Consul](https://consul.io/)を使うのが簡単そうなので、そちらを試してみました。

追記：
CentOS7で動かす環境も作ってみたので、読むのメンドいから、取りあえず動かしたいって人は[こっち](http://koduki.hatenablog.com/entry/2014/12/07/130104)をどうぞ。

Consulって何？
---------------

まず、そもそもConsulって何？　ってとこなのですが、

- Service Discovery
- Health Check
- Key/Value Store

を提供してくれるオーケストラレーションの一種です。私もまだしっかりとは理解できてないのですが、今回この"Service Discovery"が最初からDNSインタフェースを提供しているということで採用しました。

コンテナ上のresolved.confの参照を変更
-------------------------------------------------------------------

Consulを準備する前に、Dockerコンテナ上の名前解決の設定を変更します。これを変更することで、後述する方法で、docekr0にバインドしたDNSをすべてのDockerコンテナから利用出来るようになります。
Linux(RH7系)とMacOSでboot2dockerを使った場合で、設定方法に差分があるので両方記載。
ちなみに、RH7系だといくつかのサイトで説明してある/etc/default/dockerじゃなくて、/etc/sysconfig/dockerを見るようなので要注意。
**※ どうもRH7だと、セキュリティの設定が違うのか、Docker0にforwardしたサーバを探せないようなのでRUNした時点でresolve.confを変更しないと動きません。この辺、詳しい方居ましたらアドバイス頂ければと...**

### Linux(CentOS 7)の場合

```bash
$ echo "OPTIONS=--selinux-enabled --dns 172.17.42.1 --dns `grep nameserver /etc/resolv.conf|head -1|awk '{print $2}'` --dns-search service.consul" > /etc/sysconfig/docker
$ 
$ # 上記設定を変更するために再起動
$ sudo systemctl stop docker
$ sudo systemctl start docker
```

### MacOSでboot2dockerを使ってる場合

```bash
$ boot2docker ssh sudo "ash -c \"echo EXTRA_ARGS=\'--dns 172.17.42.1 --dns $(scutil --dns | awk -F ': ' '/nameserver/{print $2}' | head -1) --dns-search service.consul\' > /var/lib/boot2docker/profile\""
$
$ # 上記設定を変更するために再起動
$ boot2docker stop    
$ boot2docker up
```

これでDockerコンテナのresolve.confにdocker0が追加されるようになる。

Consulサービスを起動
---------------------------

つづいて、Consulのサービスをクラスタとして起動する。
まずは、Consul組込み済みのコンテナを取得。

```[bash]
$ docker pull progrium/consul
```

取得が終わったら、1台目のノードを立ち上げ。管理用ポート(HTTP)はlocalhostに、DNSはdocker0にバインドする。

```[bash]
$ docker run -d -p 8400:8400 -p 8500:8500 -p 172.17.42.1:53:53/udp --name node1 -h node1 progrium/consul -server -bootstrap-expect 3
```

2台目のノードからは、この1台目のノードに接続する形になるので、IPアドレスを取得。

```[bash]
$ JOIN_IP="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' node1)"
$ echo $JOIN_IP
```

2台目以降のノードをConsulサービスのクラスタに追加。１つのクラスタに対して推奨は3から5くらいのノードらしいので、とりあえず3つ追加。

```[bash]
$ docker run -d -h node2 progrium/consul -server -join $JOIN_IP
$ docker run -d -h node3 progrium/consul -server -join $JOIN_IP
```

下記の通り、HTTPベースでノードの状態が確認できる。

```[bash]
$ curl localhost:8500/v1/catalog/nodes
[{"Node":"node1","Address":"172.17.0.2"},{"Node":"node2","Address":"172.17.0.3"},{"Node":"node3","Address":"172.17.0.4"}]
```

また 'http://localhost:8500/ui/' にアクセスすることで、Web画面でも確認ができる。結構便利。

![ss01.png](https://qiita-image-store.s3.amazonaws.com/0/54904/e4e16480-1fa1-9a5b-969b-084670a8925c.png)

登録されたDNSの確認
-------------------------

続いて、Dockerコンテナ上からconsulに登録されたホスト名が引けることを確認。

```[bash]
$ docker run --rm -it koduki/centos7 /bin/bash
```

まずはノード単位。{host name}.node.consulの形式で引ける。

```[bash]
docker% ping node1.node.consul
PING node1.node.consul (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.055 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.091 ms
^C
--- node1.node.consul ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.055/0.073/0.091/0.018 ms
docker%  ping node2.node.consul
PING node2.node.consul (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.096 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.076 ms
^C
--- node2.node.consul ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.076/0.086/0.096/0.010 msl
```

次はサービス名。{service name}.service.consulの形式で引ける。

```[bash]
docker% ping consul.service.consul
PING consul.service.consul (172.17.0.4) 56(84) bytes of data.
64 bytes from 172.17.0.4: icmp_seq=1 ttl=64 time=0.090 ms
64 bytes from 172.17.0.4: icmp_seq=2 ttl=64 time=0.213 ms
^C
--- consul.service.consul ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.076/0.087/0.098/0.011 ms

docker%  ping consul.service.consul
PING consul.service.consul (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.105 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.181 ms
^C
--- consul.service.consul ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.105/0.143/0.181/0.038 ms 
```

こんな感じで、ラウンドロビンで複数のIPが引けてることが分かる。サービス用のDNSを毎回建てる必要無し！　すばらっ！

サービスを登録
----------------

Consulサービスのクラスタは出来たので、新規にHTTPDなど独自のサービスも登録してみる。

まずは、クラスタに追加したいコンテナにconsulをインストール。

```[bash]
$ docker run --rm -it koduki/centos7 /bin/bash
docker# wget https://dl.bintray.com/mitchellh/consul/0.4.1_linux_amd64.zip && \
unzip 0.4.1_linux_amd64.zip && \
mv consul /usr/local/bin && \
rm -f 0.4.1_linux_amd64.zip 
```

続いて、サービスの定義ファイルを書くディレクトリを作成。

```[bash]
docker# mkdir -p /etc/consul/conf.d
```

サービスは下記の用にJSONで指定する。

/etc/consul/conf.d/httpd.json 

```[json]
{"service": {"name": "tinyweb",  "tags": ["tinyweb", "web"],  "port": 8000}} 
```

ここまでで、準備は完了。
最後に対象のサービスを起動して、consulのクライアントとして登録する。

```[bash]
docker# python -m SimpleHTTPServer &
docker# consul agent -data-dir=tmp/consul -config-dir=/etc/consul/conf.d -join=consul.service.consul
```

これで、tinywebが新規にサービスとしてクラスタに登録された。
新規にDockerコンテナを立ち上げて適切に登録されているかを確認する。

```[bash]
$ docker run --rm -it koduki/centos7 /bin/bash
docker# curl consul.service.consul:8500/v1/catalog/nodes
[{"Node":"a3fcf553511f","Address":"172.17.0.5"},{"Node":"node1","Address":"172.17.0.2"},{"Node":"node2","Address":"172.17.0.3"},{"Node":"node3","Address":"172.17.0.4"}]
docker# curl consul.service.consul:8500/v1/catalog/services
{"consul":[],"tinyweb":["tinyweb","web"]}[root@538fbef09ddf /]# 
```

新規ノードとして"a3fcf553511f"、新規サービスとして"tinyweb"が登録されてることが分かる。
もちろん、この状態でサービスのDNSは適切に登録されているので、下記のようにアクセスが出来る。

```[bash]
docker# curl -LI tinyweb.service.consul:8000  -o /dev/null -w '%{http_code}\n' -s
200
```

こんな感じで、Dockerコンテナの起動時にconsul agentを起動するように設定すれば、DNS管理は特に何もしなくてもOK! すばらっ！

まとめ
-------------

今回は、名前解決が目的だったので他のConsulの機能は基本調べてないけど、クラスタ参加時に特定の処理をしたり、サービスに対して一括してコマンド送ったりも出来たり、内蔵のKVSでデータ管理及び変更に起因するイベント管理をしたりできるので、クラウド環境でサーバが増減する環境では必須の機能かも。
オーケストレーションは、初めて触ったけど、思ったより簡単だったし、ちょっとしたUIもあるので、かなり良さ気。色々使い方を研究しないと。

それでは、Happy Hacking!

参考
-------------

- [progrium/docker-consul](https://github.com/progrium/docker-consul/blob/master/README.md)
- [Consul Service Discovery with Docker](http://progrium.com/blog/2014/08/20/consul-service-discovery-with-docker/)
- [Dockerを使って軽くConsulを触ってみる - さくらのナレッジ](http://knowledge.sakura.ad.jp/tech/2524/)
- [Consul 使ってみる ひつまぶし食べたい 1.0 documentation](http://www.hitsumabushi.org/pages/blog/sphinx/html/automation/consul.html)
-[docker - Set Docker_Opts in centos - Stack Overflow](http://stackoverflow.com/questions/26166550/set-docker-opts-in-centos)