docker
consul
DockerDay 14

DockerコンテナをConsulで管理する方法

More than 3 years have passed since last update.

はじめに

Dockerを利用するとコンテナをぽこぽこ沢山立てることが多いと思います。
コンテナが沢山できるので、それらに対していかに効率よくアクセス出来るかが肝になります。

またコンテナで提供するサービスのポートをホストに割り当てて利用する場合(-pオプションを利用する場合)、
ポコポコ出来るコンテナのポートを静的に(-p 80:8080みたいに)割り当てるのは面倒です。
なので動的に(-p 80みたいにしてホストの適当なポートに)割り当てたいところです。

ただし、動的に割り当てるとどのコンテナがどのポートでサービスを提供しているか把握するのが難しくなり、さらにマルチホストになるとどのホストで動いているかどうかを把握する必要もあり、これも難しいです。

where.png

この辺うまいこと出来ないかな、ということでServiceDiscoveryといったらConsulですよねってことで組み合わせて使ってみます。
Consulについての説明は省きます。

環境整備

DockerコンテナとしてConsul Agentを起動する

Agentを各マシンで起動させてクラスタを組みます。
progrium/docker-consul( https://github.com/progrium/docker-consul )を利用するとお手軽に立ち上げられます。

Server Agent(1台目):

$ docker run -d \
-p 8300:8300 \
-p 8301:8301/tcp \
-p 8301:8301/udp \
-p 8302:8302/tcp \
-p 8302:8302/udp \
-p 8400:8400 \
-p 8500:8500 \
-p 8600:53/tcp \
-p 8600:53/udp \
-h node-1 --name consul progrium/consul -server -bootstrap

Server Agent(2台目以降):

$ docker run -d \
-p 8300:8300 \
-p 8301:8301/tcp \
-p 8301:8301/udp \
-p 8302:8302/tcp \
-p 8302:8302/udp \
-p 8400:8400 \
-p 8500:8500 \
-p 8600:53/tcp \
-p 8600:53/udp \
-h node-2 --name consul progrium/consul -server -join {node-1のIP}

Client Agent:

$ docker run -d \
-p 8301:8301/tcp \
-p 8301:8301/udp \
-p 8400:8400 \
-p 8500:8500 \
-p 8600:53/tcp \
-p 8600:53/udp \
-h node-3 --name consul progrium/consul -join {node-1のIP}

クラスタメンバ確認(どこかのサーバにログインして確認)

core@server-1 ~ $ docker exec -it コンテナ名 consul members
Node             Address             Status  Type    Build  Protocol
node-1  192.168.33.11:8301  alive   server  0.4.1  2
node-2  192.168.33.12:8301  alive   server  0.4.1  2
node-3  192.168.33.21:8301  alive   client  0.4.1  2

Registratorを利用してサービスの追加/削除を動的に行う

Registrator( https://github.com/progrium/registrator )を利用すると

  • docker run foo
  • fooサービスをConsulに自動登録
  • docker kill foo
  • fooサービスをConsulから自動削除

をしてくれます。
RegistratorはDockerHubにイメージが上がっているのでこれでお手軽に利用できます(実装はGo言語なのでそもそもインストールは簡単)。

各サーバでRegistratorのコンテナを立ち上げていきます。

$ docker run -d \
-v /var/run/docker.sock:/tmp/docker.sock \
-h registrator-1 --name registrator progrium/registrator consul://consul:8500

以上で環境整備は終わりです。

デモ環境

以上の作業までをまとめたVagrantfileを用意しましたので必要であればご利用下さい。
https://github.com/foostan/consul-playground/tree/master/registrator

$ vagrant up

すると下記のような環境が出来上がります。

consul_with_registrator.png

なお、本エントリの操作は全てこのデモ環境で行っていますので、適宜ホスト名やコンテナ名は読み替えて下さい。

コンテナの起動とServiceDiscovery

コンテナの起動

試しにRedisコンテナを立ち上げてみます。

core@node-1 ~ $ docker run -d \
--dns `docker inspect --format="{{ .NetworkSettings.IPAddress }}" consul` \ 
--link consul:consul \
-p 6379 redis

core@node-1 ~ $ docker ps -l
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS                     NAMES
36e962d0bdb8        redis:latest        "/entrypoint.sh redi   About a minute ago   Up About a minute   0.0.0.0:49153->6379/tcp   sick_jones

core@node-1 ~ $ docker logs registrator
2014/12/14 06:13:57 registrator: Using consul registry backend at consul://consul:8500
2014/12/14 06:13:57 registrator: ignored: 9f81c5811654 no published ports
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:53:udp
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8300
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8301
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8301:udp
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8302
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8302:udp
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8400
2014/12/14 06:13:57 registrator: added: 8062bd07f8eb registrator-node-1:consul:8500
2014/12/14 06:13:57 registrator: Listening for Docker events...
2014/12/14 06:44:02 registrator: added: 36e962d0bdb8 registrator-node-1:drunk_tesla:6379

Registratorによる登録がされているみたいなので、ConsulのServiceDiscoveryで確認してみます。

HTTP API

$ curl -s 192.168.33.11:8500/v1/catalog/service/redis  | jq .
[
  {
    "Node": "node-1",
    "Address": "192.168.33.11",
    "ServiceID": "registrator-node-1:sick_jones:6379",
    "ServiceName": "redis",
    "ServiceTags": null,
    "ServicePort": 49153
  }
]

登録されてました。
WebUIからも確認できます。
webui.png

DNS Interface

$ dig @192.168.33.11 -p 8600 redis.service.consul ANY
; <<>> DiG 9.9.5-4.3-Ubuntu <<>> @192.168.33.11 -p 8600 redis.service.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18338
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;redis.service.consul.      IN  ANY

;; ANSWER SECTION:
redis.service.consul.   0   IN  A   192.168.33.11

;; Query time: 4 msec
;; SERVER: 192.168.33.11#8600(192.168.33.11)
;; WHEN: Sun Dec 14 16:26:19 JST 2014
;; MSG SIZE  rcvd: 74

こちらも大丈夫そうです。

また、Redisコンテナを立ち上げるときに--dnsオプションでconsulコンテナのIPを指定しました。
こうすると名前解決するときにconsulのDNSを利用するようになるので、各サービスへ名前でアクセスできるようになります。

core@node-1 ~ $ docker exec sick_jones cat /etc/resolv.conf
nameserver 172.17.0.2

core@node-1 ~ $ docker exec -it sick_jones ping consul.service.consul
PING consul.service.consul (192.168.33.12): 48 data bytes
56 bytes from 192.168.33.12: icmp_seq=0 ttl=63 time=0.690 ms
56 bytes from 192.168.33.12: icmp_seq=1 ttl=63 time=0.557 ms
56 bytes from 192.168.33.12: icmp_seq=2 ttl=63 time=0.493 ms

core@node-1 ~ $ docker exec -it sick_jones ping redis.service.consul
PING redis.service.consul (192.168.33.11): 48 data bytes
56 bytes from 192.168.33.11: icmp_seq=0 ttl=64 time=0.130 ms
56 bytes from 192.168.33.11: icmp_seq=1 ttl=64 time=0.129 ms
56 bytes from 192.168.33.11: icmp_seq=2 ttl=64 time=0.131 ms

Registratorのオプションについて

先ほどのRedisの例では、

  • ServiceName: "redis",
  • ServiceTags: null,

となっていましたが、これらの値は自由に変更することができます。

$ core@node-1 ~ $ docker run -d \
--dns `docker inspect --format="{{ .NetworkSettings.IPAddress }}" consul` \ 
--link consul:consul \
-e "SERVICE_NAME=db" \
-e "SERVICE_TAGS=master,backups" \
-p 6379 redis

このようにコンテナを立ち上げるときに SERVICE_NAMESERVICE_TAGSの環境変数に任意の値を入れると、それぞれその値で登録されます。
詳しいことは README.md( https://github.com/progrium/registrator/blob/master/README.md ) に記載されています。

まとめ

Dockerを利用するとコンテナをぽこぽこ立てることになり、各コンテナへのアクセスが困難になることがありますが、本エントリではそれに対してConsulのServiceDiscoveryを使う解決策を示しました。

ConsulではHTTP APIとDNS Interfaceが用意されているため様々なシーンで利用できると思います。
また、今回は紹介しませんでしたが、Consul-template( https://github.com/hashicorp/consul-template )を利用すると、Consulに登録されたサービスを取得してテンプレートからファイルを自動生成できるようにあります(例えばHA Proxyの設定ファイルを自動生成してWebサーバを動的に抜き差しする、なんてことが簡単にできる。)

CoreOSやk8sなどはetcdなどを利用してこのあたりのことを解決していますが、DockerとConsulを組み合わせて使うというのも選択のひとつに入れてみるものいいかもしれませんね。