Apache Zeppelinでデータ分析を分散処理する - Part 2: Ambari on DockerにZeppelinをセットアップする

## このシリーズ

* [Apache Zeppelinでデータ分析を分散処理する - Part 1: データ分析のライフサイクル](http://qiita.com/masato/items/b66930a2ab8d888df5ff)
* [Apache Zeppelinでデータ分析を分散処理する - Part 2: Ambari on DockerにZeppelinをセットアップする](http://qiita.com/masato/items/911e06fd31202181f0fd)
* [Apache Zeppelinでデータ分析を分散処理する - Part 3: ZeppelinでHiveを使えるようにする](http://qiita.com/masato/items/78a45cf9b6984552d4d9)
* [Apache Zeppelinでデータ分析を分散処理する - Part 4: Ambari on Dockerのambari-functionsを使ってみる](http://qiita.com/masato/items/b879b84486aeb7d3d6d8)


## Ambari on Docker
　[Apache Ambari](https://ambari.apache.org/)でHadoopクラスタをDocker上にセットアップしてみます。AmbariはHadoop管理プラットフォームです。ブラウザの管理画面からHadoopのプロビジョニングや監視を行うことができますが、今回は[Ambari Shell](https://github.com/sequenceiq/ambari-shell)のCLIから[HDP](http://jp.hortonworks.com/hdp/)をクラスタをセットアップしてみます。

　[SequenceIQ](http://sequenceiq.com/)が公開している[sequenceiq/ambari](https://hub.docker.com/r/sequenceiq/ambari/)のDockerイメージをpullします。リポジトリは[docker-ambari](https://github.com/sequenceiq/docker-ambari)です。

```bash
$ docker pull sequenceiq/ambari
```

　[docker-ambari](https://github.com/sequenceiq/docker-ambari)をダウンロードしてシェルスクリプトを読み込みます。

```bash
$ git clone https://github.com/sequenceiq/docker-ambari
$ cd docker-ambari
$ source ambari-functions
```

　[ambari-functions](https://github.com/sequenceiq/docker-ambari/blob/master/ambari-functions)には`amb-*`関数が定義されています。Ambari on Dockerのセットアップ前に環境変数を確認してみます。

```bash
$ amb-settings
  NODE_PREFIX=amb
  CLUSTER_SIZE=3
  AMBARI_SERVER_NAME=amb-server
  IMAGE=sequenceiq/ambari:2.1.2-v1
  DOCKER_OPTS=
  AMBARI_SERVER_IP=
  CONSUL=amb-consul
  CONSUL_IMAGE=sequenceiq/consul:v0.5.0-v6
  DRY_RUN=false
```

　Dockerに4ノードのクラスタを構築します。4ノードのうち1ノードはAmbari Serverになります。そのためHadoopクラスタは3ノードのコンテナで構成されます。

```bash
$ amb-start-cluster 4
```

 コンテナが起動すると`AMBARI_SERVER_IP`環境変数にAmbari ServerコンテナのIPアドレスが設定されます。

```bash
$ amb-settings
  NODE_PREFIX=amb
  CLUSTER_SIZE=3
  AMBARI_SERVER_NAME=amb-server
  IMAGE=sequenceiq/ambari:2.1.2-v1
  DOCKER_OPTS=
  AMBARI_SERVER_IP=172.17.0.3
  CONSUL=amb-consul
  CONSUL_IMAGE=sequenceiq/consul:v0.5.0-v6
  DRY_RUN=false
```

　Ambariの[REST API](https://github.com/apache/ambari/blob/trunk/ambari-server/docs/api/v1/index.md)をcurlコマンドからAmbariが管理しているホストの情報を確認してみます。

```bash
$ curl -u admin:admin  http://$AMBARI_SERVER_IP:8080/api/v1/hosts
{
  "href" : "http://172.17.0.3:8080/api/v1/hosts",
  "items" : [
    {
      "href" : "http://172.17.0.3:8080/api/v1/hosts/amb1.service.consul",
      "Hosts" : {
        "host_name" : "amb1.service.consul"
      }
    },
    {
      "href" : "http://172.17.0.3:8080/api/v1/hosts/amb2.service.consul",
      "Hosts" : {
        "host_name" : "amb2.service.consul"
      }
    },
    {
      "href" : "http://172.17.0.3:8080/api/v1/hosts/amb3.service.consul",
      "Hosts" : {
        "host_name" : "amb3.service.consul"
      }
    }
  ]
}
```

　別のシェルから実行するなど`AMBARI_SERVER_IP`環境変数が読み込まれていない場合は手動で設定します。

```bash
$ AMBARI_SERVER_IP=$(docker inspect --format="{{ .NetworkSettings.IPAddress }}" amb-server)
```



## Ambari Shell

 [Ambari Shell](https://github.com/sequenceiq/ambari-shell)にも[sequenceiq/ambari-shell](https://hub.docker.com/r/sequenceiq/ambari-shell/)Dockerイメージが公開されています。リポジトリは[docker-ambari-shell](https://github.com/sequenceiq/docker-ambari-shell)です。今回は[sequenceiq/ambari](https://hub.docker.com/r/sequenceiq/ambari/)に入っている[ambari-shell.sh](https://github.com/sequenceiq/docker-ambari/blob/master/ambari-server/ambari-shell.sh)からAmbari Shellを起動します。

```bash
$ docker run -it --rm \
  --link amb-server:ambariserver \
  --entrypoint /bin/sh \
  sequenceiq/ambari \
  ./ambari-shell.sh
```

　Ambari Shellが起動しました。

```bash
AMBARI_HOST=172.17.0.3
[DEBUG] waits for ambari server: 172.17.0.3 RUNNING ...

[DEBUG] waits until 1 hosts connected to server ...
[DEBUG] connected hosts: 3
    _                _                   _  ____   _            _  _
   / \    _ __ ___  | |__    __ _  _ __ (_)/ ___| | |__    ___ | || |
  / _ \  | '_ ` _ \ | '_ \  / _` || '__|| |\___ \ | '_ \  / _ \| || |
 / ___ \ | | | | | || |_) || (_| || |   | | ___) || | | ||  __/| || |
/_/   \_\|_| |_| |_||_.__/  \__,_||_|   |_||____/ |_| |_| \___||_||_|

Welcome to Ambari Shell. For command and param completion press TAB, for assistance type 'hint'.
ambari-shell>
```

### Blueprint

 [Blueprint](https://cwiki.apache.org/confluence/display/AMBARI/Blueprints)はHadoopクラスタをJSON形式で定義します。Web画面のウィザードを使わなくてもREST APIやAmbari Shellからクラスタのプロビジョニングを行うことができます。


### CloudbreakのBlueprint

　[SequenceIQ](http://sequenceiq.com/)の[Cloudbreak](http://hortonworks.com/hadoop/cloudbreak/)[Blueprints](http://sequenceiq.com/cloudbreak-docs/latest/blueprints/)にいくつかBlueprintの例が掲載されています。CloudbreakはAmbariをベースにしたクラウド向けのHadoop管理ツールです。Azure、AWS、GCP、OpenStackのクラウドとDockerにも対応しています。ドキュメントを読むとAmbari Blueprintだけでも使えそうです。


### datascientistをセットアップ

　Cloudbreakの[リポジトリ](https://github.com/sequenceiq/cloudbreak/tree/master/core/src/main/resources/defaults/blueprints)から[datascientist.bp](https://github.com/sequenceiq/cloudbreak/blob/master/core/src/main/resources/defaults/blueprints/datascientist.bp)から、Dockerコンテナで構成したAmbariノードにHDPクラスタをプロビジョニングします。

　デフォルトで登録されている`single-node-hdfs-yarn`を適用したプロビジョニングの流れは次のようになります。

```bash
blueprint defaults
blueprint list
cluster build --blueprint single-node-hdfs-yarn
cluster autoAssign
cluster create
tasks
```

　先ほど起動したAmbari Shellの中で`datascientist`のBlueprintのURLを登録します。

```bash
ambari-shell> blueprint add --url https://raw.githubusercontent.com/sequenceiq/cloudbreak/master/core/src/main/resources/defaults/blueprints/datascientist.bp
Blueprint: 'datascientist' has been added
```

　登録した`datascientist`をビルドします。Ambariホストの情報とBlueprintにて疑義されたホストグループが表示されます。

```bash
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

  HOSTGROUP  COMPONENT
  ---------  ----------------------
  client     TEZ_CLIENT
  client     YARN_CLIENT
  client     HIVE_CLIENT
  client     HDFS_CLIENT
  client     PIG
  client     MAPREDUCE2_CLIENT
  client     ZOOKEEPER_CLIENT
  client     HCAT
  client     METRICS_COLLECTOR
  client     SPARK_CLIENT
  client     METRICS_MONITOR
  client     HBASE_CLIENT
  slave_1    HBASE_REGIONSERVER
  slave_1    NODEMANAGER
  slave_1    DATANODE
  slave_1    METRICS_MONITOR
  master_1   RESOURCEMANAGER
  master_1   JOURNALNODE
  master_1   WEBHCAT_SERVER
  master_1   MYSQL_SERVER
  master_1   ZOOKEEPER_CLIENT
  master_1   HISTORYSERVER
  master_1   SECONDARY_NAMENODE
  master_1   METRICS_MONITOR
  master_1   NAMENODE
  master_1   ZEPPELIN_MASTER
  master_1   TEZ_CLIENT
  master_1   YARN_CLIENT
  master_1   MAPREDUCE2_CLIENT
  master_1   HDFS_CLIENT
  master_1   HIVE_SERVER
  master_1   HBASE_MASTER
  master_1   PIG
  master_1   SPARK_JOBHISTORYSERVER
  master_1   HIVE_METASTORE
  master_1   ZOOKEEPER_SERVER
  master_1   APP_TIMELINE_SERVER
```

　`cluster autoAssign`を実行するとホストグループの定義に応じて自動的にAmbariホストがアサインされます。今回は3つのホストグループに1つずつ手動でホストをアサインしていきます。

```bash
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
```

　最後に`cluster create`コマンドを実行してHDPクラスタのプロビジョニングを開始します。

```bash
CLUSTER_BUILD:datascientist> cluster create --exitOnFinish true
```

　`tasks`コマンドを実行すると各コンポーネントのインストール状況がわかります。またシェルの右上に完了までの全体のパーセントが表示されます。IDCFクラウドの`standard.L16` (4 CPU x 2.4 GHz 16 GB RAM)の仮想マシンでは13分ほどかかりました。


## ConsulとNginxのリバースプロキシ

　Hadoopクラスタのサービスディスカバリには[Consul](https://www.consul.io/)を使っています。Dockerイメージは[sequenceiq/consul](https://hub.docker.com/r/sequenceiq/consul/)、リポジトリは[gliderlabs/docker-consul](https://github.com/gliderlabs/docker-consul)からforkした[sequenceiq/docker-consul](https://github.com/sequenceiq/docker-consul)です。
 

psコマンドでコンテナのポートを確認します。

```bash
$ docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                                                              NAMES
ce146ebf73f1        sequenceiq/ambari:2.1.2-v1    "/start-agent"           12 minutes ago      Up 12 minutes       8080/tcp                                                           amb3
9c23d449cb83        sequenceiq/ambari:2.1.2-v1    "/start-agent"           12 minutes ago      Up 12 minutes       8080/tcp                                                           amb2
162662822eb1        sequenceiq/ambari:2.1.2-v1    "/start-agent"           12 minutes ago      Up 12 minutes       8080/tcp                                                           amb1
6729936f59f8        sequenceiq/ambari:2.1.2-v1    "/start-server"          12 minutes ago      Up 12 minutes       8080/tcp                                                           amb-server
02ee7f29a74e        sequenceiq/consul:v0.5.0-v6   "/bin/start -server -"   12 minutes ago      Up 12 minutes       53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8500/tcp, 8301-8302/udp   amb-consul
```

　上記のようにDockerコンテナのポートはDockerホストにマップしていません。そのためDockerホストからしかDockerコンテナのIPアドレスへアクセスができません。今回の環境はIDCFクラウド上の仮想マシンなので、パブリックIPアドレスからAmbariとZeppelinの管理画面にアクセスできるようにNignxコンテナでリバースプロキシを作ります。


### Consul

　Ambari ServerのREST APIのJSONを見やすくするために[jq](https://stedolan.github.io/jq/)をDockerホストにインストールします。

```bash
$ wget -P /usr/local/bin jq http://stedolan.github.io/jq/download/linux64/jq
$ chmod u+x /usr/local/bin/jq
$ which jq
/usr/local/bin/jq
```

 Consulクラスタのノードを表示してみます。

```bash
$ AMB_CONSUL=$(docker inspect --format="{{ .NetworkSettings.IPAddress }}" amb-consul)
$ echo $AMB_CONSUL
172.17.0.2
$ curl -s $AMB_CONSUL:8500/v1/catalog/nodes | jq .
[
  {
    "Node": "amb-consul.service.consul",
    "Address": "172.17.0.2"
  },
  {
    "Node": "amb-server",
    "Address": "172.17.0.3"
  },
  {
    "Node": "amb1",
    "Address": "172.17.0.4"
  },
  {
    "Node": "amb2",
    "Address": "172.17.0.5"
  },
  {
    "Node": "amb3",
    "Address": "172.17.0.6"
  },
  {
    "Node": "ambari-8080",
    "Address": "172.17.0.3"
  }
]
```

　Consulに登録されたサービスを表示します。

```bash
$ curl -s $AMB_CONSUL:8500/v1/catalog/services | jq .
{
  "amb-server": [],
  "amb1": [],
  "amb2": [],
  "amb3": [],
  "ambari-8080": [],
  "consul": []
}
```

　amb-consulサービスの詳細を表示します。

```bash
$ curl -s $AMB_CONSUL:8500/v1/catalog/service/consul | jq .
[
  {
    "Node": "amb-consul.service.consul",
    "Address": "172.17.0.2",
    "ServiceID": "consul",
    "ServiceName": "consul",
    "ServiceTags": [],
    "ServiceAddress": "",
    "ServicePort": 8300
  }
]
```

　digコマンドでConsulのDNSサービスを見ることもできます

```bash
$ dig @$AMB_CONSUL -p 53 amb-consul.service.consul

; <<>> DiG 9.9.5-9+deb8u3-Debian <<>> @172.17.0.2 -p 53 amb-consul.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 65241
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;amb-consul.service.consul.     IN      A

;; Query time: 1 msec
;; SERVER: 172.17.0.2#53(172.17.0.2)
;; WHEN: Sat Dec 05 21:47:03 JST 2015
;; MSG SIZE  rcvd: 43
```

### Consul TempateとNginx

　[Consul Template](https://github.com/hashicorp/consul-template)を使いDockerコンテナの情報からNginxの設定ファイルを生成します。[jlordiales/nginx-consul](https://hub.docker.com/r/jlordiales/nginx-consul/)のDockerイメージが公開されています。今回はAmbari用にリポジトリを[fork](https://github.com/masato/docker-nginx-consul)しました。

 以下のような[Dockerfile](https://github.com/masato/docker-nginx-consul/blob/master/Dockerfile)からDockerイメージをビルドします。

```bash
FROM nginx:latest

ENV CONSUL_TEMPLATE_VERSION 0.11.0
ENV CONSUL_URL consul:8500

ADD start.sh /bin/start.sh
RUN rm -v /etc/nginx/conf.d/*.conf

ADD https://github.com/hashicorp/consul-template/releases/download/v${CONSUL_TEMPLATE_VERSION}/consul_template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip /tmp/consul-template.zip

RUN apt-get update && apt-get install unzip -y && \
    unzip -j -d /usr/local/bin /tmp/consul-template.zip

EXPOSE 8080 9995 9996
VOLUME /templates

CMD ["/bin/start.sh"]
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
```

 Consul Templateのテンプレートは[service.ctmpl](https://github.com/masato/docker-nginx-consul/blob/master/service.ctmpl)です。9995と9996はZeppelinサーバーが起動している`amb1`ノードにプロキシーします。9996ポートはWebSocket用にヘッダーも追加します。8080ポートはAmb Serverコンテナが起動している`amb-server`ノードにプロキシーします。
 
```bash
server {
  listen 9995;
 
  location / {
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-Host $host;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_pass         http://{{with node "amb1"}}{{.Node.Address}}{{end}}:9995;
  }
}

server {
  listen 9996;

  location / {
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-Host $host;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_pass         http://{{with node "amb1"}}{{.Node.Address}}{{end}}:9996;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection "upgrade";
    proxy_read_timeout 86400;
  }
}

server {
  listen 8080;

  location / {
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-Host $host;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_pass         http://{{with node "amb-server"}}{{.Node.Address}}{{end}}:8080;
  }
}
```

　forkしたリポジトリをcloneしてDockerイメージをビルドします。

```bash
$ git clone https://github.com/masato/docker-nginx-consul.git
$ cd docker-nginx-consul
$ docker build -t nginx-consul .
```


　`--dns`と`--dns-search`フラグをつけてNginxのコンテナを起動します。`--dns`フラグの順番はDockerホスト、Google DNSの前にConsulコンテナのIPアドレスを指定するとNginxコンテナの`/etc/resolv.conf`は以下のようになります。


```bash:/etc/resolv.conf
search service.consul
nameserver 172.17.0.2
nameserver 172.17.0.1
nameserver 8.8.8.8
```

　Ambari Serverの管理画面で使う8080、ZeppelinのNotebookに使う9995と9996ポートをDockerホストにマップして外部から接続できるようにします。

```bash
$ AMB_CONSUL=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" amb-consul)
$ echo $AMB_CONSUL
172.17.0.2
$ docker run \
  --name nginx \
  -d \
  -p 8080:8080 -p 9995:9995 -p 9996:9996 \
  --volume $PWD/service.ctmpl:/templates/service.ctmpl \
  --dns $AMB_CONSUL \
  --dns 172.17.0.1 \
  --dns 8.8.8.8 \
  --dns-search service.consul \
  nginx-consul
```

　Nginxコンテナに入り名前解決ができるようになったか確認します。

```bash
$ docker exec -it nginx bash
$ echo $CONSUL_URL
consul:8500
$ ping -c 1 amb1
PING amb1.service.consul (172.17.0.4): 56 data bytes
64 bytes from 172.17.0.4: icmp_seq=0 ttl=64 time=0.049 ms
--- amb1.service.consul ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.049/0.049/0.049/0.000 ms
```

　

　最後にpsコマンドで今回作成したDockerコンテナを確認します。

```bash
$ docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                                                                       NAMES
48df8cfdd49f        nginx-consul                  "/bin/start.sh"          29 minutes ago      Up 29 minutes       80/tcp, 0.0.0.0:8080->8080/tcp, 443/tcp, 0.0.0.0:9995-9996->9995-9996/tcp   nginx
ce146ebf73f1        sequenceiq/ambari:2.1.2-v1    "/start-agent"           2 hours ago         Up 2 hours          8080/tcp                                                                    amb3
9c23d449cb83        sequenceiq/ambari:2.1.2-v1    "/start-agent"           2 hours ago         Up 2 hours          8080/tcp                                                                    amb2
162662822eb1        sequenceiq/ambari:2.1.2-v1    "/start-agent"           2 hours ago         Up 2 hours          8080/tcp                                                                    amb1
6729936f59f8        sequenceiq/ambari:2.1.2-v1    "/start-server"          2 hours ago         Up 2 hours          8080/tcp                                                                    amb-server
02ee7f29a74e        sequenceiq/consul:v0.5.0-v6   "/bin/start -server -"   2 hours ago         Up 2 hours          53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8301-8302/udp, 8500/tcp            amb-consul
```

## HDFS

　Zeppelinの実行をする前にHDFSにrootユーザーのディレクトリを作成します。`hdfs`ユーザーにスイッチして`/user/root`ディレクトリを作成します。

```bash
$ docker exec -it amb1 bash
$ hadoop fs -ls /user
Found 4 items
drwxrwx---   - ambari-qa hdfs          0 2015-12-05 12:08 /user/ambari-qa
drwxr-xr-x   - hcat      hdfs          0 2015-12-05 12:09 /user/hcat
drwx------   - hive      hdfs          0 2015-12-05 12:09 /user/hive
drwxrwxr-x   - spark     hdfs          0 2015-12-05 12:08 /user/spark
$ su hdfs 
$ hadoop fs -mkdir /user/root
$ hadoop fs -chown root /user/root
```

## コンポーネントの再起動

　理由はよくわかりませんが`master_1`グループのコンポーネントを再起動しないとZeppelinの実行に失敗してしまうようです。


　Ambariの管理画面をパブリックIPアドレスからブラウザで開きます。

<img width="512" alt="ambari-server.png" src="https://qiita-image-store.s3.amazonaws.com/0/43745/ebdaba89-9a43-991d-5d54-4aebe57f8556.png">

 Hostsメニューから`amb1.service.consul`を開いて`Restart All Components`を実行します。


<img width="512" alt="ambari-restart.png" src="https://qiita-image-store.s3.amazonaws.com/0/43745/289a5598-a7ff-6910-9921-580d1f064d4f.png">


　コンポーネントの再起動が終了するとようやくZeppelinが使えるようになります。9995ポートをブラウザで開きます。

<img width="1047" alt="zeppelin-9995.png" src="https://qiita-image-store.s3.amazonaws.com/0/43745/f46db625-e404-1605-3bd3-64a7cb252468.png">







## 参考

* [Data Science in the Cloud with Spark, Zeppelin, and Cloudbreak](http://www.slideshare.net/Hadoop_Summit/data-science-in-the-cloud-with-spark-zeppelin-and-cloudbreak)
* [Multi-node Hadoop cluster on Docker](http://blog.sequenceiq.com/blog/2014/06/19/multinode-hadoop-cluster-on-docker/)