Motivation
Hadoopの開発をしていると複数ノードを持ったクラスタが欲しくなる時がある。なんとなく全体的な挙動を見たいときとかHDFS Erasure Codingのような最低6つのノードがないと動かないような機能もある。
こんな時にEC2のような環境に自分でビルドしたHadoopを使ってクラスタ構築をする必要がでてくるのだけれど、通常これはビルド目的にしては大変めんどくさい上に時間がかかる。お金もかかる。VirtualBoxを使って手元で複数ノードを立ち上げることもできるがこれも時間がかかる。(Stormで同じようなことをやってみたことがある)
開発をしていると1行変えては試し、1文字変えては試しということがたまにある。その度に数十分待たされるという非常にストレスフルな開発を解決したかった。
A solution
というわけでDockerを使うと一定の動作環境が整ったisolateされたノードが数秒で立ち上がることがわかったのでこれを使ってHadoop Clusterを構築してみた。
作ったDocker imageがこちら
Hadoopのimage自体はSequenceIQのDocker imageをほぼそのまま使っている。ちょっと手を加えて点としてはcontainer同士のネットワーク周りの名前解決をさせるためにoption(-h)が必要なので起動scriptを整えた点。jarが入ったimageとmaster, slave用のimageは分けた。非常にシンプルに自分でビルドしたHadoopで簡単に複数ノードのclusterが立ち上がる。
On Linux
はじめから自分はMacで使うように作っていたのでLinuxではテストしていなかった。どうせ同じだろうとも思っていたが先日動かしてみると動かない。どうやらcontainer間の名前解決ができていないようだった。いろいろ試してみると以下のことがわかった。
- Mac上で動くdocker-machine(or docker daemon?)は同ネットワーク(bridge)に後から追加された他のcontainerのホスト名を自動で
/etc/hosts
に追記をしてくれる - Linuxで動くdocker daemonは後から追加された他のcontainerのホスト名を追記してはくれない
そのためなんとなく-h
だけ指定して起動させて動いていたMacとは違いLinuxでは各containerのIPとホスト名のひも付けができなくなる問題が起きていた。
Link
単一ホスト上でのDocker container同士をつなぐにはlinkという機能が一般的だけれど、実は今回はこいつは使えない。この機能はすでに立ち上がっているcontainerのホスト名のaliasを指定して環境変数、/etc/hosts
への追記などを行ってくれる機能だ。Hadoop clusterは通常Master -> Slaveの順番で立ち上げるがじつは両者ともお互いのホスト名の名前解決ができないといけない。MasterはSlaveから送られてきたヘルスチェックやブロックレポートにのっているホスト名を元にコマンドを送り返したりする。この時に
- Master -> Slave
の順で起動するとlinkの機能を使ってSlaveはMasterの情報を得られるが、MasterはSlaveのことを知ることはできない。Masterが起動した時はSlaveがいなかったからだ。この問題はもちろん順番を変えても解決しない。linkはお互いの名前解決をするようにはできていないらしい。
Resolve hostname
いろいろ試行錯誤した結果、名前解決をしたいんだったら/etc/hosts
が更新されればよいということで、Slave立ち上げたあとにIPとホスト名のひも付けを配布するようにした。
#!/usr/bin/env python
def get_ipaddress(host):
(code, out, err) = exec_process('sudo docker inspect {}'.format(host))
conf = json.loads(out)
return conf[0]['NetworkSettings']['IPAddress']
docker inspect
コマンドでそのcontainerの情報が取得できるのでcontainerがたちあがったあと、IPを収集して/etc/hosts
を配布する。これで問題は解決した。
実はこれを配布する時にはすでにMaster, Slaveが立ち上がっているのだけれど、ファイルを作成したりMapReduceを走らせないと直ちにMasterからSlaveにアクセスが行くことはないのでSlaveにだけMasterへのlinkを設定することで起動時のエラーも起きることがないようになっている。
Conclusion
結構泥臭いやり方になってしまったので、これを調べているときに見つけたfigを導入してみようかとも思っています。あと単一ホスト上でのcontainer同士の接続がもっと簡単になるような方法があればぜひ教えていただけると嬉しいです。
Thanks!
追記
MacとLinuxによる挙動の違いはplatformの違いではなくversionに依るものだった。
ubuntu $ docker --version
Docker version 1.9.1, build a34a1d5
mac $ docker --version
Docker version 1.8.3, build f4bf5c7
1.9.0からの挙動の違いはbugではなくdesign.
https://github.com/docker/docker/pull/17230
もともとdefault network(bridge)で/etc/hostsに自動追加されていた機能(implicit discovery)は意図されたものではなく、それが元に戻ったという認識みたい。
default networkでなく、user defined networkなら直ちにホスト同士繋がるか試してみたところ果たしてうまくいった。
[e387acc] Create user defined network for connecting each contaner immediately