冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

Docker Swarmを使ってみた

Dockerおじさん業はまだまだ続きます。理想のかっこいいデプロイ環境を作りたいんですよ、こんてぃにゅーあすでぷろいですよ。

Docker Composeを使うとコンテナのスケールは簡単にできる訳ですが、負荷分散の面でいうとホスト自体も分けてスケーリングしたいところです。Dockerが提供するツール群には、Docker Swarmという複数のホストをあたかも一台のホストのように扱うためのツールがあります。Docker Swarmで管理している複数ホストにDocker Composeでコンテナをスケーリングし、フロントはリバースプロキシとして機能するnginxでロードバランスするという構成を試してみたいと思います。今回のゴールは以下のような構成です。

f:id:mao_instantlife:20150820100042p:plain

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_PATHDOCKER_TLS_VERIFY の設定が必要になります。

また、 service-port オプションですが、 docker-compose run はデフォルトではホストのポートにマッピングされません(衝突防止のため)。ポートをホストにマッピングさせるためにはこのオプションを指定する必要があります。

今回、作っては消ししている検証環境で、VMIPアドレスが変わってしまうためにコマンドで環境変数を指定する方向に落ち着きましたが、実際の環境で運用する場合は実サーバにSwarm環境を作るので、配布先さえ決まっていれば docker-compose.yml に事前に書けるケースがほとんどかな、と思います。そういう環境であれば、上記ではなく以下のようにしてコンテナを実行できるはずです。

$ docker-compose up lb

アプリケーションのスケーリングは簡単です。

$ docker-compose scale apps=3

で、クラスタリングしたノードにコンテナが分配されます。

コンテナの分配について

ちなみに、昨日社内でざくっと概要を話したときに「コンテナ作る先はどうやって決めてるの?」という質問が出ましたが、わからなかったので質問された後にちょっと調べてみました。ノードの優先順位を決めるStrategiesの項目があったのでおそらくこれだと思います。

Docker Swarm strategies

ドキュメントを見ている限りデフォルトはspreadで、これは立ち上がっているコンテナの少ないノードから優先的にコンテナを割り当てていくようです。リソースの状況が同じものは同じ優先度となり、この優先度からはランダムだと書いています。

これから

検証してみて、運用をイメージするともうちょっと確認したいこととか、運用の間を埋めるツールを整理したいところとか出てきますね。おじさん業はまだまだ続きそうです。これについては本当に運用に乗せたいと思ってるので、頑張ってみたい所です。

*1:どうやらイメージのマスターノードでの集約まではしておらず、全てのノードで同じ様にイメージをpullするみたいです。起動時に設定できるのかな。。。