Cloudera Manager API を使ったクラスタ構築

[Spark, SQL on Hadoop etc. Advent Calendar 2014](http://qiita.com/advent-calendar/2014/distributedcomputing) 11日目です。

## はじめに

Hadoop クラスタの構築は手のかかる作業ですが、Cloudera Managerのようなツールを使えばかなりインストール作業は簡単になります。また、設定なども適宜デフォルトから推奨値に変えてくれたりするので便利です。

ただし、Cloudera ManagerのGUIを使うとインストールは非常に簡単になるのですが、それでも検証などのために環境の作成/削除を繰り返すといった場合、逆にGUI操作が自動化のネックになります。

Cloudera Managerはこの点も考慮していてAPIを公開してCUI処理で代替できるようになっています。
今回はこの[Cloudera Manager API](http://cloudera.github.io/cm_api/)を使ったクラスタ構築の簡単なサンプルを紹介します。

## 前提
CentOS6.5がインストール済み、かつHost Inspectorでひっかかるのでカーネルパラメータvm.swappinessが0になっていることを想定しています。
設定されてなければrootで下記を実行しておいてください。

```bash
# echo "vm.swappiness=0" >> /etc/sysctl.conf
# sysctl -p
```

## Cloudera Manager
なにはともあれCloudera Managerのインストールです。
sudo 可能なユーザで下記を実行すればインストールされます。sudoできない場合は最後はsuしてrootで実行してください。

```bash
$ curl -O http://archive.cloudera.com/cm5/installer/latest/cloudera-manager-installer.bin 
$ chmod 755 cloudera-manager-installer.bin
$ sudo ./cloudera-manager-installer.bin --i-agree-to-all-licenses --noprompt --noreadme
```

これが終ればブラウザから `http://<nodename>:7180` にアクセスすることで Cloudera Manager にアクセスできるようになるはずです。
`<nodename>` はインストールしたノードのhostnameを指定してください。インストールノードのブラウザからであれば http://localhost:7180 でOKです。
上記のコマンドが終了してもブラウザからのアクセスに即時応答できず404が返ることがあるのでその場合数分程度待ってみてください。

成功すれば、下記のような画面が見えます。adminユーザの初期パスワードはadminですが、今はログインする必要はないのでこの画面が見られれば十分です。

![01_login.png](https://qiita-image-store.s3.amazonaws.com/0/58396/797f93d7-48f5-96a5-e5b8-2eb85ae2f3fc.png "Login")


## Cloudera Manager API について
Cloudera Manager API は REST/Python/Java の形式で提供されています。RESTについては(curlコマンドがあれば)インストールなども必要なくすぐに実行できます。
Python と Java のAPIはインストールが必要なので試す場合には[こちら](https://github.com/cloudera/cm_api)からダウンロードしてセットアップしてください。

今回はREST APIを使って進めていきます。私は今回初めてRESTに触れたので色々と戸惑いましたが今回のサンプルと[マニュアル](http://cloudera.github.io/cm_api/apidocs/v8/index.html)を比較することで他のコマンドについても使い方がなんとなくわかるのではないかと思います。


## Cloudera Manager API を使った構成
スクリプト全体は[こちら](https://gist.github.com/azurecube/ee88c330d6d4883de95d)に置いてあります。
以下、bashで実行するコマンドですが、そのままコピペするように先頭のプロンプトは抜いてあります。一箇所sudoしている箇所があるので注意してください。

なお、例外処理などは一切していないので、ネットワークにつながってなかった、などの理由でコマンドが失敗していても終了を待機する箇所の処理は延々と待ち続けることになります。待機処理の中でGETしているコマンドで状況を確認できるので適宜確認してみてください。

### 環境設定
これはとくにAPIを使うにあたって必須ではないのですが、一部設定を環境変数にして共有してあります。

CMNODEはCloudera Managerを稼動させるノードです。
TARGETはAgentを配置してCloudera Managerで管理するノードになります。space区切りで記載してください。
CLUSTERはClusterの名前です好きにつけてもらって大丈夫です。
各ノードのrootのパスワードを指すROOT_PASSは適宜変更してください。
BASEはバージョン情報(v8)を含むので新しいバージョンを使う場合は変えたのが良いかもしれません。
CDH_VERはCDHのメジャーバージョンです。こちらも新しいバージョンを使う場合には変えることになると思います。
とりあえず、Cloudera Manager 5.2.1/CDH 5.2.1 であればこの設定のままで大丈夫です。

```bash:cm_rest.sh
CMNODE="node1.cloudera.com"
TARGET="node1.cloudera.com node2.cloudera.com node3.cloudera.com"
CLUSTER="Cluster1"
ROOT_PASS="cloudera"
BASE=http://$CMNODE:7180/api/v8
CDH_VER=5
```

とりあえずここまでを設定しておけばsudo可能な環境であれば実行できると思います。
結構時間はかかるので、[ダウンロード](https://gist.github.com/azurecube/ee88c330d6d4883de95d)の上編集して、`bash cm_rest.sh`を実行の後続きを読むことをお勧めします。

以下、実行している内容について簡単に説明していきます。

### トライアル
ClouderaManagerを使うにはライセンスの選択が必要ですが、今回はセットアップの検証をするだけなので60日間のトライアルを選択します。

```bash:cm_rest.sh
# Start Trial
curl -X POST -u "admin:admin" -i $BASE/cm/trial/begin
```

### deploymentへのホストの追加
ちょっと面倒な処理をしていますが、やってることは下記の3つで、自動化するのでなければ2.のみを実行すれば大丈夫です。
1. jsonの配列形式にできる追加するhostのリスト作成
2. リストに対するインストールのkick
3. 2.はインストールが終わらなくてもプロンプトが返ってくるので完了の待機
この処理はagentで使用するパッケージのダウンロード/インストールを行うのでそれなりに時間がかかります。

```bash:cm_rest.sh
# Setup Hosts
host_list=""
for i in $TARGET
do
  host_list=$host_list'"'$i'"',
done
host_list=${host_list/%?/}

curl -X POST -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "hostNames": ['$host_list'],
       "userName" : "root",
       "password" : "'$ROOT_PASS'"}'  \
$BASE/cm/commands/hostInstall

echo "Waiting for setup nodes"
while [ 1 ]
do
  target_size=$(expr 1 + `echo $TARGET | grep -o " " | wc -l`)
  installed_hosts=`curl -sS -X GET -u "admin:admin" -i $BASE/hosts | grep '"hostname" :' | wc -l`
  test $target_size -eq $installed_hosts && break
  echo -e "setuped:" $installed_hosts"/"$target_size"\c"
  echo -e "\r\c"
  sleep 10
done
```

最終的には下記のようなコマンド(上記の2.にあたります)を実行して、結果を待つ、ということを意図しています。


```bash
curl -X POST -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "hostNames": ["node1.cloudera.com", "node2.cloudera.com", "node3.cloudera.com"],
       "userName" : "root",
       "password" : "cloudera"}'  \
http://$CMNODE:7180/api/v8/cm/commands/hostInstall
```

これで各ノードがCloudera Managerでの管理下に置かれたことになります。

### Management Serviceの設定
クラスタの監視/管理をする Management Service を構成します。

```bash:cm_rest.sh
## Create mgmt service
curl -X PUT -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "name": "mgmt" }'  \
$BASE/cm/service
```

### ロールの配置と構成
ロールを配置し、デフォルトの設定を作成します。

```bash:cm_rest.sh
## Assign and Configure Roles
curl -X PUT -u "admin:admin" -i $BASE/cm/service/autoAssignRoles
curl -X PUT -u "admin:admin" -i $BASE/cm/service/autoConfigure
```

### Report Manager DBの構成
一部のManagement ServiceではLevelDBを使うようになり、RDBMSを使用しなくなっていますが、ReportManagerは引き続きRDBMSを使用しているので構成します。
パスワードは /etc/cloudera-scm-server/db.mgmt.properties にあるので、root権限で取得しています。もしsudoができない場合には先のファイルの com.cloudera.cmf.REPORTSMANAGER.db.passwordエントリから取得し、rman_passという変数に入れて続きを実行してください。

```bash:cm_rest.sh
## Setting Report Manager DB
### Report Manager password
rman_pass=`sudo grep com.cloudera.cmf.REPORTSMANAGER.db.password /etc/cloudera-scm-server/db.mgmt.properties | cut -d= -f2`
### Configuration
curl -X PUT -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "items": [{"name": "headlamp_database_host",     "value": "'$CMNODE':7432"},
                  {"name": "headlamp_database_name",     "value": "rman"},
                  {"name": "headlamp_database_password", "value": "'$rman_pass'"},
                  {"name": "headlamp_database_user",     "value": "rman"},
                  {"name": "headlamp_database_type",     "value": "postgresql"}
      ]}'  \
$BASE/cm/service/roleConfigGroups/mgmt-REPORTSMANAGER-BASE/config
```

### 使わないロールの削除
今回Navigatorは使わないのでNavigator関連のロールは削除しておきます。

```bash:cm_rest.sh
## Delete Navigator Entry
curl -X DELETE -u "admin:admin" -i \
$BASE/cm/service/roles/`curl -sS -X GET -u "admin:admin" -i $BASE/cm/service/roles | grep -B1 '"type" : "NAVIGATORMETASERVER"' | grep name | cut -d'"' -f4`

curl -X DELETE -u "admin:admin" -i \
$BASE/cm/service/roles/`curl -sS -X GET -u "admin:admin" -i $BASE/cm/service/roles | grep -B1 '"type" : "NAVIGATOR"' | grep name | cut -d'"' -f4`
```

### Management Serviceの起動
起動します。ここまででManagement Serviceの構成は終了です。この後はClusterの構成になります。

```bash:cm_rest.sh
# Startup
curl -X POST -u "admin:admin" -i $BASE/cm/service/commands/start
```

### Clusterの作成
まずは枠としてのClusterを作成します

```bash:cm_rest.sh
## Create Cluster
curl -X POST -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "items": [
          {
            "name": "'$CLUSTER'",
            "version": "CDH'$CDH_VER'"
          }
      ] }'  \
$BASE/clusters
```

### ノードをClusterに追加
作成した枠にノードを追加します。Clusterへの追加時にはhostnameではなくhostIdという値を使用します。
これはDeploymentにノードを追加したときに設定されているので下記のhost_ids=...の行のようにして確認することできます。

```bash:cm_rest.sh
## Assign Hosts
host_ids=`curl -sS -X GET -u "admin:admin" -i $BASE/hosts | grep '"hostId" :' | cut -d'"' -f 4`
for i in $host_ids
do 
  curl -X POST -u "admin:admin" -i \
    -H "content-type:application/json" \
    -d '{ "items": [ {"hostId": "'$i'"} ]}'  \
  $BASE/clusters/$CLUSTER/hosts
done
```

### Clusterで稼動させるサービスの選択
typeがサービスの種類です。他のサービスを選択したい場合は[一覧を取得するAPI](http://cloudera.github.io/cm_api/apidocs/v8/path__clusters_-clusterName-_serviceTypes.html)を実行して確認してみてください。
クラスタ上での名前がnameになり、こちらは適当につけられます。下記のようにHIVEサービスにshiumachiとつけることも可能です。

```bash:cm_rest.sh
curl -X POST -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "items": [  {"name": "zookeeper", "type": "ZOOKEEPER"},
                    {"name": "shiumachi", "type": "HIVE"},
                    {"name": "sqoop"    , "type": "SQOOP"},
                    {"name": "yarn"     , "type": "YARN"},
                    {"name": "hdfs"     , "type": "HDFS"}
      ] }'  \
$BASE/clusters/$CLUSTER/servicess
```
※コードはイメージです。[実際のスクリプト](https://gist.github.com/azurecube/ee88c330d6d4883de95d)とは異なることがあります。ご了承ください。
上記で実行してしまった方はご愁傷様です。

特に脈絡のない元ネタ
<blockquote class="twitter-tweet" lang="en"><p>しうまちはHiveのように愛されてる. <a href="https://twitter.com/hashtag/cwt2014?src=hash">#cwt2014</a></p>&mdash; Tatsuo Kawasαki (@kernel023) <a href="https://twitter.com/kernel023/status/530328689720832000">November 6, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

### サービスの配置と構成
Management Service同様に実施します。

```bash:cm_rest.sh
## Assign and Configure Roles
curl -X PUT -u "admin:admin" -i $BASE/clusters/$CLUSTER/autoAssignRoles
curl -X PUT -u "admin:admin" -i $BASE/clusters/$CLUSTER/autoConfigure
```

### Parcelの操作
インストールするCDHのparcelの名前を取得して変数CDHに代入しています。
また、ダウンロード、配布、アクティベートの終了を待つ関数を定義しています。

これらの処理はすぐにプロンプトに返ってきますが、Cloudera Manager側で処理が継続するので処理状況を監視しています。

```bash:cm_rest.sh
### Getting CDH Parcel name
CDH=`curl -sS -X GET -u "admin:admin" -i $BASE/clusters/$CLUSTER/parcels | grep -A1 '"product" : "CDH"' | grep '"version" : "'$CDH_VER | cut -d'"' -f4`

### short function to wait previous operation
parcel_wait_for () {
  while [ 1 ]
  do
    curl -sS -X GET -u "admin:admin" -i $BASE/clusters/$CLUSTER/parcels/products/CDH/versions/$CDH | grep '"stage" : "'$1'"' && break
    sleep 5
  done
}

### Download Parcel
curl -X POST -u "admin:admin" -i $BASE/clusters/$CLUSTER/parcels/products/CDH/versions/$CDH/commands/startDownload
parcel_wait_for DOWNLOADED

### Distribute Parcel
curl -X POST -u "admin:admin" -i $BASE/clusters/$CLUSTER/parcels/products/CDH/versions/$CDH/commands/startDistribution
parcel_wait_for DISTRIBUTED

### Activate Parcel
curl -X POST -u "admin:admin" -i $BASE/clusters/$CLUSTER/parcels/products/CDH/versions/$CDH/commands/activate
parcel_wait_for ACTIVATED
```

### Hive Metastoreの設定
Hive MetastoreもReport Manager同様に設定が必要なので実施していきます。

```bash:cm_rest.sh
## Configuring Hive metastore DB
curl -X PUT -u "admin:admin" -i \
  -H "content-type:application/json" \
  -d '{ "items": [{"name": "hive_metastore_database_host",     "value": "'$CMNODE'"},
                  {"name": "hive_metastore_database_name",     "value": "hive"},
                  {"name": "hive_metastore_database_password", "value": "hive"},
                  {"name": "hive_metastore_database_port",     "value": "7432"},
                  {"name": "hive_metastore_database_type",     "value": "postgresql"}
      ]}'  \
$BASE/clusters/$CLUSTER/services/hive/config
```
※HIVEの名前をshiumachiとした場合、`$BASE/clusters/$CLUSTER/services/shiumachi/config` に対して実行する必要があります。

### FirstRun
各種初期化などを内部的に実行しつつ各サービスを起動するFirstRunを実行していきます。

```bash:cm_rest.sh
## FirstRun
curl -X POST -u "admin:admin" -i $BASE/clusters/$CLUSTER/commands/firstRun
```

## 構成状況の確認
上記まで実行したところで `http://<nodename>:7180` にアクセスし、今度はログインするとサービスの構成状況を確認することができます。
FirstRunはKickしただけでプロンプトが返ってきて、実際の処理はClouderaManager側で継続しています。実行後すぐにアクセスすると少しずつ起動していく様子が見えると思います。


## まとめ
いかがでしたでしょうか。GUIでやるとクリックするだけ、というところが多いのに対してCUIのコマンドにすると意識する箇所が多く、大変になった気もしますが、繰り返す環境構築を自動化する際の参考になれば幸いです。