Hadoop multi node cluster on Docker


# Motivation
Hadoopの開発をしていると複数ノードを持ったクラスタが欲しくなる時がある。なんとなく全体的な挙動を見たいときとか[HDFS Erasure Coding](http://blog.cloudera.com/blog/2015/09/introduction-to-hdfs-erasure-coding-in-apache-hadoop/)のような最低6つのノードがないと動かないような機能もある。

こんな時にEC2のような環境に自分でビルドしたHadoopを使ってクラスタ構築をする必要がでてくるのだけれど、通常これはビルド目的にしては大変めんどくさい上に時間がかかる。お金もかかる。VirtualBoxを使って手元で複数ノードを立ち上げることもできるがこれも時間がかかる。([Stormで同じようなことをやってみたことがある](https://github.com/Lewuathe/storm-devenv)）

開発をしていると1行変えては試し、1文字変えては試しということがたまにある。その度に数十分待たされるという非常にストレスフルな開発を解決したかった。

# A solution

というわけで[Docker](https://www.docker.com/)を使うと一定の動作環境が整ったisolateされたノードが数秒で立ち上がることがわかったのでこれを使ってHadoop Clusterを構築してみた。

作ったDocker imageがこちら

**[docker-hadoop-cluser](https://github.com/Lewuathe/docker-hadoop-cluster)**

Hadoopのimage自体は[SequenceIQ](https://github.com/sequenceiq/hadoop-docker)のDocker imageをほぼそのまま使っている。ちょっと手を加えて点としてはcontainer同士のネットワーク周りの名前解決をさせるためにoption(-h)が必要なので[起動script](https://github.com/Lewuathe/docker-hadoop-cluster/blob/master/bin/build_cluster.sh)を整えた点。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](https://docs.docker.com/v1.8/userguide/dockerlinks/)という機能が一般的だけれど、実は今回はこいつは使えない。この機能はすでに立ち上がっている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とホスト名のひも付けを配布するようにした。

```py
#!/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](http://www.fig.sh/)を導入してみようかとも思っています。あと単一ホスト上でのcontainer同士の接続がもっと簡単になるような方法があればぜひ教えていただけると嬉しいです。

Thanks!

# 追記

MacとLinuxによる挙動の違いはplatformの違いではなくversionに依るものだった。

```bash

ubuntu $ docker --version
Docker version 1.9.1, build a34a1d5
```

```bash
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)は意図されたものではなく、それが元に戻ったという認識みたい。

https://github.com/docker/docker/issues/17826

default networkでなく、user defined networkなら直ちにホスト同士繋がるか試してみたところ果たしてうまくいった。

[\[e387acc\] Create user defined network for connecting each contaner immediately](https://github.com/Lewuathe/docker-hadoop-cluster/commit/e387acc7b084535dae0990c0900b1e5527a32db8)