Docker Swarmを使ってみた
Dockerおじさん業はまだまだ続きます。理想のかっこいいデプロイ環境を作りたいんですよ、こんてぃにゅーあすでぷろいですよ。
Docker Composeを使うとコンテナのスケールは簡単にできる訳ですが、負荷分散の面でいうとホスト自体も分けてスケーリングしたいところです。Dockerが提供するツール群には、Docker Swarmという複数のホストをあたかも一台のホストのように扱うためのツールがあります。Docker Swarmで管理している複数ホストにDocker Composeでコンテナをスケーリングし、フロントはリバースプロキシとして機能するnginxでロードバランスするという構成を試してみたいと思います。今回のゴールは以下のような構成です。
Docker Swarmで複数ホストを連携させる
Docker ToolboxまたはDocker Machineをインストール済の場合は特に新規ツールのインストールは必要ありません。Docker Machineと公開済コンテナイメージを使って、Docker Swarmを使った複数ホスト連携の構成を組むことができます。
連携管理のためのトークン発行
Docker Swarmは各ホスト間の連携管理をトークンを元に行います。Docker Swarmのツール類を専門に扱うためのホストを一つ作っておきましょう。
$ docker-machine create -d virtual box swarm-manager
ホストを作ったら、トークンを発行します。トークンの発行は以下のようにswarmイメージのcreateコマンドを叩きます。
$ docker run swarm create
発行されたトークンはファイルなどに保持して下さい。後ほど連携するホストを作成する際に必要になります。トークンの生成に必要なだけなので、生成後にこのホストは落としてしまっても問題ありません。
ちなみに、もう一回生成すると別のトークンが発行されます。新しい物を生成しても旧トークンは使えます。
マスタノードとなるホストを作る
Docker Swarmはホスト連携時にマスタノードとなるホストを作って、そのノードにアクセスすることで連携されている各ホストを一元的に扱うことが可能となります。マスタノードとなるホストは以下の様に作成します。
$ docker-machine create -d virtualbox --swarm --swarm-master --swarm-discovery token://<さっきのトークン> swarm-master
これから作るホストがマスタノードであることを示すため、 swarm-master
オプションを指定します。 swarm-discovery
オプションはDocker Swarmが連携先を探すための指定で、ここではトークンを使うので token://<生成したトークン>
という指定の仕方をします。
子ノードとなるホストを作る
マスタノードができたので、このホストと連携をするための子ノードとなるホストを作成します。子ノードの作成も、マスタノードと同じくDocker Machineから作成します。
$ docker-machine create -d virtualbox --swarm --swarm-discovery token://<さっきのトークン> swarm-agent-01
swarm-master
オプションがつかないだけで他の指定の仕方は同じです。当然のことながらトークンはマスタノードと同じものをセットして下さい。
ホストを確認してみる。
$ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM swarm-manager * virtualbox Running tcp://192.168.99.100:2376 swarm-agent-01 virtualbox Running tcp://192.168.99.102:2376 swarm-master swarm-master virtualbox Running tcp://192.168.99.101:2376 swarm-master (master)
SWARMの所にどこに関連づいていて、どれがマスタノードなのかわかるようになっています。
マスタノードでの情報確認
マスタノードの情報を見てみましょう。dockerコマンドを使うための環境変数をセットします。
$ docker-machine env --swarm swarm-master
この swarm
オプションをつけるのを忘れないで下さい。これをつけないと先ほど docker-machine ls
で確認したマスターノードのURLにつながる設定になります。このURLはマスターノードをシングル構成のDockerとして扱うためのものなので、Docker Swarmが扱えなくなります。
$ docker info Containers: 7 Images: 4 Role: primary Strategy: spread Filters: affinity, health, constraint, port, dependency Nodes: 2 swarm-agent-01: 192.168.99.102:2376 └ Containers: 1 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 1.022 GiB └ Labels: executiondriver=native-0.2, kernelversion=4.0.9-boot2docker, operatingsystem=Boot2Docker 1.8.1 (TCL 6.3); master : 7f12e95 - Thu Aug 13 03:24:56 UTC 2015, provider=virtualbox, storagedriver=aufs swarm-master: 192.168.99.101:2376 └ Containers: 6 └ Reserved CPUs: 0 / 1 └ Reserved Memory: 0 B / 1.022 GiB └ Labels: executiondriver=native-0.2, kernelversion=4.0.9-boot2docker, operatingsystem=Boot2Docker 1.8.1 (TCL 6.3); master : 7f12e95 - Thu Aug 13 03:24:56 UTC 2015, provider=virtualbox, storagedriver=aufs CPUs: 2 Total Memory: 2.043 GiB Name: 6a91ee91c135
Nodes
というセクションでDocker Swarmが管理しているノードの情報が出力されています。ちょっと結果を取り忘れたので割愛しますが、この状態で docker ps -a
とすると、Swarmが管理しているコンテナ情報を見ることができます。この中で、 COMMAND
が /swarm manage —tlsv
となっているものがDocker Swarmの本体のようなもので、先ほど環境変数にセットした接続先(DOCKER_HOST)のポートもこのコンテナにつながる様になっています。
ノードの増減
ノードを増やしたい時はどうしたら良いのでしょうか?試してみたところ同じトークンでそのまま増やせば良いのではないかと思います。作成後にdocker ps -aで見たらちゃんと増えているので、ポーリングしているのではないでしょうか?
逆にノードを減らしたい時はというと、これについてはノードを減らしたら自動で、という訳には行かないようでした。以下は別の検証環境で子ノードを二つ作り、一つのノードのVMを停止したときのSwarm Managerのログです。
$ docker logs 9b5188d49596 time="2015-08-18T05:34:21Z" level=info msg="Listening for HTTP" addr="0.0.0.0:3376" proto=tcp time="2015-08-18T05:34:23Z" level=info msg="Registered Engine swarm-old-master at 192.168.99.100:2376" time="2015-08-18T05:36:22Z" level=info msg="Registered Engine swarm-old-agent-01 at 192.168.99.101:2376" time="2015-08-18T05:42:42Z" level=info msg="Registered Engine swarm-old-agent-02 at 192.168.99.102:2376" time="2015-08-18T05:46:22Z" level=error msg="Flagging engine as dead. Updated state failed: Get https://192.168.99.102:2376/v1.15/containers/json?all=1&size=0: dial tcp 192.168.99.102:2376: i/o timeout" id="IGXZ:7GMK:FIUW:PRAM:MAFI:H4ZU:3SLB:LUP4:VA7Q:EZXV:VTW4:7WB2" name=swarm-old-agent-02 time="2015-08-18T05:46:55Z" level=error msg="Flagging engine as dead. Updated state failed: Get https://192.168.99.102:2376/v1.15/containers/json?all=1&size=0: dial tcp 192.168.99.102:2376: no route to host" id="IGXZ:7GMK:FIUW:PRAM:MAFI:H4ZU:3SLB:LUP4:VA7Q:EZXV:VTW4:7WB2" name=swarm-old-agent-02 time="2015-08-18T05:47:02Z" level=info msg="Removed Engine swarm-old-agent-02"
一時的に接続できない状況とノードを減らしたい状況の判断ができないのだと思います。ノードを減らした後にいったんマスタノードのdocker daemonを再起動したらエラーは出なくなりました(多分コマンドがあるけど確認してない)。
Docker Composeと連携してクラスタリングしたノードでスケーリングさせる
で、Docker Swarmを使ってこのようにしてクラスタリングしたホストに対して、先ほどの図のような構成をDocker Composeを使ってオーケストレーションしてみましょう。
アプリケーションが稼働するコンテナについてはさほど問題なくスケーリングできたのですが、ロードバランサについては、マスタノードにだけ立ててフロントエンドは集約する、という構成だったので少し確認と検証が必要でした。
docker-compose.yml
諸々検証した結果、以下のような形になりました。
lb: image: jwilder/nginx-proxy ports: - "80:80" volumes: - “DOCKER_CERT_PATH:DOCKER_CERT_PATH" environment: - constraint:node==swarm-master apps: image: private-registry:5000/appimage ports: - "8080" environment: SPRING_PROFILES_ACTIVE: demo VIRTUAL_HOST: nippoagent.swarmlb.com
lb
がロードバランサの設定です。volumes
にセットしているのは、Docker Swarmのマネージャアクセス時の環境変数 DOCKER_CERT_PATH
で得られるSSL秘密鍵のパスをセットして下さい(検証したときは環境変数でのセットができないので直接値で入れました)。environment
にはDocker Swarmに渡す条件を指定しています。ロードバランサはマスタノードに一つだけにしたかったので、作成可能なノードの制限をするために --constraint:node==swarm-master
と記載しています。
apps
がアプリケーションが稼働するコンテナの設定です。image
にセットしているのは先日試したプライベートレジストリにpushしたイメージです。 ports
にはアプリケーションのポートしか指定せずにホスト上のポートはランダムに割当たるようにしています。 VIRTUAL_HOST
がロードバランサ用の設定で、これにロードバランサが稼働するホストと同じホスト名をセットしておくと自動でリバースプロキシの対象としてくれます。
ロードバランサのコンテナ実行、アプリケーションのスケーリング
構成が出来上がったので、ロードバランサとアプリケーションを立ち上げてみましょう。なお、今回はオレオレ認証のプライベートレジストリを使用していますので、先日の設定は全てのノードに対して事前にしておく必要があります *1 。
mao-instantlife.hatenablog.com
まずはロードバランサの立ち上げです。ここ、色々とはまりまして試行錯誤した結果、下記コマンドであれば実行できました。
$ docker-compose pull $ docker-compose run -e DOCKER_HOST=${DOCKER_HOST} -e DOCKER_CERT_PATH=${DOCKER_CERT_PATH} -e DOCKER_TLS_VERIFY=${DOCKER_TLS_VERIFY} —service-port lb
デフォルトだと実行時の DOCKER_HOST
がDocker Swarm Managerではないホスト単体の方で実行されてしまいます。その状態だとSwarmの子ノードを参照することができずに、リバースプロキシする対象がマスタノードのみに限定されます。そのため、 DOCKER_HOST
にDocker Swarm ManagerのURLがセットされるようにする必要があります。また、これらのアクセスがTCPを使うため、 DOCKER_CERT_PATH
と DOCKER_TLS_VERIFY
の設定が必要になります。
また、 service-port
オプションですが、 docker-compose run
はデフォルトではホストのポートにマッピングされません(衝突防止のため)。ポートをホストにマッピングさせるためにはこのオプションを指定する必要があります。
今回、作っては消ししている検証環境で、VMのIPアドレスが変わってしまうためにコマンドで環境変数を指定する方向に落ち着きましたが、実際の環境で運用する場合は実サーバにSwarm環境を作るので、配布先さえ決まっていれば docker-compose.yml
に事前に書けるケースがほとんどかな、と思います。そういう環境であれば、上記ではなく以下のようにしてコンテナを実行できるはずです。
$ docker-compose up lb
アプリケーションのスケーリングは簡単です。
$ docker-compose scale apps=3
で、クラスタリングしたノードにコンテナが分配されます。
コンテナの分配について
ちなみに、昨日社内でざくっと概要を話したときに「コンテナ作る先はどうやって決めてるの?」という質問が出ましたが、わからなかったので質問された後にちょっと調べてみました。ノードの優先順位を決めるStrategiesの項目があったのでおそらくこれだと思います。
ドキュメントを見ている限りデフォルトはspreadで、これは立ち上がっているコンテナの少ないノードから優先的にコンテナを割り当てていくようです。リソースの状況が同じものは同じ優先度となり、この優先度からはランダムだと書いています。
これから
検証してみて、運用をイメージするともうちょっと確認したいこととか、運用の間を埋めるツールを整理したいところとか出てきますね。おじさん業はまだまだ続きそうです。これについては本当に運用に乗せたいと思ってるので、頑張ってみたい所です。
*1:どうやらイメージのマスターノードでの集約まではしておらず、全てのノードで同じ様にイメージをpullするみたいです。起動時に設定できるのかな。。。