K2NR.ME

このエントリーをはてなブックマークに追加 Tweet

Dockerにおけるマルチホストでのコンテナ間リンク考察

最近dockerの事ばかり考えてます。 マルチホストでdockerを運用する際、真っ先にぶちあたる問題はコンテナ間の連携をどのようにして実現するか、あるいは広義にはService Discoveryをいかにして実現するか、ということだ。

いろいろ調べたり作ったりした結果わかったことをまとめておく。

シングルホストの場合

まずシングルホストの場合についておさらいする。

dockerでコンテナ間のリンクを行う方法はたくさんある。シングルホストであれば一般的なのはLinking Containersである。

まずリンク先となるDB用コンテナを起動する

docker run -d --name db training/postgres

次に、リンク元のwebアプリ用コンテナ起動する

docker run -d -P --name web --link db:db training/webapp python app.py

--link db:dbの部分がリンクの指定だ。先に起動したdbコンテナをここで起動したwebコンテナにdbという名前でリンクすることを意味している。 リンクすると、webコンテナには次のような環境変数がセットされて、dbコンテナへのアクセスが可能となる。

DB_NAME=/web/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5000_TCP=tcp://172.17.0.5:5432
DB_PORT_5000_TCP_PROTO=tcp
DB_PORT_5000_TCP_PORT=5432
DB_PORT_5000_TCP_ADDR=172.17.0.5

このリンク方法の問題は、マルチホストに渡るコンテナ間ではリンクできないことだ。単一のホスト内でコンテナの連携が完結するのであればこの手法でなんら問題はないのだが、実運用にdockerを使うとなると大抵はマルチホストでコンテナを分散させることになる。

Static Linking

ではホストを跨るコンテナ間のリンクはどうすればよいだろう。最も素朴な方法は以下で説明するStatic Linkingである。Static Linkingは僕が今名付けた。

例えばホストA(192.168.100.1)でredisのコンテナを次のように起動する。

docker run -d -p 5432:5432 --name db training/postgres

で、ホストBで起動するwebappコンテナから接続するのに、192.168.100.1:5432を直接指定して接続するというものだ。

この方法の問題点はご想像のとおり、

という点だ。

Static Ambassador

上記の問題を解決する手法として一般的なのはいわゆるAmbassadorパターンというやつだろう。

リンク先の説明が分かりやすいが、簡単にいえばconsumerコンテナから別ホストのredisコンテナへリンクする場合、

(consumer) --> (redis-ambassador) --> (redis)

となるように間にredis-ambassadorというコンテナを挟む。このredis-ambassadorconsumerコンテナと同一のホストで動作しており、redis-ambassador6379ポートへのアクセスをredisコンテナに転送するプロキシである。

consumerは最初に説明した方法でredis-ambassadorにリンクする。 このように間にambassadorを入れることでconsumerから見たらredis-ambassadorにリクエストを送るだけなのでredisコンテナは別ホストにいるかどうかを気にしなくてよい。redisコンテナの場所が変わる場合はredis-ambassadorの設定を変えて再起動すればよい。

すればよいのだが、この方法の問題はambassadorの設定が静的であることだ。上述のようにリンク先のコンテナの場所が変わる場合、それにあわせてambassadorの設定を変更・再起動しなければならない。

Dynamic Ambassador

このStatic Ambassadorの問題を解決するための手法がDynamic Ambassadorである。 簡単にいえば、Static Ambassadorで起動時に静的に指定していたリンク先の情報をetcd等のKVSに持たせて、Dynamic AmbassadorはKVSの変化を監視し、ルーティング情報を更新する。

Dynamic Ambassadorはいくつか実装がある

このDynamic Ambassadorを実現するにあたって重要なのは「誰が、いつ、KVSを更新するのか」ということだ。 コンテナのスタート時に同時にKVSを更新するようにデプロイスクリプトを組むというのがひとつだが、より良いと感じたのはregistratorによる手法。

registratorはdockerのイベントを監視し、コンテナの開始と停止のタイミングでコンテナの情報をdocker APIを使って取得、適切にKVSを更新する。この際、起動するコンテナの環境変数を利用して任意のメタデータを登録することも可能だ。

DNS Based Service Discovery

たとえばconsulskydnsといったDNSベースのサービスディスカバリツールを使ってマルチホストでのコンテナリンクを実現する手法だ。

consulよく分かってないのでskydnsを例に説明する。

skydnsは、etcdをバックエンドにしたDNSだ。たとえば以下のようにetcdに値を登録することで、1.rails.production.east.skydns.localservice1.example.com:8080に解決するSRVレコードを登録できる。

curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/production/rails/1 \
    -d value='{"host":"service1.example.com","port":8080}'

ちょうどいい実装が見つからなかったので、あくまで想像だが実現する手順としては

  1. このskydnsを各ホストで起動しておき、
  2. 各ホストで起動するコンテナのdnsをdocker run--dnsオプションでlocalhostのskydnsに向けるようにする。
  3. そして、上で紹介したregistratorと同様の仕組みを利用してDNSレコードを更新する。つまり、dockerコンテナの起動・停止イベントを監視してetcdを通じてskydnsのレコード情報を更新するコンテナを各ホストで動作させておく。このとき、自コンテナのドメインに関する情報はやはり環境変数にしていするのがよいだろう。

ちなみに、registratorではskydnsに対応の予定はあるようだが現時点では対応してない。

このようにすることで、各コンテナは予め取り決めたドメイン名を通じて任意のコンテナとやりとりすることができるようになり、さらにambassadorのような中間コンテナも不要となる。

まとめ

ということで、マルチホスト・マルチコンテナでのコンテナ間通信についてまとめてみた。 どの手法を選択するかというのは実際の要件によりけりだと思うが、「Dynamic Ambassador」と「DNS Based Service Discovery」が汎用性高そうでおすすめである。 実はほかにもKubernetesのproxyとか、ambassadordのomni modeとか、flynnのdiscoverdとか紹介したかったのだが力尽きた。

comments powered by Disqus