fig使って遊んでいたら、どうしても欲しくなり、Goでプログラム書きたかったこともあり、作ってみた。
実際にはこんな回りくどいことしなくても、既存のDNS使った、マシなソリューションがあると思う。参考
まあ200行くらいで済んだし、これはこれでいいかなー。
go-docker-dns
原理は死ぬほど単純で、dockerのリモートAPIをひたすら見守り続けて、コンテナの作成イベントを捉えたら、そのホスト名とIPの組を登録。逆にコンテナ停止のイベントを捉えたら、その情報を削除する。これとは別のDNSのスレッドが動いていて(ほとんど流用だけどっ)、dockerコンテナからの要求に応じて、上で登録したホスト名-IPの組を見つけたら、そのIPを回答している。
逆引きは実装していない。結構簡単に実装できそうだけど、現状でも用途には応えているから作る理由もない…。
もともとfigを使って、普段使っているPC上で比較的大きなシステム(WebAPI x MQ x KVS x RDB x LB)のはりぼてみたいなのを作り、その動作を検証するといったことをやりたかった。
この時、DNSがいないとクラスタを組む類のサービス(典型的には、今回変なDNSを実装するきっかけになったRabbitMQがスケール時にまともに動かない。
RabbitMQの場合:masterノードとslaveノードを用意し、masterノードに対して複数のslaveを登録する仕組みを今回作ったが、
fig scale mqslave=3
としたときに、docker --link
の仕組みでmasterノードのIPアドレスは解決できるが、他のslaveノードのIPアドレスはDNSがいないと解決のしようがない。
他の人のアプローチは、もっとslaveノードが欲しかったら、
fig.yml
rabbit:
image: bijukunjummen/rabbitmq-server
hostname: rabbit_1
ports:
- "5672:5672"
- "15672:15672"
mqnode1:
image: bijukunjummen/rabbitmq-server
hostname: mqnode_1
environment:
- CLUSTERED=true
- CLUSTER_WITH=rabbit_1
- RAM_NODE=true
links:
- rabbit
mqnode2:
image: bijukunjummen/rabbitmq-server
hostname: mqnode_2
environment:
- CLUSTERED=true
- CLUSTER_WITH=rabbit_1
- RAM_NODE=true
links:
- rabbit
- mqnode1
...
こんな感じに増やしていけばいいじゃない!って言ってるけど、流石に「ス、スケールとは一体」…って言いたくなる。fig scale mqnode=3
ってやったら1 master x 3 slaves の構成でクラスタが構築できてほしい。
コンテナ用のDNSサーバーを立てることで、わざわざfig.yml
にlinks
を記載しなくても、scale
時に名前解決してくれるようになる。
ここでもう一つ面倒な話があって、(RabbitMQでは)コンテナのホスト名がクラスタを組むときに大事になってくる。具体的にいうとfig
は、scale
して増やすコンテナのホスト名をfig.yml
で記述されたものを使う。fig scale mqnode=3
とやると、mqnode
というホスト名を持つコンテナが3つできるorz じゃあ、どやって名前解決してるの?っていうと、これとは別に環境変数env
(docker exec <コンテナID> echo env
で覗ける)と/etc/hosts
に、link
のところで指定された、関係のあるコンテナのIPと外側向けホスト名みたいのを列挙している。(この辺はdocker
側の実装。)
この状況だと、RabbitMQのmasterノードはslaveノードをクラスタに参加させる時に、「お前、slave_1っていう名前で参加しようとしてきたのに、実際にはslaveじゃん。」みたいな事を指摘して参加が上手くいかない。頑張って参加するときの名前とホスト名を一致させても、今度は2つめ以降のslaveが参加するときにホスト名が被る。つまり、1ノードだけが運よく参加できて、他は今参加しているノードが落ちるまで参加できない…\(^o^)/ いや、ある意味、可用性あるけど、そうじゃないいいいいいい。
fig改悪?版
という訳でfig本体にもしぶしぶ手を入れた。具体的には上に書いたようにホスト名をscale
を考慮した形で生成する。
以下のようなfig.yml
があったとする。
mqnode:
image: bijukunjummen/rabbitmq-server
hostname: mqnode
environment:
- CLUSTERED=true
- CLUSTER_WITH=rabbit_1
- RAM_NODE=true
fig up -d mqnode
とやると、コンテナが1つだけ立ち上がる。ホスト名はmqnode
ではなく、mqnode_1
。続いてfig scale mqnode=3
。これで新しいコンテナが2つ立ち上がる。ホスト名はそれぞれmqnode_2
, mqnode_3
。自作したDNSサーバーは、コンテナが作成されたイベントを検知すると、このホスト名とIPの組をレコードとして登録する。IPアドレスが10.10.0.1
から始まってたとすると、こういうレコード。
mqnode_1 10.10.0.1
mqnode_2 10.10.0.2
mqnode_3 10.10.0.3
RabbitMQのクラスタ構築時の動きが
- slaveノードが起動する
- slaveノードがmasterノードに自身の存在を通知
- masterノードが参加してきたslaveノードに他のslaveの存在を教える
- slaveノードが教えてもらった他のslaveノードにアクセスを試みる
ってな感じになってるから、fig
側の修正とDNSサーバの合わせ技で、この振る舞いが正しく機能するようになった。
オマケ
開発中は、必要に応じてコンテナを頻繁に作り直すことになると思うんだけど、fig up service
の現在の実装がとても気に入らない。
fig up -d service
fig scale service=3
こうすると、ホスト名、およびコンテナ名がservice_1, service_2, service_3
となり、これはいい。でも、この後、もう一度
fig up -d service
ってやると、service_1, service_2, service_3
が削除され、service_3, service_4, service_5
が新たに立ち上がる。なんで番号が増えんのよorz
別途立ち上げていたnginx
コンテナにロードバランスの設定を
upstream webapp {
server service_1;
server service_2;
server service_3;
}
こんな感じに書いてるものだから、コンテナを作り直す度にnginx
の設定を書き直さなきゃならなくなる。流石に気が滅入ってくるorz ので、fig up -d service
した時に既存のサービスが立ち上がっているなら、そのホスト名で作り直すように変更。better_recreateって名前のブランチで、ここまでに書いたfig側の変更を全て反映している。
まとめ
Go
の練習も兼ねてdocker用の超絶シンプルなDNSを作成fig
を使ってRabbitMQ
クラスタを自動構築できるようにしたfig
のコードをちょっと変えてrecreate
時にホスト名を引き継げるようにした
ここまでやって、メモリ4G程度のゲストマシン(CoreOS)上に計18のサービス(Rails App 2種類, KVS, RDB, Node.js App, Nginx)が協調動作する環境を構築できた。全部動いていると、だいたい3.8Gくらいメモリを消費。
なんやかんや問題も多いわけだけど、fig
みたいのを使うと、これまで鯖を何台も用意、provisonerなんかを使って構築していた比較的大きいシステムを、(ハリボテだけど)各開発者が自分のマシン上に持てるようになる、これで開発効率上がるといいな、とか思ったり思わなかったり。ここまでできると、本番環境へのデプロイを同じ設定ファイル使いまわしで実現したくなるね。
No comments:
Post a Comment