Hadoop HDFS の NameNode は長い間単一障害点だったのですが、 CDH 4 から、 NFS 上の edits ログを共有する形でのアクティブ/スタンバイ構成が可能になりました。しかし、フェイルオーバが中途半端になると共有 edits ログが破壊されるとか、 NFS が新しい単一障害点になってしまうとかで、評判が良くなかったようです。
そこで CDH 4.1 からは、 JournalManager という、 edits ログを書き込む専門のデーモンが追加されました。アクティブな NameNode は、 Quorum Journal Manager (QJM) を通じて、共有 edits ログを JournalNode クラスタに書き込めるようになりました。
今回、 QJM ベースの NameNode 冗長化を試すため、 HDFS クラスタを作ってみました。同時に ZooKeeper を使った NameNode 自動フェイルオーバも試してみました。その時のメモを書きます。
マシン構成
ホスト名 | 役割 | パッケージ |
---|---|---|
hjt | JournalNode, ZooKeeper | hadoop-hdfs-journalnode, zookeeper-server |
hnn1, hnn2 | NameNode, JournalNode, ZooKeeper | hadoop-hdfs-namenode, hadoop-hdfs-zkfc, hadoop-hdfs-journalnode, zookeeper-server |
hslave1 - hslave4 | DataNode | hadoop-hdfs-datanode |
hclient | Hadoop クライアント | hadoop-client |
SecondaryNameNode / CheckpointNode は必要ありません。スタンバイ NameNode がチェックポイントを行うためです。
Hadoop と ZooKeeper の設定
hdfs-site.xml
プロパティ | 値 |
---|---|
dfs.nameservices | localns |
dfs.ha.namenodes.localns | hnn1,hnn2 |
dfs.namenode.rpc-address.localns.hnn1 | hnn1.localnet:8020 |
dfs.namenode.http-address.localns.hnn1 | hnn1.localnet:50070 |
dfs.namenode.rpc-address.localns.hnn2 | hnn2.localnet:8020 |
dfs.namenode.http-address.localns.hnn2 | hnn2.localnet:50070 |
dfs.namenode.shared.edits.dir | qjournal://hjt.localnet:8485; hnn1.localnet:8485;hnn2.localnet:8485/localns (本当はスペースなし) |
dfs.client.failover.proxy.provider.localns | org.apache.hadoop.hdfs.server. namenode.ha.ConfiguredFailoverProxyProvider (本当はスペースなし) |
dfs.ha.automatic-failover.enabled | true |
dfs.ha.fencing.methods | sshfence |
dfs.ha.fencing.ssh.private-key-files | ${user.home}/.ssh/id_rsa |
ha.zookeeper.quorum | hjt.localnet:2181, hnn1.localnet:2181, hnn2.localnet:2181 |
dfs.ha.namenodes.localns には NameNode (以下 NN) の ID を指定します。常識的にはホスト名をそのまま指定するものでしょう。 HA 構成の管理コマンドを発行する時 (hdfs haadmin ...) は、この ID を使います。
dfs.ha.fencing.methods には sshfence だけを指定しています。この場合、何かがおかしくなって NN の自動フェイルオーバが発生すると、新しくアクティブになった側の ZKFC が、以前アクティブだった側の NN を、 SSH 経由で殺しに行きます。各 NN の hdfs ユーザ同士は、 SSH で鍵認証できるようにする必要があります。
CDH 4.0 時点から提供されていた、 NFS ベースの NN 冗長化では、自らがアクティブだと認識している NN が複数存在すると、 edits ログがぐちゃぐちゃに破壊されてしまう可能性がありました。このため、自動フェイルオーバ時に、古い側の NN に確実にとどめを刺して二度と NFS にアクセスできないようにするため、 sshfence に加えてクラスタ固有のコマンドでフェンシングを行う必要がありました *1 。 QJM ベースの冗長化では、古い側の NN からの書き込みを JournalNode が弾くため、 edits ログが破壊されることはありません *2 。このため、 sshfence だけで OK な場合もあるんじゃないかなーという感じです。
hadoop-env.sh
インストールした時点では /etc/hadoop/conf 下に hadoop-env.sh が入っていないので *3 、ソースから持って来ます。次の二点を修正すればとりあえず動きます。
変数 | 変更内容 |
---|---|
JAVA_HOME | /usr/java/default |
HADOOP_IDENT_STRING | 削除 |
HADOOP_IDENT_STRING を持って来たままにしておくと、 init スクリプトで Hadoop の各サービスを制御した時に、 start したサービスが status も stop もできなくなってしまいます *4 。とりあえず消しとけば大丈夫です。
zoo.cfg
追加部分のみ。
プロパティ | 値 |
---|---|
server.1 | hjt.localnet:2888:3888 |
server.2 | hnn1.localnet:2888:3888 |
server.3 | hnn2.localnet:2888:3888 |
「server.X」の X の部分が、 ZooKeeper Server ごとの myid です。
手順
冗長化関係ないとこもそれなりに書きます。
前準備
名前解決の確認
FQDN で正引きできることを確認します。
taku@hclient ~$ nslookup hnn1.localnet Server: 192.168.**.** Address: 192.168.**.**#53 Name: hnn1.localnet Address: 192.168.XX.YY
それから、逆引きで FQDN が返ることを確認します。
taku@hclient ~$ nslookup 192.168.XX.YY Server: 192.168.**.** Address: 192.168.**.**#53 YY.XX.168.192.in-addr.arpa name = hnn1.localnet.
NN 同士が SSH で鍵認証できるようにする
NN サーバの hdfs ユーザ同士が SSH 鍵認証できるようにします。まず hnn1 の鍵を hnn2 に登録します。
hdfs@hnn1$ ssh-keygen ... hdfs@hnn1$ ssh-copy-id hnn2 ...
hnn2 から hnn1 へも同様に。
hdfs@hnn2$ ssh-keygen ... hdfs@hnn2$ ssh-copy-id hnn1 ...
CDH のパッケージでインストールすると、 hdfs ユーザのホームディレクトリ (/var/lib/hadoop-hdfs) のパーミッションが 775 になっています。これだと鍵による認証ができないので、 755 にします。
hdfs@hnn1$ chmod g-w ~hdfs
hdfs@hnn2$ chmod g-w ~hdfs
互いにパスワードなしで認証できれば OK 。
hdfs@hnn1$ ssh hnn2 hostname hnn2.localnet
hdfs@hnn2$ ssh hnn1 hostname hnn1.localnet
ZooKeeper クラスタの構築, 起動
hjt, hnn1, hnn2 の各マシンで ZooKeeper 用のディレクトリを初期化します。指定する myid は zoo.cfg で設定したものです。
root@hjt# service zookeeper-server init --myid=1 Using myid of 1
root@hnn1# service zookeeper-server init --myid=2 Using myid of 2
root@hnn2# service zookeeper-server init --myid=3 Using myid of 3
hjt, hnn1, hnn2 の各マシンで zookeeper-server を上げます。
root@hjt# service zookeeper-server start ...
ZKFC の初期化, 起動
ZKFC 用の znode を作成します。 ZooKeeper をインストールした任意のマシンで次のコマンドを実行します。
root@hjt$ sudo -u hdfs hdfs zkfc -formatZK ...
そんで、 NN のマシン (hnn1, hnn2) で hadoop-hdfs-zkfc を上げます 。
root@hnn1# service hadoop-hdfs-zkfc start ...
JournalNode 起動
hjt, hnn1, hnn2 の各マシンで hadoop-hdfs-journalnode を上げます。
root@hjt# service hadoop-hdfs-journalnode start ...
NameNode のメタデータ作成, 起動
アクティブ側で NN のメタデータを作成して、起動します。
root@hnn1# sudo -u hdfs hdfs namenode -format ... root@hnn1# service hadoop-hdfs-namenode start ...
スタンバイ側に NN のメタデータをコピーして、起動します。
root@hnn2# sudo -u hdfs hdfs namenode -bootstrapStandby ... root@hnn2# service hadoop-hdfs-namenode start ...
各 NN の状態を確認します。ここで指定するのは dfs.ha.namenodes.localns で設定した NN の ID です。
taku@hclient$ sudo -u hdfs hdfs haadmin -getServiceState hnn1 active taku@hclient$ sudo -u hdfs hdfs haadmin -getServiceState hnn2 standby
DataNode 起動
hslave1〜hslave4 の各マシンで DataNode を上げます。
root@hslave1# service hadoop-hdfs-datanode start ...
NameNode 手動フェイルオーバ確認
hnn1 から hnn2 にフェイルオーバします。
taku@hclient ~$ sudo -u hdfs hdfs haadmin -failover hnn1 hnn2 Failover to NameNode at hnn2.localnet/192.168.**.**:8020 successful taku@hclient ~$ sudo -u hdfs hdfs haadmin -getServiceState hnn1 standby taku@hclient ~$ sudo -u hdfs hdfs haadmin -getServiceState hnn2 active
次は hnn2 から hnn1 にフェイルオーバします。
taku@hclient ~$ sudo -u hdfs hdfs haadmin -failover hnn2 hnn1 Failover to NameNode at hnn1.localnet/192.168.**.**:8020 successful taku@hclient ~$ sudo -u hdfs hdfs haadmin -getServiceState hnn1 active taku@hclient ~$ sudo -u hdfs hdfs haadmin -getServiceState hnn2 standby
NameNode 自動フェイルオーバ確認
アクティブ側の NN プロセスが暴走して応答しなくなったという想定で、自動フェイルオーバを確認します。
ここでは、 iptables を起動して、アクティブ側の NN のポートを塞ぐことでシミュレーションします。デフォルトの設定では 22 番ポートだけ通すようになっているので、これでも sshfence は通るはずです。
root@hnn1# service iptables start ...
hnn2 がアクティブに切り替わっていることを確認します。
taku@hclient ~$ sudo -u hdfs hdfs haadmin -getServiceState hnn2 active
元の NN が殺されていることを確認します。
[root@hnn1 ~]# service hadoop-hdfs-namenode status Hadoop namenode is dead and pid file exists [FAILED]
確認できたので、元に戻します。
root@hnn1# service iptables stop ... root@hnn1# service hadoop-hdfs-namenode start ... root@hnn1# sudo -u hdfs hdfs haadmin -failover hnn2 hnn1 Failover to NameNode at hnn1.localnet/192.168.**.**:8020 successful
OS 起動時に各サービスが立ち上がるようにする
その他さまざま確認できたら、各マシンのサービスが起動時に立ち上がるようにします。
root@hjt# chkconfig zookeeper-server on ... root@hjt# chkconfig hadoop-hdfs-journalnode on ... ... ...
*2:Software Configuration for Quorum-based Storage
*3:#HADOOP-8287 etc/hadoop is missing hadoop-env.sh - ASF JIRA
*4:PID ファイルの名前を決めるのに、この変数の名前を使っているのだけど、コマンドによって /etc/default 下の設定値が有効になったり hadoop-env.sh の設定値が有効になったりしてバラバラ。名前の不一致で PID ファイルが参照できなくなる