EC2で複数ホストでdocker swarm します、swarm managerも冗長化するよ
この記事でやること
こういう感じのクラスタをつくります
ポイントとしては、
- EC2インスタンスを2台動かし、両方で
swarm manager
を動かすswarm agent
を動かす ということをやります。
agentを複数ホストでやるやつは、ググれば結構出てきますが、
今回はmanagerも複数ホストでやってみます。
今回利用した各バージョンとかは以下のとおり。
- AMI: ami-f2338ff2 CoreOS-stable-723.3.0-hvm
- CoreOS: 723.3.0
- docker: 1.6.2
- etcd: 2.0.12
- CoreOS: 723.3.0
- docker swarm: 0.4.0
作ろう
以下、cloud-config を作っていきます
あ、先に断っておきますが、 Docker Swarm によると現在まだ
Note: Swarm is currently in BETA, so things are likely to change. We don’t recommend you use it in production yet.
な感じなので、将来的にこのコマンドで動くかどうかはわかりません。
etcd
サービスディスカバリーの部分です。
本来、swarm create
でdocker hub のトークンを得るのが楽で良いのですが、
複数マネージャーの立て方を紹介したページ High availability in Docker Swarm によると、複数managerを動かす場合は token://
が使えず、現在のところ etcd://
、 zk://
、 consul://
の3つでしか動かないとのことです。
今回は、CoreOSなので、etcd
を使ってみました。
以下のサンプルを元にやりました。
(サンプルに合った不要な記述を少し削除しましたが)簡単に動きました。
こんな感じ。
#cloud-config coreos: etcd2: # <https://discovery.etcd.io/new?size=Xでトークンを得る。今回はX=1> discovery: https://discovery.etcd.io/tokenxxxxxxxxxxxxxxxxxxxxxxxxxxx advertise-client-urls: http://$private_ipv4:2379 initial-advertise-peer-urls: http://$private_ipv4:2380 listen-client-urls: http://0.0.0.0:2379 listen-peer-urls: http://$private_ipv4:2380 units: - name: etcd2.service command: start
Docker Remote APIを、TCP経由で使えるように準備
Create a swarm for development によると、
swarm agentは、Docker daemonに対して外からTCP経由での接続が必要なようです。
ここで大事なこととして、
Caution : Only use this set up if your network environment is secured by a firewall or other measures.
とありますので、注意が必要です(私はセキュリティグループで 該当ポートはVPC内での接続しか受け付けないようにしました)。
今回はCoreOSのこちらのページ を参考に、DockerのRemote APIを、TCP 2375番で晒しました。
coreos: units: - name: docker-tcp.socket command: start enable: true content: | [Unit] Description=Docker Socket for the API [Socket] ListenStream=2375 BindIPv6Only=both Service=docker.service [Install] WantedBy=sockets.target
ここで、もしうまくいっていれば、以下の様に docker ps
コマンドが、(エラーにならずに)実行できるはずです
# docker daemonが2375ポートで動いている場合 $ docker -H :2375 ps; echo $? CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0 # docker daemonが2375ポートで動いていない場合(うまくいっていない場合) $ docker -H :2375 ps; echo $? FATA[0000] Cannot connect to the Docker daemon. Is 'docker -d' running on this host? 1
swarm agentを動かす
Create a swarm for development を参考に。
コマンドの形は docker run [runのoption] swarm(=イメージ名) join [swarm-joinのoption] argment
みたいな感じです。
最後の引数は、サービスディスカバリーの情報です。さっき作った自ホストのetcdを指定します。
シェルからならこんな感じ。
$ docker run -d --name=swarm-join \ swarm join \ --advertise=ホストのプライベートIPアドレス:2375 \ etcd://ホストのプライベートIPアドレス::2379/swarm # ここは0.0.0.0でもイケたかも、試してない
cloud-configにした場合、こんな感じ。$private_ipv4
という、変数的なものが使えます。
coreos: units: - name: swarm-join.service command: start content: | [Unit] Description=swarm-join Container After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 Restart=always ExecStartPre=-/usr/bin/docker stop swarm-join ExecStartPre=-/usr/bin/docker rm swarm-join ExecStartPre=-/usr/bin/docker pull swarm ExecStart=/usr/bin/docker run --name=swarm-join swarm join --advertise=$private_ipv4:2375 etcd://$private_ipv4:2379/swarm [Install] WantedBy=multi-user.target
swarm managerを動かす
https://docs.docker.com/swarm/multi-manager-setup/#create-the-primary-manager を参考にやります。
シェルからならこんな感じ。
$ docker run -d --net=host --name=swarm-manage \ swarm manage \ -H :4000 \ --replication \ --strategy=binpack \ --advertise 自ホストのローカルIPアドレス:4000 \ etcd://自ホストのローカルIPアドレス:2379/swarm
cloud-configにするとこんな形になりました。
coreos: units: - name: swarm-manage.service command: start content: | [Unit] Description=swarm-manage Container After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 Restart=always ExecStartPre=-/usr/bin/docker stop swarm-manage ExecStartPre=-/usr/bin/docker rm swarm-manage ExecStartPre=-/usr/bin/docker pull swarm ExecStart=/usr/bin/docker run --net=host --name=swarm-manage swarm manage -H :4000 --replication --strategy=binpack --advertise $private_ipv4:4000 etcd://$private_ipv4:2379/swarm [Install] WantedBy=multi-user.target
ポイントとしては、他のmanagerから来る問い合わせに使うポートを、-H 4000
と--advertise $private_ipv4:4000
で、指定しています。
このあたり、--net=host
としないとうまく動かず… うまい書き方があったら教えてほしいところです。
準備完了っす!
ぜんぶくっつけた全体像はこんな感じ。
#cloud-config hostname: node1 coreos: etcd2: # <https://discovery.etcd.io/new?size=1でトークンを得る> discovery: https://discovery.etcd.io/tokenxxxxxxxxxxxxxxxxxxxxxxxxxxx advertise-client-urls: http://$private_ipv4:2379 initial-advertise-peer-urls: http://$private_ipv4:2380 listen-client-urls: http://0.0.0.0:2379 listen-peer-urls: http://$private_ipv4:2380 units: - name: etcd2.service command: start - name: docker-tcp.socket command: start enable: true content: | [Unit] Description=Docker Socket for the API [Socket] ListenStream=2375 BindIPv6Only=both Service=docker.service [Install] WantedBy=sockets.target - name: swarm-join.service command: start content: | [Unit] Description=swarm-join Container After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 Restart=always ExecStartPre=-/usr/bin/docker stop swarm-join ExecStartPre=-/usr/bin/docker rm swarm-join ExecStartPre=-/usr/bin/docker pull swarm ExecStart=/usr/bin/docker run --name=swarm-join swarm join --advertise=$private_ipv4:2375 etcd://$private_ipv4:2379/swarm [Install] WantedBy=multi-user.target - name: swarm-manage.service command: start content: | [Unit] Description=swarm-manage Container After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 Restart=always ExecStartPre=-/usr/bin/docker stop swarm-manage ExecStartPre=-/usr/bin/docker rm swarm-manage ExecStartPre=-/usr/bin/docker pull swarm ExecStart=/usr/bin/docker run --net=host --name=swarm-manage swarm manage -H :4000 --replication --strategy=binpack --advertise $private_ipv4:4000 etcd://$private_ipv4:2379/swarm [Install] WantedBy=multi-user.target
以上のyamlをユーザーデータとして利用し、CoreOSのAMIを選択し、最初からEC2を起動します(ユーザーデータについては説明割愛します)。
2台目を起動するときは、今回は hostname: node1
の部分を hostname: node2
に書き換えて起動します(ここだけ超手動)
動いた!
EC2が起動したら node1でコマンド発行してみます。
# 普通にdocker ps。これも一応動く。managerとagentの2つが動いています。 core@node1 ~ $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1153b3bc311d swarm:latest "/swarm manage -H :4 5 minutes ago Up 5 minutes swarm-manage bb475b187a5e swarm:latest "/swarm join --adver 5 minutes ago Up 5 minutes 2375/tcp swarm-join # 自ノードのポート指定してdocker ps。 ポートが利用されていることが確認できる。 core@node1 ~ $ docker -H:2375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1153b3bc311d swarm:latest "/swarm manage -H :4 5 minutes ago Up 5 minutes swarm-manage bb475b187a5e swarm:latest "/swarm join --adver 5 minutes ago Up 5 minutes 2375/tcp swarm-join # node2のポート指定してdocker ps。node2.innerという名前は、別途手で、2台めのIPに対して付与しています。。 # CONTAINER IDがnode1のものと違うので、別のコンテナであることがわかります core@node1 ~ $ docker -H node2.inner:2375 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3c6a0a41d3e6 swarm:latest "/swarm manage -H :4 5 minutes ago Up 5 minutes swarm-manage 1f340aef1237 swarm:latest "/swarm join --adver 5 minutes ago Up 5 minutes 2375/tcp swarm-join core@node1 ~ $
よさ気です。つぎに、 manager である4000番ポートも調べてみます。
# 普通にdocker psするとなにも表示されない。 core@node1 ~ $ docker -H :4000 ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # -aオプションつけると、managerやagentも表示される。NAMES列でホスト名が確認できる。 core@node1 ~ $ docker -H :4000 ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3c6a0a41d3e6 swarm:latest "/swarm manage -H :4 6 minutes ago Up 6 minutes node2/swarm-manage 1f340aef1237 swarm:latest "/swarm join --adver 6 minutes ago Up 6 minutes 2375/tcp node2/swarm-join 1153b3bc311d swarm:latest "/swarm manage -H :4 6 minutes ago Up 6 minutes node1/swarm-manage bb475b187a5e swarm:latest "/swarm join --adver 6 minutes ago Up 6 minutes 2375/tcp node1/swarm-join # node2のポートも見られることを確認。 core@node1 ~ $ docker -H node2.inner:4000 ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3c6a0a41d3e6 swarm:latest "/swarm manage -H :4 6 minutes ago Up 6 minutes node2/swarm-manage 1f340aef1237 swarm:latest "/swarm join --adver 6 minutes ago Up 6 minutes 2375/tcp node2/swarm-join 1153b3bc311d swarm:latest "/swarm manage -H :4 7 minutes ago Up 7 minutes node1/swarm-manage bb475b187a5e swarm:latest "/swarm join --adver 7 minutes ago Up 7 minutes 2375/tcp node1/swarm-join
以上のようにmanagerに対してdocker ps
すると、4個のコンテナ((manageer + agent) * 2台)がrunしていることがわかります。
managerの動きを調べる
つぎに、managerに対して docker info
してみます。
core@node1 ~ $ docker -H :4000 info Containers: 4 Images: 4 Storage Driver: Role: primary Strategy: binpack Filters: affinity, health, constraint, port, dependency Nodes: 2 node1: 172.31.14.196:2375 └ Containers: 2 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 1.022 GiB └ Labels: executiondriver=native-0.2, kernelversion=4.0.5, operatingsystem=CoreOS 723.3.0, storagedriver=overlay node2: 172.31.25.198:2375 └ Containers: 2 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 1.022 GiB └ Labels: executiondriver=native-0.2, kernelversion=4.0.5, operatingsystem=CoreOS 723.3.0, storagedriver=overlay Execution Driver: Kernel Version: Operating System: CPUs: 2 Total Memory: 2.043 GiB Name: node1 ID: Http Proxy: Https Proxy: No Proxy:
Nodeを2つ認識しており、Role
はPrimary
であることがわかります。
対して、node2
に対して実行すると
core@node1 ~ $ docker -H node2.inner:4000 info Containers: 4 Images: 4 Storage Driver: Role: replica Primary: 172.31.14.196:4000 Strategy: binpack …以下同じなので省略…
Role
はreplica
であることがわかります。
今回はたまたま、node1(172.31.14.196
)がPrimaryに選出されたようですね。
選挙の様子は、それぞれのmanagerに対してdocker logs
を実行すれば見られます。
core@node1 ~ $ docker -H :4000 logs node1/swarm-manage time="2015-09-29T21:45:30Z" level=info msg="Listening for HTTP" addr=":4000" proto=tcp time="2015-09-29T21:45:30Z" level=info msg="Leader Election: Cluster leadership lost" time="2015-09-29T21:45:30Z" level=error msg="Leader Election: watch leader channel closed, the store may be unavailable..." time="2015-09-29T21:45:30Z" level=info msg="Leader Election: Cluster leadership acquired" time="2015-09-29T21:45:30Z" level=info msg="Registered Engine node1 at 172.31.14.196:2375" time="2015-09-29T21:45:49Z" level=info msg="Registered Engine node2 at 172.31.25.198:2375" core@node1 ~ $ docker -H :4000 logs node2/swarm-manage time="2015-09-29T21:45:50Z" level=info msg="Listening for HTTP" addr=":4000" proto=tcp time="2015-09-29T21:45:50Z" level=info msg="Leader Election: Cluster leadership lost" time="2015-09-29T21:45:50Z" level=info msg="New leader elected: 172.31.14.196:4000" time="2015-09-29T21:45:50Z" level=info msg="Registered Engine node2 at 172.31.25.198:2375" time="2015-09-29T21:45:51Z" level=info msg="Registered Engine node1 at 172.31.14.196:2375"
managerのうち、1台を止めてみる
止めてみます。
sudo systemctl disable swarm-manage
sudo systemctl stop swarm-manage
このあとnode2のmanagerのlogを見てみると、自分がNew leader
であると主張しています!
$ docker -H node2.inner:4000 logs swarm-manage …(略) time="2015-09-30T10:28:41Z" level=info msg="Leader Election: Cluster leadership acquired"
docker infoして確認しても、今度はnode2のほうがprimaryだと主張していることがわかります。
core@node1 ~ $ docker -H node2.jnner:4000 info Containers: 4 Images: 4 Storage Driver: Role: primary ...(略)
フェイルオーバーっぽいことができていることがわかります。
まぁ、docker -H $(etcdctl get /swarm/docker/swarm/leader) ps
的なことが、1台壊れるぐらいなら大丈夫な感じみたいな。感じ感じ
あと、etcdは安定的に動かすには3台以上無いとダメっぽいです
まとめ
- managerの冗長化も、etcd使ってなんとかなる
- しかも割と簡単
- docker-machineとかdocker-composeとかvagrantとか使わんでも、なんとかなる!
- EC2のオートスケーリングとかと組み合わせれば夢が広がりそう
次回(いつか)は、このクラスタでswarmのbinpackストラテジーを体験できることを書きたいと思います。