さて、別記事でクラスタの構築をやっていますが、手作業でやるのは面倒ですよね。私は面倒です。
スケールさせられるのが分散処理基盤たるHadoopの魅力。ノード追加もストレスなくやりたいものです。Ansibleでやってしまいましょう。
前提構成
クラスタとして構築する予定の各ノードが、管理対象としてセットアップされているものとします。具体的な作業はAnsibleの解説を参照して下さい。
 Hadoopの実行ユーザuserとしてコントローラにログインし、AnsibleのPlaybookを実行していきます。
また、Hadoopインストール用のtarballはコントローラにダウンロードしておきます。
 今回はコントローラに置いたtarballを管理対象(クラスタの各ノード)にコピー・解凍することにします。
 もちろん、get_urlしてもいいのですが、プロキシ環境で設定が面倒だったためです。
| 1 | wget http://ftp.jaist.ac.jp/pub/apache/hadoop/common/hadoop-2.6.5/hadoop-2.6.5.tar.gz /tmp | 
ファイルの準備
コントローラで作業用のディレクトリを作成し、必要なファイル一式を作成しましょう。
 ディレクトリ構成の完成像はこんな感じ。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | (current directory) ├─prep.yml (前半Playbook) ├─sendkey.yml (後半Playbook) ├─combine_keys.py (Python スクリプト) ├─tmp (後ほど作成) │ (以下をここで準備) ├─assets │  ├─core-site.xml │  ├─hdfs-site.xml │  ├─yarn-site.xml │  └─ssh_conf ├─group_vars │  └─targets.yml └─inventory     └─hosts | 
Playbook本体のprep.ymlファイルとsendkey.ymlファイルの他、Playbookに読み込ませるために、下記のようなファイルを準備しておきます。
| 1 2 3 4 | [targets] 192.168.100.10 192.168.100.11 192.168.100.12 | 
| 1 2 3 4 | Host *   UserKnownHostsFile /dev/null   StrictHostKeyChecking no   LogLevel quiet | 
| 1 | hadoop_dir: hadoop-2.6.5 | 
core-site.xmlファイルやhdfs-site.xmlファイル、yarn-site.xmlファイルは元の記事と同じ内容で作成してassetsサブディレクトリに格納しておきます。
 一応再掲。
| 1 2 3 4 5 6 | <configuration>    <property>       <name>fs.defaultFS</name>       <value>hdfs://192.168.100.10:9000</value>    </property> </configuration> | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <configuration>    <property>       <name>dfs.replication</name>       <value>1</value>    </property>    <property>       <name>dfs.data.dir</name>       <value>/var/lib/hadoop/data</value>    </property>    <property>       <name>dfs.name.dir</name>       <value>/var/lib/hadoop/name</value>    </property>    <property>       <name>dfs.namenode.datanode.registration.ip-hostname-check</name>       <value>false</value>    </property> </configuration> | 
| 1 2 3 4 5 6 | <configuration>     <property>         <name>yarn.resourcemanager.hostname</name>         <value>192.168.100.10</value>     </property> </configuration> | 
Playbookの作成と実行:Hadoopのインストール
まずはHadoopをインストールする諸々をprep.ymlファイルとして作成していきます。
 一部にroot権限が必要なものがあったので、基本become: yesで昇格、不要な場合のみ除外(become: no)していきます。
 あまり賢いPlaybookではありませんが、まあ動けばよし、ということで。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | - hosts: targets   become: yes   tasks:     - name: install modules       apt: name={{ item }} state=present update_cache=yes       with_items:         - openjdk-8-jdk     - stat: path=/home/user/.ssh/id_rsa       register: id_rsa_path     - name: generate ssh key       become: no       shell: ssh-keygen -P "" -t rsa -f /home/user/.ssh/id_rsa       when: not id_rsa_path.stat.exists     - name: configure ssh       become: no       copy: src=assets/ssh_conf dest=/home/user/.ssh/config mode=600     - name: download ssh key       fetch: src=/home/user/.ssh/id_rsa.pub dest=tmp/     - name: download known ssh key       fetch: src=/home/user/.ssh/authorized_keys dest=tmp/     - name: update hosts (/etc/hosts) file       lineinfile: >         dest=/etc/hosts         line={{ item }}       with_items:         - '192.168.100.10    ha1'         - '192.168.100.11    ha2'         - '192.168.100.12    ha3'     - name: remove itself from hosts file       lineinfile: >-         dest=/etc/hosts         regexp='{{ inventory_hostname }}'         line=''     - name: expand archive and send extracts       unarchive: src=/tmp/{{ hadoop_dir }}.tar.gz dest=/var/local     - name: make link to hadoop       file: dest=/usr/local/hadoop src=/var/local/{{ hadoop_dir }} state=link     - name: append environment variables to hadoop-config.sh       blockinfile:         path: /usr/local/hadoop/libexec/hadoop-config.sh         content: |           export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64           export HADOOP_HOME=/usr/local/hadoop           export HADOOP_PREFIX=/usr/local/hadoop           export HADOOP_COMMON_HOME=/usr/local/hadoop           export HADOOP_HDFS_HOME=/usr/local/hadoop           export HADOOP_MAPRED_HOME=/usr/local/hadoop           export HADOOP_YARN_HOME=/usr/local/hadoop           export HADOOP_CONF_DIR=/usr/local/hadoop/etc/hadoop           export YARN_CONF_DIR=$HADOOP_PREFIX/etc/hadoop         insertbefore: BOF     - name: send hadoop configuration xml files       copy: src=assets/{{ item }} dest=/usr/local/hadoop/etc/hadoop/{{ item }}       with_items:         - hdfs-site.xml         - core-site.xml         - yarn-site.xml     - name: create directories for hdfs       file: path={{ item }} state=directory mode=775       with_items:         - /var/lib/hadoop/data         - /var/lib/hadoop/name     - name: change owners       file: dest={{ item }} owner=user group=root mode=775 recurse=yes       with_items:         - /var/lib/hadoop/data         - /var/lib/hadoop/name         - /var/local/{{ hadoop_dir }} | 
下記のように、権限昇格用のオプションをつけてPlaybookを実行します。
 また、公開鍵をダウンロード(fetchモジュール)するためのサブディレクトリ(tmp)を事前に作成しておきます。
| 1 2 | mkdir -p tmp ansible-playbook -i inventory/hosts prep.yml --ask-become-pass | 
Playbookの作成と実行:SSH公開鍵の転送
公開鍵を対象ノードに登録するには、各ノードの~/.ssh/authorized_keysファイルにアクセス元の鍵をすべて列挙しておく必要があります。
 前のPlaybookで全ての公開鍵(と登録済の鍵)を個別のファイルとダウンロードしておいたので、後で一括でコピーするために、一旦これらを全て一つのファイルに書き出しておきます。
Ansibleで自動化しても良かったのですが、ちまちまタスクを書くのが面倒だったのでPythonスクリプト(combine_keys.py)を組んで代替としました。
 tmpサブディレクトリ以下にあるファイルを読み、内容をまとめて同じファイルtmp/keys.pubに書き出すだけのスクリプトです。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import os # file name for combined public keys keyfile_name = "keys.pub" def _search_files(base_dir):     for root, dirs, files in os.walk(base_dir):         for file_name in files:             # skip existing key file             if file_name.endswith(keyfile_name):                 continue             file_path = os.path.join(root, file_name)             with open(file_path, 'r') as f:                 yield f.readlines() if __name__ == "__main__":     src_dir = "tmp"     dest_file = os.path.join(src_dir, keyfile_name)     print "combine key files to: {}".format(dest_file)     if not os.path.isdir(src_dir):         raise Exception("source directory not found")     contents = []     for file_content in _search_files(src_dir):         contents.extend(file_content)     with open(dest_file, 'w') as f:         f.writelines(contents) | 
sendkey.ymlファイルとしてPlaybookを作成します。Pythonスクリプトで集めた公開鍵を各ノードにコピーするタスクです。
| 1 2 3 4 5 6 | - hosts: targets   become: no   gather_facts: no   tasks:     - name: send authorized key to targets       copy: src=tmp/keys.pub dest=/home/user/.ssh/authorized_keys mode=600 | 
上記で作成したスクリプトとPlaybookを順に実行します。
| 1 2 | python combine_keys.py ansible-playbook -i inventory/hosts sendkey.yml | 
残りの作業
あとはHDFSの初期フォーマットと、各ノード機能の立ち上げが残っていますが、これは各マシンにログインして実行しましょう。
Masterノードへログインして行う作業:
| 1 2 3 4 5 6 | # コントローラからMasterノードへログイン ssh user@192.168.100.10 # Masterノードで実行 /usr/local/hadoop/bin/hdfs namenode -format /usr/local/hadoop/sbin/hadoop-daemon.sh start namenode /usr/local/hadoop/sbin/yarn-daemon.sh start resourcemanager | 
Slaveノードへログインして行う作業(該当ノード台数分だけ繰り返す):
| 1 2 3 4 5 | # コントローラからSlaveノードへログイン ssh user@192.168.100.11 # Slaveノードで実行 /usr/local/hadoop/sbin/hadoop-daemon.sh start datanode /usr/local/hadoop/sbin/yarn-daemon.sh start nodemanager |