夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Docker-Registrator(Normal/internal)でConsulに登録される内容は?

こんにちは。

最近、Dockerでマルチコンテナのクラスタを組もう、ということをやっています。
で、そこで課題になってくるのがIPアドレスが一定しない、ということですよね。

ですので、そのためにConsul等のサービスディスカバリの仕組みを使う形になります。
ただ、Dockerコンテナの内部からConsulに登録するのはいまいち面倒・・ということで、
自動登録が可能なDocker Registratorを2パターン試してみました。

とりあえず、出来ると構成は下記のようになる・・はず。
f:id:kimutansk:20150712191036p:plain

尚、OSはCentOS7、あとFirewalldは無効化してiptablesを用いています。
理由は後ほど(次回以降?)

1. OS側準備

まずはDockerインストールなどの諸々のセットアップを行います。
尚、rootユーザでログインを予め許容しておき、rootユーザでセットアップを行います。
Selinux無効化

# setenforce 0
# sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

IPv6無効化

# echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
# echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf
# sysctl -p

■各ホストの名称追加

# echo "192.168.100.231 host1" >> /etc/hosts
# echo "192.168.100.232 host2" >> /etc/hosts
# echo "192.168.100.233 host3" >> /etc/hosts

■firewalldを無効化&iptablesを有効化

# yum install iptables-services
# systemctl status firewalld
# systemctl stop firewalld
# systemctl disable firewalld
# systemctl enable iptables
# systemctl start iptables
# systemctl status iptables

■Dockerインストール&有効化

# curl -O -sSL https://get.docker.com/rpm/1.7.0/centos-7/RPMS/x86_64/docker-engine-1.7.0-1.el7.centos.x86_64.rpm
# yum localinstall --nogpgcheck docker-engine-1.7.0-1.el7.centos.x86_64.rpm
# systemctl enable docker.service
# systemctl restart docker

2. Atlasを使ってConsul Clusterを組んでみる

どうせですので、Atlasを使ってConsul Clusterを組んでみることにします。
まず、下記のページにアクセスしてアカウントを作成します。
「Sign up & Tutorials」からアカウントは作成可能です。
Atlas by HashiCorp

で、作成してログインすると下記のようなページに遷移します。
f:id:kimutansk:20150712193546j:plain
右上の自分のアカウント名をクリックすると設定画面に飛ぶので、そこから「Tokens」をクリックすると下記の画面に飛びますので、名前を指定してTokenを発行します。
尚、一度発行した後は2度と見れないため、発行時にきちんと残しておきましょう。
f:id:kimutansk:20150712193842j:plain
という形で、Tokenの発行が出来ました。これを用いて構築します。

では、各ホストでiptablesの許容定義追加とconsulインストールと設定を行います。
余分な設定かもしれませんが、念のため。
iptables許容定義追加

# iptables -I INPUT 5 -p tcp --dport 53 -j ACCEPT
# iptables -I INPUT 5 -p udp --dport 53 -j ACCEPT
# iptables -I INPUT 5 -p tcp --dport 8300 -j ACCEPT
# iptables -I INPUT 5 -p tcp --dport 8301 -j ACCEPT
# iptables -I INPUT 5 -p udp --dport 8301 -j ACCEPT
# iptables -I INPUT 5 -p tcp --dport 8302 -j ACCEPT
# iptables -I INPUT 5 -p udp --dport 8302 -j ACCEPT
# iptables -I INPUT 5 -p tcp --dport 8400 -j ACCEPT
# iptables -I INPUT 5 -p tcp --dport 8500 -j ACCEPT
# iptables -I INPUT 5 -p udp --dport 8500 -j ACCEPT
# /usr/libexec/iptables/iptables.init save

■consulインストール&設定

# rpm -iUvh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
# yum install -y jq
# wget https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip
# wget https://dl.bintray.com/mitchellh/consul/0.5.2_web_ui.zip
# unzip 0.5.2_linux_amd64.zip
# mkdir /opt/consul
# mv consul /opt/consul/
# ln -s /opt/consul/consul /usr/local/sbin/consul
# mkdir /opt/consul/data
# unzip 0.5.2_web_ui.zip
# mv dist /opt/consul/webui
# mkdir /etc/consul.d/
# echo '{"ports": {"dns": 53}, "recursor": "8.8.8.8" }' | jq . > /etc/consul.d/consul.json

では、先ほど発行したTokenを用いてconsul clusterを構築してみます。
尚、「-atlas」と「-atlas-token」の値はダミーです。

実際は、「-atlas」にはAtlasのアカウントID/Token名称を、
「-atlas-token」には発行したTokenを設定します。

# export HOST_IP=$(ifconfig eno16780032 | grep 'inet ' | awk '{ print $2  }')
# nohup consul agent -server -bootstrap-expect 3  -atlas-join -atlas=consul/test -atlas-token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -client ${HOST_IP} -data-dir /opt/consul/data -ui-dir /opt/consul/webui -config-dir /etc/consul.d -config-file /etc/consul.d/consul.json &

すると・・・?
コンソール出力(nohupなので正確にはそこから読んでます)に下記のように出力され、クラスタが構築されたことが確認できました。
素晴らしい。

2015/07/12 20:08:13 [INFO] scada-client: connect requested (capability: http)
2015/07/12 20:08:47 [INFO] scada-client: auto-joining LAN with [192.168.100.232 192.168.100.233]

メンバー一覧にもきちんと表示されます。

# consul members -rpc-addr=${HOST_IP}:8400
Node   Address               Status  Type    Build  Protocol  DC
host1  192.168.100.231:8301  alive   server  0.5.2  2         dc1
host2  192.168.100.232:8301  alive   server  0.5.2  2         dc1
host3  192.168.100.233:8301  alive   server  0.5.2  2         dc1

3.Registrator導入&登録内容確認

では、次にRegistratorの導入を行い、実際にconsulに登録される内容を見てみます。
■Registrator起動

# docker run -d -v /var/run/docker.sock:/tmp/docker.sock -h registrator --name registrator progrium/registrator consul://${HOST_IP}:8500
# docker ps
CONTAINER ID        IMAGE                  COMMAND                CREATED              STATUS              PORTS               NAMES
bb2639c9710e        progrium/registrator   "/bin/registrator co   About a minute ago   Up About a minute                       registrator

Registratorが起動できたので、適当なコンテナに名前つけてExposeして起動してみます。
どうせですので、SSHを使えるようにしたCentOS7で。
ということで、下記のDockerfileを用意します。
■Dockerfile

FROM centos:centos7

RUN yum -y install passwd openssh-server initscripts

RUN /usr/sbin/sshd-keygen
RUN sed -ri 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config
RUN echo 'root:rootroot' | chpasswd

EXPOSE 22

■コンテナビルド&起動

# docker build -t centos7ssh .
※host1で実施
# docker run -p 22 -d --hostname=container1 --name=container1 centos7ssh /usr/sbin/sshd -D
※host2で実施
# docker run -p 22 -d --hostname=container2 --name=container2 centos7ssh /usr/sbin/sshd -D
※host3で実施
# docker run -p 22 -d --hostname=container3 --name=container3 centos7ssh /usr/sbin/sshd -D

こうすると、下記のようにconsulに「centos7ssh」というサービスが追加されて、情報が取得できるようになります。

# curl -s http://${HOST_IP}:8500/v1/catalog/service/centos7ssh | jq .
[
  {
    "ServicePort": 32770, // ★ホストのポート番号★
    "ServiceAddress": "",
    "ServiceTags": null,
    "ServiceName": "centos7ssh",
    "ServiceID": "registrator:container1:22",
    "Address": "192.168.100.231", // ★ホストのIPアドレス★
    "Node": "host1" // ★ホストのホスト名★
  },
  {
    "ServicePort": 32768,
    "ServiceAddress": "",
    "ServiceTags": null,
    "ServiceName": "centos7ssh",
    "ServiceID": "registrator:container2:22",
    "Address": "192.168.100.232",
    "Node": "host2"
  },
  {
    "ServicePort": 32768,
    "ServiceAddress": "",
    "ServiceTags": null,
    "ServiceName": "centos7ssh",
    "ServiceID": "registrator:container3:22",
    "Address": "192.168.100.233",
    "Node": "host3"
  }
]

ただ、ちょっと厄介なこともあります。
上記の結果を見ればわかるように、centos7sshはNodeの値がホストのものとなっています。
つまり、下記のようにホストのサービスとして登録されているわけですね。

# curl -s http://${HOST_IP}:8500/v1/catalog/node/host1 | jq .
{
  "Services": {
    "registrator:container1:22": {
      "Port": 32770,
      "Address": "",
      "Tags": null,
      "Service": "centos7ssh",
      "ID": "registrator:container1:22"
    },
    "consul": {
      "Port": 8300,
      "Address": "",
      "Tags": [],
      "Service": "consul",
      "ID": "consul"
    }
  },
  "Node": {
    "Address": "192.168.100.231",
    "Node": "host1"
  }
}

そのため、「DNSからコンテナの名称を指定して引く」ことができません。
結果、コンテナの名前を指定して接続をするということが出来ず、
コンテナ同士で特定のコンテナを指定して名前解決をするということが出来ないわけですね。

# dig @${HOST_IP} container1.node.consul ANY
※結果が取得できない。

コンテナ同士で通信が出来ればいいポートについても全部Exposeすればいいのかもしれませんが、
それはそれでいまいち取り回しが悪いように思えます。

4.Registrator(internal)登録内容確認

コンテナの名前を指定してDNSを引く方法はないのか、と思って調べると案の定ありました。sttts.github.io

マルチホスト上のコンテナが通信可能なネットワークを構築可能なweaveで、
Registratorに対してPullRequestを投げており、internalという設定が出来るようです。

ですので、一度コンテナを全てkill&rmして、下記のコマンドを試してみます。

# docker run -d -v /var/run/docker.sock:/tmp/docker.sock -h registrator --name registrator progrium/registrator  -internal consul://${HOST_IP}:8500
※host1で実施
# docker run -p 22 -d --hostname=container1 --name=container1 centos7ssh /usr/sbin/sshd -D
※host2で実施
# docker run -p 22 -d --hostname=container2 --name=container2 centos7ssh /usr/sbin/sshd -D
※host3で実施
# docker run -p 22 -d --hostname=container3 --name=container3 centos7ssh /usr/sbin/sshd -D

すると、今度はNodeとしてコンテナが登録されています。

# curl -s http://${HOST_IP}:8500/v1/catalog/nodes | jq .
[
  {
    "Address": "172.17.0.11",
    "Node": "container1"
  },
  {
    "Address": "172.17.0.9",
    "Node": "container2"
  },
  {
    "Address": "172.17.0.9",
    "Node": "container3"
  },
  {
    "Address": "192.168.100.231",
    "Node": "host1"
  },
  {
    "Address": "192.168.100.232",
    "Node": "host2"
  },
  {
    "Address": "192.168.100.233",
    "Node": "host3"
  }
]

そのため、下記のように「コンテナの名前を指定して、consul DNSからコンテナのアドレスを取得する」ことが可能になります。
#今はマルチホストのコンテナネットワーク化していないため、IPアドレス被っていたりはしていますが。
これで、「コンテナ同士が名前を引いてIPアドレスを特定できる」ということが可能になるわけですね。

# dig @${HOST_IP} container1.node.consul ANY
container1.node.consul. 0       IN      A       172.17.0.11

StormやZookeeper、Kafka等、クラスタの構成プロセス同士がノード単位で名前を指定して通信する・・
というパターンでConsul DNSを用いて上手く名前解決できそうな感じです。

実際全て構築するのはまた大変そうですが、出来そうな見込みは立った、という感じでしょうか。


尚、参考までに、Serviceのポート番号はContainerのものが用いられているようです。

# curl -s http://${HOST_IP}:8500/v1/catalog/service/centos7ssh | jq .
[
  {
    "ServicePort": 22, // ★Containerのポート番号★
    "ServiceAddress": "",
    "ServiceTags": [],
    "ServiceName": "centos7ssh",
    "ServiceID": "container1:container1:22",
    "Address": "172.17.0.11", // ★ContainerのIPアドレス★
    "Node": "container1" // ★Containerのホスト名★
  },
  {
    "ServicePort": 22,
    "ServiceAddress": "",
    "ServiceTags": [],
    "ServiceName": "centos7ssh",
    "ServiceID": "container2:container2:22",
    "Address": "172.17.0.9",
    "Node": "container2"
  },
  {
    "ServicePort": 22,
    "ServiceAddress": "",
    "ServiceTags": [],
    "ServiceName": "centos7ssh",
    "ServiceID": "container3:container3:22",
    "Address": "172.17.0.9",
    "Node": "container3"
  }
]