はじめに
今さらジローではあるが、自分の中で Docker を絡めたクラスタ構成について気になっているので CoreOS やその関連技術を改めて勉強してみたいと思う。CoreOS 等の各種情報については参考にさせて頂いた記事がとても詳しく参考になるので、そちらを都度確認しつつ理解を深めていきたい。
今回は以下を学びたい。
- Vagrant で CoreOS ノードを複数起動する
- etcd と fleet をザクっと触る
- fleet で Docker コンテナクラスタを管理する
- 起動したコンテナのサービスを registrator で etcd に登録する
- etcd に登録したサービスの情報を利用して HAProxy の設定に反映させる
構成
今回、教材として利用する構成は以下の通り。
参考
- http://deeeet.com/writing/2014/11/20/fleet/
- http://enakai00.hatenablog.com/entry/20130917/1379374797
- http://www.freedesktop.org/software/systemd/man/systemd.unit.html
- https://sites.google.com/site/kandamotohiro/systemd/man-systemd-unit-no-yi
- https://coreos.com/fleet/docs/latest/unit-files-and-scheduling.html
- https://coreos.com/fleet/docs/latest/launching-containers-fleet.html
- http://knowledge.sakura.ad.jp/tech/2519/
- http://knowledge.sakura.ad.jp/tech/3390/
CoreOS on VirtualBox
vagrant up
git clone する。
$ git clone https://github.com/coreos/coreos-vagrant.git
3 台の CoreOS ノードを起動したいので Vagrantfile の以下を修正。
$ diff -u Vagrantfile.bk Vagrantfile --- Vagrantfile.bk 2015-08-10 00:14:07.000000000 +0900 +++ Vagrantfile 2015-08-08 18:36:52.000000000 +0900 @@ -9,14 +9,14 @@ CONFIG = File.join(File.dirname(__FILE__), "config.rb") # Defaults for config options defined in CONFIG -$num_instances = 1 +$num_instances = 3 $instance_name_prefix = "core" $update_channel = "alpha" $image_version = "current" $enable_serial_logging = false $share_home = false $vm_gui = false -$vm_memory = 1024 +$vm_memory = 512 $vm_cpus = 1 $shared_folders = {} $forwarded_ports = {}
etcd のクラスタリングには discovery.etcd.io を利用したいので token を取得する。
$ curl -s https://discovery.etcd.io/new
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
出力された token を user-data に追記する。
$ diff -u user-data.sample user-data --- user-data.sample 2015-08-08 18:33:32.000000000 +0900 +++ user-data 2015-08-08 18:46:53.000000000 +0900 @@ -5,12 +5,14 @@ # generate a new token for each unique cluster from https://discovery.etcd.io/new # WARNING: replace each time you 'vagrant destroy' #discovery: https://discovery.etcd.io/<token> + discovery: https://discovery.etcd.io/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx addr: $public_ipv4:4001 peer-addr: $public_ipv4:7001 etcd2: #generate a new token for each unique cluster from https://discovery.etcd.io/new #discovery: https://discovery.etcd.io/<token> # multi-region and multi-cloud deployments need to use $public_ipv4 + # discovery: https://discovery.etcd.io/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx advertise-client-urls: http://$public_ipv4:2379 initial-advertise-peer-urls: http://$private_ipv4:2380 # listen on both the official ports and the legacy ports
vagrant up
する。
$ vagrant up
起動したら各ノードにログインする。
$ vagrant ssh core-01 $ vagrant ssh core-02 $ vagrant ssh core-03
いずれかのノードにて etcd のクラスタが構成されているかを確認する。
core@core-01 ~ $ etcdctl --version etcdctl version 2.1.1 core@core-01 ~ $ etcdctl cluster-health cluster is healthy member 7aa2819a5e2a4c9cac1e3d4b87760f7f is healthy member bc1eb30f9eb844648110fec1455db88e is healthy member e1103feeae8744c9ab93dc662803ea66 is healthy
fleet でサービスを管理する
fleet クラスタ構成の確認
fleetctl list-machines
を実行してクラスタの構成を確認する。
core@core-01 ~ $ fleetctl list-machines
MACHINE IP METADATA
7aa2819a... 172.17.8.101 -
bc1eb30f... 172.17.8.103 -
e1103fee... 172.17.8.102 -
unit ファイルには systemd の知識が必要になる...
fleetctl でサービスを管理する場合に unit という単位でサービスを管理することになるが、この unit 設定ファイルが systemd の unit 設定ファイルに fleet 独自の [X-Fleet]
というセクションを加えた内容になる。以下は Docker コンテナを各 CoreOS ノードで起動することを前提とした unit ファイル。尚、詳細な記述方法や各オプションの意味についてはこちらやこちら記事にとても詳しく記載されている。有難うございます。
[Unit] Description=kappa-test After=docker.service Requires=docker.service [Service] ExecStartPre=-/usr/bin/docker kill kappa-test-%i ExecStartPre=-/usr/bin/docker rm kappa-test-%i ExecStart=/usr/bin/docker run --name=kappa-test-%i --hostname=kappa-test-%i -p 8000 centos:centos6 /bin/sh -c 'cd /tmp/ ; hostname -s > index.html ; python -m SimpleHTTPServer' ExecStop=/usr/bin/docker stop kappa-test-%i [Install] WantedBy=multi-user.target [X-Fleet] X-Conflicts=kappa-test@*.service
上記例の %i
については sysytemd.unit の man ページによると「ユニット名の中の "@" 文字とサフィックスの間の文字列を参照する」と記載されており、いまいち「ピン」と来ていないが実際にサービスを start させてコンテナを起動させてみると 1 から 3 の数値がコンテナ名等に展開されていることから、サービス起動時に各ノードに割り当てられる番号を %i
という変数で参照することが出来るようだ。(※後述するが、厳密にはノードでは無くインスタンスに割り当てられた instance suffix を参照することが出来る)また、[X-Fleet]
セクションの X-Conflicts
オプションではユニット名をグロブマッチさせて同じユニットが起動しないように設定している。
詳細については Template unit files が詳しく解説されている。
上記例を kappa-test@.service というファイル名で保存しておいて、以降の手順を進める。
unit ファイルの登録と確認
先ほど保存した kappa-test@.service ファイルを fleetctl submit
コマンドを利用して fleet に登録する。
fleetctl submit kappa-test\@.service
登録内容(unit 一覧や内容)を確認する場合には以下のように確認する。
# list-unit-files で登録されている unit ファイル一覧を確認 core@core-01 ~ $ fleetctl list-unit-files --full UNIT HASH DSTATE STATE TARGET kappa-test@.service 559024d01378ac913e63c18dfd75f7a58d1f0bf8 inactive inactive - # cat で登録されている unit ファイルの内容を確認 core@core-01 ~ $ fleetctl cat kappa-test@.service [Unit] Description=kappa-test After=docker.service Requires=docker.service [Service] ExecStartPre=-/usr/bin/docker kill kappa-test-%i ExecStartPre=-/usr/bin/docker rm kappa-test-%i ExecStart=/usr/bin/docker run --name=kappa-test-%i --hostname=kappa-test-%i -p 8000 centos:centos6 /bin/sh -c 'cd /tmp/ ; hostname -s > index.html ; python -m SimpleHTTPServer' ExecStop=/usr/bin/docker stop kappa-test-%i [Install] WantedBy=multi-user.target [X-Fleet] X-Conflicts=kappa-test-@*.service
ちなみに、登録済みの内容を更新したい場合には以下のような手順が必要になる。上書きは出来ない。
# 一覧確認 core@core-01 ~ $ fleetctl list-unit-files --full UNIT HASH DSTATE STATE TARGET kappa-test@.service 559024d01378ac913e63c18dfd75f7a58d1f0bf8 inactive inactive - # destroy で削除 core@core-01 ~ $ fleetctl destroy kappa-test\@.service Destroyed kappa-test@.service # 既にサービスが起動している場合には stop でサービスを停止して unload してから destroy する # stop $ fleetctl stop kappa-test@{1..3}.service Unit kappa-test@1.service loaded on 7aa2819a.../172.17.8.101 Unit kappa-test@3.service loaded on e1103fee.../172.17.8.102 Unit kappa-test@2.service loaded on bc1eb30f.../172.17.8.103 # unload core@core-01 ~ $ fleetctl unload kappa-test\@{1..3}.service Unit kappa-test@1.service inactive Unit kappa-test@2.service inactive Unit kappa-test@3.service inactive # destroy core@core-01 ~ $ fleetctl destroy kappa-test\@.service Destroyed kappa-test@.service core@core-01 ~ $ fleetctl destroy kappa-test\@{1..3}.service Destroyed kappa-test@1.service Destroyed kappa-test@2.service Destroyed kappa-test@3.service # submit で再登録 core@core-01 ~ $ fleetctl submit kappa-test\@.service
サービスの起動
登録した unit のサービスを起動する。サービスの起動には fleetctl start
を利用する。
core@core-01 ~ $ fleetctl start kappa-test\@1.service Unit kappa-test@1.service launched on 7aa2819a.../172.17.8.101 core@core-01 ~ $ fleetctl start kappa-test\@2.service Unit kappa-test@2.service launched on bc1eb30f.../172.17.8.103 core@core-01 ~ $ fleetctl start kappa-test\@3.service Unit kappa-test@3.service launched on e1103fee.../172.17.8.102
fleetctl start
の引数として kappa-test\@1.service 〜 kappa-test\@3.service を渡して起動している。この引数については以下のような意味がある。
@
はテンプレートから起動する(テンプレート:kappa-test\@.service)1
〜3
はサービスを起動するインスタンス(CoreOS ノードと必ずイコールではない)を識別する為の任意につけた番号となり、unit ファイル内から%i
で参照することが出来る
fleetctl list-units
で確認する。
core@core-01 ~ $ fleetctl list-units UNIT MACHINE ACTIVE SUB kappa-test@1.service 7aa2819a.../172.17.8.101 active running kappa-test@2.service bc1eb30f.../172.17.8.103 active running kappa-test@3.service e1103fee.../172.17.8.102 active running
fleetctl status
でも確認する。
core@core-01 ~ $ fleetctl status kappa-test@{1..3}.service ● kappa-test@1.service - kappa-test Loaded: loaded (/run/fleet/units/kappa-test@1.service; linked-runtime; vendor preset: disabled) Active: active (running) since Sun 2015-08-09 23:15:50 UTC; 26min ago Process: 19214 ExecStartPre=/usr/bin/docker rm kappa-test-%i (code=exited, status=0/SUCCESS) Process: 19206 ExecStartPre=/usr/bin/docker kill kappa-test-%i (code=exited, status=0/SUCCESS) Main PID: 19220 (docker) CGroup: /system.slice/system-kappa\x2dtest.slice/kappa-test@1.service └─19220 /usr/bin/docker run --name=kappa-test-1 --hostname=kappa-test-1 -p 8000 centos:centos6 /bin/sh -c cd /tmp/ ; hostname -s > index.html ; python -m SimpleHTTPServer Aug 09 23:15:50 core-01 systemd[1]: Starting kappa-test... Aug 09 23:15:50 core-01 docker[19206]: kappa-test-1 Aug 09 23:15:50 core-01 docker[19214]: kappa-test-1 Aug 09 23:15:50 core-01 systemd[1]: Started kappa-test. ● kappa-test@2.service - kappa-test Loaded: loaded (/run/fleet/units/kappa-test@2.service; linked-runtime; vendor preset: disabled) Active: active (running) since Sun 2015-08-09 23:16:00 UTC; 25min ago Process: 20600 ExecStartPre=/usr/bin/docker rm kappa-test-%i (code=exited, status=0/SUCCESS) Process: 20591 ExecStartPre=/usr/bin/docker kill kappa-test-%i (code=exited, status=0/SUCCESS) Main PID: 20609 (docker) CGroup: /system.slice/system-kappa\x2dtest.slice/kappa-test@2.service └─20609 /usr/bin/docker run --name=kappa-test-2 --hostname=kappa-test-2 -p 8000 centos:centos6 /bin/sh -c cd /tmp/ ; hostname -s > index.html ; python -m SimpleHTTPServer Aug 09 23:16:00 core-03 systemd[1]: Starting kappa-test... Aug 09 23:16:00 core-03 docker[20591]: kappa-test-2 Aug 09 23:16:00 core-03 docker[20600]: kappa-test-2 Aug 09 23:16:00 core-03 systemd[1]: Started kappa-test. ● kappa-test@3.service - kappa-test Loaded: loaded (/run/fleet/units/kappa-test@3.service; linked-runtime; vendor preset: disabled) Active: active (running) since Sun 2015-08-09 23:16:10 UTC; 25min ago Process: 21104 ExecStartPre=/usr/bin/docker rm kappa-test-%i (code=exited, status=0/SUCCESS) Process: 21095 ExecStartPre=/usr/bin/docker kill kappa-test-%i (code=exited, status=0/SUCCESS) Main PID: 21110 (docker) CGroup: /system.slice/system-kappa\x2dtest.slice/kappa-test@3.service └─21110 /usr/bin/docker run --name=kappa-test-3 --hostname=kappa-test-3 -p 8000 centos:centos6 /bin/sh -c cd /tmp/ ; hostname -s > index.html ; python -m SimpleHTTPServer Aug 09 23:16:10 core-02 systemd[1]: Starting kappa-test... Aug 09 23:16:10 core-02 docker[21095]: kappa-test-3 Aug 09 23:16:10 core-02 docker[21104]: kappa-test-3 Aug 09 23:16:10 core-02 systemd[1]: Started kappa-test.
念のためにコンテナにアクセスして確認する。
# コンテナを確認 core@core-01 ~ $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e193b197dc14 centos:centos6 "/bin/sh -c 'cd /tmp 28 minutes ago Up 28 minutes 0.0.0.0:32771->8000/tcp kappa-test-1 # コンテナで起動している Web サーバーにアクセス core@core-01 ~ $ curl localhost:32771 kappa-test-1
サービス登録
せっかくなので registrator で
registrator を使って起動しているコンテナのサービスを etcd に登録してみる。
registrator の起動
registrator 自体も Docker コンテナで提供されているので以下のように起動する。
core@core-01 ~ $ docker run -d --name registrator -h $HOSTNAME -v /var/run/docker.sock:/tmp/docker.sock progrium/registrator -ip=172.17.8.101 etcd://172.17.8.101:4001/services core@core-02 ~ $ docker run -d --name registrator -h $HOSTNAME -v /var/run/docker.sock:/tmp/docker.sock progrium/registrator -ip=172.17.8.102 etcd://172.17.8.101:4001/services core@core-03 ~ $ docker run -d --name registrator -h $HOSTNAME -v /var/run/docker.sock:/tmp/docker.sock progrium/registrator -ip=172.17.8.103 etcd://172.17.8.101:4001/services
etcd のエンドポイントを指定する場合には etcd の leader ノードの IP を指定する必要があった。尚、leader ノードの確認は以下のように行う。
core@core-01 ~ $ curl http://127.0.0.1:4001/v2/leader http://172.17.8.101:7001
登録されたサービスを確認
# registrator によって登録されているサービスを確認 core@core-01 ~ $ curl -s GET http://127.0.0.1:4001/v2/keys/services | jq . { "action": "get", "node": { "key": "/services", "dir": true, "nodes": [ { "key": "/services/centos", "dir": true, "modifiedIndex": 81615, "createdIndex": 81615 }, { "key": "/services/docker-discover-1936", "dir": true, "modifiedIndex": 81796, "createdIndex": 81796 }, { "key": "/services/docker-discover-8000", "dir": true, "modifiedIndex": 81797, "createdIndex": 81797 } ], "modifiedIndex": 81615, "createdIndex": 81615 } } # centos というキーに登録されているようなので確認する core@core-01 ~ $ curl -s GET http://127.0.0.1:4001/v2/keys/services/centos | jq . { "action": "get", "node": { "key": "/services/centos", "dir": true, "nodes": [ { "key": "/services/centos/core-02:kappa-test-3:8000", "value": "172.17.8.102:32771", "modifiedIndex": 126869, "createdIndex": 126869 }, { "key": "/services/centos/core-01:kappa-test-1:8000", "value": "172.17.8.101:32771", "modifiedIndex": 126833, "createdIndex": 126833 }, { "key": "/services/centos/core-03:kappa-test-2:8000", "value": "172.17.8.103:32771", "modifiedIndex": 126851, "createdIndex": 126851 } ], "modifiedIndex": 81615, "createdIndex": 81615 } }
docker-discover でサービス検出
docker-discover とは
etcd に登録されているサービスの情報を元にして HAProxy の設定を自動で生成して HAProxy を再起動する...こちらも Docker コンテナ。HAProxy も同梱されている。
ちょっと修正
どうやら docker-discover は docker-register という、これまたコンテナが etcd に登録する情報を利用することが前提の作りになっているようなので registrator で登録した情報を利用しようとした場合に少し修正が必要だったので、fork させて頂いて修正したものが以下。
俺の docker-discover 起動
core@core-01 ~ $ docker run -d --net host --name docker-discover -e ETCD_HOST=172.17.8.101:4001 -e SERVICE_PORT=8000 -p 8000:8000 -p 1936:1936 -t inokappa/docker-discover core@core-02 ~ $ docker run -d --net host --name docker-discover -e ETCD_HOST=172.17.8.101:4001 -e SERVICE_PORT=8000 -p 8000:8000 -p 1936:1936 -t inokappa/docker-discover core@core-03 ~ $ docker run -d --net host --name docker-discover -e ETCD_HOST=172.17.8.101:4001 -e SERVICE_PORT=8000 -p 8000:8000 -p 1936:1936 -t inokappa/docker-discover
-e SERVICE_PORT=8000
で HAProxy で公開するポートを指定する。
確認
正常。
core@core-01 ~ $ curl localhost:8000 kappa-test-1 core@core-01 ~ $ curl localhost:8000 kappa-test-2 core@core-01 ~ $ curl localhost:8000 kappa-test-3
1 インスタンスを停止(サービスを停止)してみる。
core@core-01 ~ $ fleetctl stop kappa-test@2.service Unit kappa-test@2.service loaded on bc1eb30f.../172.17.8.103 core@core-01 ~ $ curl localhost:8000 kappa-test-3 core@core-01 ~ $ curl localhost:8000 kappa-test-1 core@core-01 ~ $ curl localhost:8000 kappa-test-3 core@core-01 ~ $ curl localhost:8000 kappa-test-1 core@core-01 ~ $
停止したインスタンスが復帰。
core@core-01 ~ $ fleetctl start kappa-test@2.service Unit kappa-test@2.service launched on bc1eb30f.../172.17.8.103 core@core-01 ~ $ curl localhost:8000 kappa-test-1 core@core-01 ~ $ curl localhost:8000 kappa-test-2 core@core-01 ~ $ curl localhost:8000 kappa-test-3 core@core-01 ~ $ curl localhost:8000 kappa-test-1 core@core-01 ~ $ curl localhost:8000 kappa-test-2 core@core-01 ~ $ curl localhost:8000 kappa-test-3
その他メモ
ssh-agent を利用する
以下のようなエラーが出てしまうことがあるが、これは fleetctl が ssh-agent を利用して他のクラスタノードにアクセスを行う為。
core@core-01 ~ $ fleetctl status kappa-test@2.service Error running remote command: SSH_AUTH_SOCK environment variable is not set. Verify ssh-agent is running. See https://github.com/coreos/fleet/blob/master/Documentation/using-the-client.md for help.
事前にノード間で公開鍵の交換を行っておく必要があり、以下のように ssh-agent も事前じ起動しておく必要がある。
# ssh-agent を実行 core@core-01 ~ $ ssh-agent SSH_AUTH_SOCK=/tmp/ssh-TyEESXG6GkAI/agent.20079; export SSH_AUTH_SOCK; SSH_AGENT_PID=20080; export SSH_AGENT_PID; echo Agent pid 20080; # 出力された内容を実行 core@core-01 ~ $ SSH_AUTH_SOCK=/tmp/ssh-TyEESXG6GkAI/agent.20079; export SSH_AUTH_SOCK; core@core-01 ~ $ SSH_AGENT_PID=20080; export SSH_AGENT_PID; core@core-01 ~ $ echo Agent pid 20080; Agent pid 20080 # 秘密鍵を登録する core@core-01 ~ $ ssh-add ~/.ssh/id_rsa Identity added: /home/core/.ssh/id_rsa (rsa w/o comment)
ssh-agent は初体験。
discovery.etcd.io にアクセスして token の取得を自動化(vagrant up 時に)
vagrant up
時に discovery.etcd.io にアクセスして token を取得するには config.rb を以下のように修正する。
$ diff -u config.rb.sample~ config.rb --- config.rb.sample~ Sat Mar 14 21:49:50 2015 +++ config.rb Mon Aug 10 14:47:12 2015 @@ -3,18 +3,19 @@ # To automatically replace the discovery token on 'vagrant up', uncomment # the lines below: # -#if File.exists?('user-data') && ARGV[0].eql?('up') -# require 'open-uri' -# require 'yaml' -# -# token = open($new_discovery_url).read -# -# data = YAML.load(IO.readlines('user-data')[1..-1].join) -# data['coreos']['etcd']['discovery'] = token -# -# yaml = YAML.dump(data) -# File.open('user-data', 'w') { |file| file.write("#cloud-config\n\n#{yaml}") } -#end +if File.exists?('user-data') && ARGV[0].eql?('up') + require 'open-uri' + require 'yaml' + require 'openssl' + + token = open($new_discovery_url, :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE).read + + data = YAML.load(IO.readlines('user-data')[1..-1].join) + data['coreos']['etcd']['discovery'] = token + + yaml = YAML.dump(data) + File.open('user-data', 'w') { |file| file.write("#cloud-config\n\n#{yaml}") } +end # #
まだまだ...
ザクッと
CoreOS や etcd ましては fleet 等はほぼ初体験でチンプンカンプンだったが、実際に動かすことでザクッと概要を掴めることが出来た。
勉強不足
fleet の unit を扱う場合に systemd の知識が必要になるのでちゃんと抑えておきたい。
サービス検出について
他のツールもありそうだが registrator を使うのは個人的な鉄板。今回検証した環境では etcd のエンドポイント指定が leader ノードの IP でないと正しくサービス登録が行われなかったのは謎。