これまでAmbari on Dockerはシングルホストで使っていました。ようやくMulti-host newtworkとDocker Swarmクラスタに慣れてきたので、AmbariとZeppelinにSparkの学習環境を移行していきます。
このシリーズ
- IDCFクラウドのDebian JessieでMulti-Host Docker Networking - Part 1: Overlayネットワークの作成
- IDCFクラウドのDebian JessieでMulti-Host Docker Networking - Part 2: Docker Swarmクラスタの作成
- IDCFクラウドのDebian JessieでMulti-Host Docker Networking - Part 3: OverlayネットワークとDocker SwarmとDocker ComposeでRethinkDBをスケールさせる
- IDCFクラウドのDebian JessieでMulti-Host Docker Networking - Part 4: Couchbase Serverをスケールしてみる
Docker Network
Part 3のRethinkDBやPart 4のCouchbase ServerではDocker Composeを使っていたので自動的にプロジェクト名のネットワークが作成されました。今回はDocker Composeを使わないため、直接ネットワークを作成します。zeppelin
という名前で、サブネットをフラグをつけて指定しました。
$ docker network create \
--subnet=192.168.0.0/24 \
--driver=overlay \
zeppelin
ambari-functions
ambari-functionsはAmbari on Dockerのセットアップ用関数などが入っています。Apache Zeppelinでデータ分析を分散処理する - Part 4: Ambari on Dockerのambari-functionsを使ってみるで中身を確認しました。
リポジトリからgit clone
してabari-functions
ファイルをDocker SwarmとDocker Networkの環境で動くように編集していきます。
$ git clone https://github.com/sequenceiq/docker-ambari/
$ cd docker-ambari
ネットワークを指定する
ネットワーク名を環境変数に定義します。クリア用のamb-clean
にも追加します。
: ${NETWORK:="zeppelin"}
...
amb-clean() {
unset NODE_PREFIX AMBARI_SERVER_NAME IMAGE DOCKER_OPTS CONSUL CONSUL_IMAGE DEBUG SLEEP_TIME AMBARI_SERVER_IP EXPOSE_DNS DRY_RUN NETWORK
}
docker run
は--net $NETWORK
フラグをつけてコンテナを起動するようにします。
_amb_run_shell() {
...
run-command docker run --net $NETWORK -it --rm -e EXPECTED_HOST_COUNT=$((NODES-1)) -e BLUEPRINT=$BLUEPRINT -e AMBARI_HOST=$AMBARI_SERVER_NAME --entrypoint /bin/sh $IMAGE -c $COMMAND
}
...
amb-start-first() {
...
run-command docker run -d --net $NETWORK $dns_port_command --name $CONSUL -h $CONSUL.service.consul $CONSUL_IMAGE -server -bootstrap
sleep 5
run-command docker run -d --net $NETWORK -e BRIDGE_IP=$(get-consul-ip) $DOCKER_OPTS --name $AMBARI_SERVER_NAME -h $AMBARI_SERVER_NAME.service.consul $IMAGE /start-server
...
amb-start-node() {
...
run-command docker run $MORE_OPTIONS --net $NETWORK -e BRIDGE_IP=$(get-consul-ip) $DOCKER_OPTS --name ${NODE_PREFIX}$NUMBER -h ${NODE_PREFIX}${NUMBER}.service.consul $IMAGE /start-agent
--net
フラグをつけて作成したコンテナはdocker inspect
するとNetworkSettings
フィールドの階層が増えます。コンテナのIPアドレスを取得するget-host-ip
を追加された階層に合わせて編集してます。
get-host-ip() {
HOST=$1
docker inspect --format="{{.NetworkSettings.Networks.$NETWORK.IPAddress}}" ${HOST}
}
コンテナの中でcurlを使う
デフォルトのdocker0
bridgeを使わないのでコンテナのIPアドレスには直接Dockerホストからアクセス出来ません。amb-members
などcurl
でConsulのAPIを実行していた箇所はdocker exec $CONSUL curl
のようにコンテナの中で使うようにします。
amb-members() {
docker exec $CONSUL curl http://$(get-consul-ip):8500/v1/catalog/nodes | sed -e 's/,{"Node":"ambari-8080.*}//g' -e 's/,{"Node":"consul.*}//g'
}
...
_consul-register-service() {
docker exec $CONSUL curl -X PUT -d "{
\"Node\": \"$1\",
\"Address\": \"$2\",
\"Service\": {
\"Service\": \"$1\"
}
}" http://$(get-consul-ip):8500/v1/catalog/register
}
編集した箇所のdiff
は以下のようになりました。
$ git diff ambari-functions
diff --git a/ambari-functions b/ambari-functions
index 8b07152..6e2f077 100644
--- a/ambari-functions
+++ b/ambari-functions
@@ -18,6 +18,7 @@ USAGE
: ${DNS_PORT:=53}
: ${EXPOSE_DNS:=false}
: ${DRY_RUN:=false}
+: ${NETWORK:="zeppelin"}
run-command() {
CMD="$@"
@@ -30,7 +31,7 @@ run-command() {
}
amb-clean() {
- unset NODE_PREFIX AMBARI_SERVER_NAME IMAGE DOCKER_OPTS CONSUL CONSUL_IMAGE DEBUG SLEEP_TIME AMBARI_SERVER_IP EXPOSE_DNS DRY_RUN
+ unset NODE_PREFIX AMBARI_SERVER_NAME IMAGE DOCKER_OPTS CONSUL CONSUL_IMAGE DEBUG SLEEP_TIME AMBARI_SERVER_IP EXPOSE_DNS DRY_RUN NETWORK
}
get-ambari-server-ip() {
@@ -43,11 +44,11 @@ get-consul-ip() {
get-host-ip() {
HOST=$1
- docker inspect --format="{{.NetworkSettings.IPAddress}}" ${HOST}
+ docker inspect --format="{{.NetworkSettings.Networks.$NETWORK.IPAddress}}" ${HOST}
}
amb-members() {
- curl http://$(get-consul-ip):8500/v1/catalog/nodes | sed -e 's/,{"Node":"ambari-8080.*}//g' -e 's/,{"Node":"consul.*}//g'
+ docker exec $CONSUL curl http://$(get-consul-ip):8500/v1/catalog/nodes | sed -e 's/,{"Node":"ambari-8080.*}//g' -e 's/,{"Node":"consul.*}//g'
}
amb-settings() {
@@ -95,7 +96,7 @@ _amb_run_shell() {
: ${COMMAND:? required}
get-ambari-server-ip
NODES=$(docker inspect --format="{{.Config.Image}} {{.Name}}" $(docker ps -q)|grep $IMAGE|grep $NODE_PREFIX|wc -l|xargs)
- run-command docker run -it --rm -e EXPECTED_HOST_COUNT=$((NODES-1)) -e BLUEPRINT=$BLUEPRINT --link ${AMBARI_SERVER_NAME}:ambariserver --entrypoint /bin/sh $IMAGE -c $COMMAND
+ run-command docker run --net $NETWORK -it --rm -e EXPECTED_HOST_COUNT=$((NODES-1)) -e BLUEPRINT=$BLUEPRINT -e AMBARI_HOST=$AMBARI_SERVER_NAME --entrypoint /bin/sh $IMAGE -c $COMMAND
}
amb-shell() {
@@ -123,10 +125,11 @@ amb-start-first() {
if [[ "$EXPOSE_DNS" == "true" ]]; then
dns_port_command="-p 53:$DNS_PORT/udp"
fi
- run-command docker run -d $dns_port_command --name $CONSUL -h $CONSUL.service.consul $CONSUL_IMAGE -server -bootstrap
+ run-command docker run -d --net $NETWORK $dns_port_command --name $CONSUL -h $CONSUL.service.consul $CONSUL_IMAGE -server -bootstrap
sleep 5
- run-command docker run -d -e BRIDGE_IP=$(get-consul-ip) $DOCKER_OPTS --name $AMBARI_SERVER_NAME -h $AMBARI_SERVER_NAME.service.consul $IMAGE /start-server
+ run-command docker run -d --net $NETWORK -e BRIDGE_IP=$(get-consul-ip) $DOCKER_OPTS --name $AMBARI_SERVER_NAME -h $AMBARI_SERVER_NAME.service.consul $IMAGE /start-server
+
get-ambari-server-ip
@@ -169,13 +172,13 @@ amb-start-node() {
MORE_OPTIONS="$@"
fi
- run-command docker run $MORE_OPTIONS -e BRIDGE_IP=$(get-consul-ip) $DOCKER_OPTS --name ${NODE_PREFIX}$NUMBER -h ${NODE_PREFIX}${NUMBER}.service.consul $IMAGE /start-agent
+ run-command docker run $MORE_OPTIONS --net $NETWORK -e BRIDGE_IP=$(get-consul-ip) $DOCKER_OPTS --name ${NODE_PREFIX}$NUMBER -h ${NODE_PREFIX}${NUMBER}.service.consul $IMAGE /start-agent
_consul-register-service ${NODE_PREFIX}${NUMBER} $(get-host-ip ${NODE_PREFIX}$NUMBER)
}
_consul-register-service() {
- curl -X PUT -d "{
+ docker exec $CONSUL curl -X PUT -d "{
\"Node\": \"$1\",
\"Address\": \"$2\",
\"Service\": {
Zeppelinのインストール
AmbariクラスタとZeppelinをインンストール方法はこちらとほぼ同じです。あとでHiveの設定も行います。
Swarm Manager
docker run
はSwarm Managerを指定して実行する必要があるのでDOCKER_HOST
環境変数にexportして使います。3333
ポートはPart 2で作成したSwarm Managerのポートです。
$ export DOCKER_HOST=tcp://10.3.0.101:3333
Ambariクラスタの作成
ambari-functions
を読み込みamb-start-cluster
を実行してAmbariクラスタをセットアップします。
$ source ambari-functions
$ amb-start-cluster 4
minion[1-3]
のDockerホストにそれぞれコンテナが作成されました。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f2a8699786d7 sequenceiq/ambari:2.2.0-v1 "/start-agent" 10 seconds ago Up 7 seconds 8080/tcp minion-1/amb3
43b8edf67263 sequenceiq/ambari:2.2.0-v1 "/start-agent" 11 seconds ago Up 10 seconds 8080/tcp minion-2/amb2
93b610b677a6 sequenceiq/ambari:2.2.0-v1 "/start-agent" 13 seconds ago Up 11 seconds 8080/tcp minion-3/amb1
3ba95ef16526 sequenceiq/ambari:2.2.0-v1 "/start-server" 15 seconds ago Up 12 seconds 8080/tcp minion-3/amb-server
b7c46370dba6 sequenceiq/consul:v0.5.0-v6 "/bin/start -server -" 21 seconds ago Up 19 seconds 53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8301-8302/udp, 8500/tcp minion-2/amb-consul
DockerネットワークとConsul DNS API
Hadoopクラスタなどでは各ノードの名前解決が必要になります。Docker SwarmとMulti-Host network環境での名前解決は/etc/hosts
を使います。-h
フラグでホスト名をつけてをコンテナを起動すると自分のhostnameはランダムのコンテナ名でなく指定したホスト名になります。
$ docker exec amb1 cat /etc/hosts
192.168.0.4 amb1.service.consul amb1
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.0.6 amb3
192.168.0.6 amb3.zeppelin
192.168.0.3 amb-server.zeppelin
192.168.0.2 amb-consul
192.168.0.2 amb-consul.zeppelin
192.168.0.3 amb-server
192.168.0.5 amb2
192.168.0.5 amb2.zeppelin
ConsulのDNS APIを使うとgliderlabs/registrator
などを使いConsulに登録したサービス名はservice.consul
をプリフィックスしてクエリすることができます。ambari-functions
では-h ${NODE_PREFIX}${NUMBER}.service.consul
のように-h
フラグで指定するホスト名はあらかじめservice.consul
をつけてコンテナを作成しています。ただし他のノードは上記のようにDockerネットワーク名がドメイン名としてプリフィックスされます。amb1.service.consul
からはamb2.service.consul
でなくamb2.zeppelin
になってしまいます。
/etc/resolv.conf
通常DockerコンテナはDockerホストの/etc/resolv.conf
コピーされます。以下はIDCFクラウドの場合です。
$ docker run -d -P --name=redis redis
$ docker exec redis cat /etc/resolv.conf
domain cs3d0idcfcloud.internal
search cs3d0idcfcloud.internal
nameserver 10.3.0.1
nameserver 158.205.237.131
nameserver 158.205.229.244
Ambari on Docker
ではConsul DNS APIを使いノード間の名前解決をするため/etc/resolv.conf
にConsulのノードを指定します。コンテナのCOMMANDに指定する[start-agent]
(https://github.com/sequenceiq/docker-ambari/blob/master/ambari-server/start-agent)スクリプトの中で`/etc/resolv.conf`を上書きしています。
$ docker exec amb1 cat /etc/resolv.conf
nameserver 192.168.0.2
search service.consul node.dc1.consul
ややこしいですがDNS用のConsulはDocker Swarmで使うため各DockerホストにセットアップしたConsulクラスタとは異なり、Docker Swarm上にある独立したConsulコンテナです。Consul DNS API用にDNSが8600でなく53ポートで使えるようにするなど、gliderlabs/docker-consulからforkしたsequenceiq/docker-consulを使っています。
public-hostname.sh
Multi-host network環境でのDNSは次の課題です。/etc/hosts
の本来の使い方はノードに閉じられているので、 $(hostname -f)
などで取得したホスト名をノードの外で使うのは良くないらしいのです。
update amb functions to use ambari 2.2.0
のコミットでambari-functions
が使うAmbari on Dockerのバージョンが2.2.0
になりました。
${IMAGE:="sequenceiq/ambari:2.2.0-v1"}
この変更でAmbari Agentの設定ファイル(/etc/ambari-agent/conf/ambari-agent.ini)で使うpublic-hostname.shがパブリックIPアドレスを使うようになりました。
[agent]
hostname_script=/etc/ambari-agent/conf/internal-hostname.sh
public_hostname_script=/etc/ambari-agent/conf/public-hostname.sh
#!/bin/bash
STATUS=$(curl -s -m 5 -w "%{http_code}" http://169.254.169.254/latest/meta-data/public-ipv4 -o /dev/null)
if [ "$STATUS" == "200" ]; then
curl -s -m 5 http://169.254.169.254/latest/meta-data/public-ipv4
else
dig +short myip.opendns.com @resolver1.opendns.com
fi
このスクリプトはAWS、GCE、Azureなどのクラウドではhostnameが取得できます。残念ながらIDCFクラウドでは動きません。IDCFクラウドのCloudStackの実装の場合は仮想マシンごとにパブリックIPアドレスが付いていません。dig
コマンドは仮想ルーターのパブリックIPアドレスが返ります。そのためAmbari AgentごとにIPアドレスが一意にならず、Ambari Shellを使いホストグループのアサインで失敗してしまいます。
IDCFクラウド用にDockerイメージをビルドしようと思いましたが、コンテナを起動した後にdocker exec
で入り修正することにしました。
$ docker exec -it amb1 bash
public-hostname.sh
を編集してhostname -f
を返すようにします。
#!/bin/bash
STATUS=$(curl -s -m 5 -w "%{http_code}" http://169.254.169.254/latest/meta-data/public-ipv4 -o /dev/null)
if [ "$STATUS" == "200" ]; then
curl -s -m 5 http://169.254.169.254/latest/meta-data/public-ipv4
else
# dig +short myip.opendns.com @resolver1.opendns.com
echo $(hostname -f)
fi
コンテナを再起動します。
$ docker restart amb1 amb2 amb3
この感じだとCloudbreakをIDCFクラウドに対応させるのは難しそうな気がしてきました。クラウドで簡単にHadoopクラスタを構築できるので便利なのですが。。
Ambari Shell
Ambari Shellを起動してBlueprintsを使いZeppelinをセットアップしていきます。
ambari-functions
のamb-shell
を使い起動します。
$ amb-shell
Ambari Shellで利用できるBlueprintの書き方は'slave_'
ではじまるホストグループが必要など制約があります。
private static final String SLAVE = 'slave_'
def slaves = groups.findAll { it.name.toLowerCase().startsWith(SLAVE) }
if (slaves) {
int k = 0
for (int j = i; j < hostSize; j++) {
result[slaves[k].name] = result[slaves[k].name] ?: []
result[slaves[k].name] << hostNames[j]
result << [(slaves[k].name): result[slaves[k++].name]]
k = k == slaves.size ? 0 : k
}
} else {
throw new InvalidHostGroupHostAssociation("At least one '$SLAVE' is required", groupSize)
}
同じBlueprintでもHortonworks GalleryにあるZeppelin Serviceは利用できません。CloudbreakのBlueprintのような書き方でないと実行時にエラーになります。
ambari-shell> blueprint add --url https://raw.githubusercontent.com/sequenceiq/cloudbreak/master/core/src/main/resources/defaults/blueprints/datascientist.bp
Ambariクラスタを構成するためのコンテナを確認します。
ambari-shell>host list
amb1.service.consul [HEALTHY] 192.168.0.4 centos6:x86_64
amb2.service.consul [HEALTHY] 192.168.0.5 centos6:x86_64
amb3.service.consul [HEALTHY] 192.168.0.6 centos6:x86_64
cluster build
コマンドで表示するHOSTNAME
はpublic-hostname.sh
を使っているようです。
ambari-shell>cluster build --blueprint datascientist
HOSTNAME STATE
------------------- -------------------
amb3.service.consul amb3.service.consul
amb1.service.consul amb1.service.consul
amb2.service.consul amb2.service.consul
autoAssign
でなく個別にホストをアサインしていきます。
CLUSTER_BUILD:datascientist>cluster assign --hostGroup master_1 --host amb1.service.consul
amb1.service.consul has been added to master_1
CLUSTER_BUILD:datascientist>cluster assign --hostGroup slave_1 --host amb2.service.consul
amb2.service.consul has been added to slave_1
CLUSTER_BUILD:datascientist>cluster assign --hostGroup client --host amb3.service.consul
amb3.service.consul has been added to client
create
してAmbariクラスタが完成されるのを待ちます。
CLUSTER_BUILD:datascientist>cluster create --exitOnFinish true
consul-templateとNginxのリバースプロキシ
Ambari on DockerではDockerコンテナのポートはDockerホストにマップしません。AmbariとZeppelinの管理画面にアクセスできるようにconsul-templateを使ってNginxのリバースプロキシを作ります。
シングルノードの時はローカルでビルドしたイメージを使い、consul-template用のテンプレートもDockerホストからボリュームをマウントしていました。Docker SwarmクラスタになるとローカルでビルドしたイメージやDockerホストのボリュームをマウントできなくなります。各ノードからdocker pull
できるようにリポジトリにイメージをおきます。
Docker Swarmでdocker pull
をするとすべてのノードでDockerイメージをダウンロードしてくれます。
$ export DOCKER_HOST=tcp://10.3.0.101:3333
$ docker pull masato/nginx-consul
Using default tag: latest
minion-2: Pulling masato/nginx-consul:latest... : downloaded
minion-1: Pulling masato/nginx-consul:latest... : downloaded
minion-3: Pulling masato/nginx-consul:latest... : downloaded
IDCFクラウドのパブリックIPアドレスのポートフォワードを特定の仮想マシンに固定したいので、-e constraint:node==minion-2
の制約を入れます。
NginxコンテナはAmbariクラスタ用のConsulコンテナを--dns
フラグの先頭に指定して起動します。
$ AMB_CONSUL=$(docker inspect --format="{{.NetworkSettings.Networks.zeppelin.IPAddress}}" amb-consul)
$ echo $AMB_CONSUL
192.168.0.2
$ docker run -d \
--name nginx \
--env constraint:node==minion-2 \
--env CONSUL_URL=$AMB_CONSUL:8500 \
--net zeppelin \
-p 8080:8080 -p 9995:9995 -p 9996:9996 \
--dns $AMB_CONSUL \
--dns 172.17.0.1 \
--dns 8.8.8.8 \
--dns-search service.consul \
masato/nginx-consul
ps
でDocker Swarmのコンテナの状態を確認します。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a81a30d32213 masato/nginx-consul "/bin/start.sh" About a minute ago Up About a minute 80/tcp, 10.3.0.102:8080->8080/tcp, 443/tcp, 10.3.0.102:9995-9996->9995-9996/tcp minion-2/berserk_swartz
b770b58d4b05 sequenceiq/ambari:2.2.0-v1 "/start-agent" 27 minutes ago Up 25 minutes 8080/tcp minion-3/amb3
df84975d8196 sequenceiq/ambari:2.2.0-v1 "/start-agent" 27 minutes ago Up 25 minutes 8080/tcp minion-2/amb2
7443b0b8c6ba sequenceiq/ambari:2.2.0-v1 "/start-agent" 27 minutes ago Up 25 minutes 8080/tcp minion-1/amb1
52118d892cb1 sequenceiq/ambari:2.2.0-v1 "/start-server" 27 minutes ago Up 27 minutes 8080/tcp minion-2/amb-server
383f3c220e8f sequenceiq/consul:v0.5.0-v6 "/bin/start -server -" 28 minutes ago Up 28 minutes 53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8500/tcp, 8301-8302/udp minion-3/amb-consul
Zeppelinの設定
ZeppwlinのHDFSとHiveの設定は以下を参考にして、NoteBookを実行する準備をします。