2021.04.08
○千万規模のコスト減!?OSSでHadoopクラスタを運用・管理したい ~ High Availabilityの章 ~
1.はじめに
こんにちは。大規模データ分析基盤運用チームのY.S.です。今回はOSSでHadoop管理を頑張るシリーズの第2弾です。
前回はAnsible AWXを触ってみて、HDFSとYARNのプロビジョニングplaybookを実行してみました。HadoopをOSSで管理したいそもそものモチベーション等は前回の記事をご参照ください。
前回作成したHadoopではマスターノードが1台だけでしたが、これだとマスターノードに障害が起きた場合に全体が止まったり、データが消失したりしてしまうので、現場でこの構成で運用することはありません。
そこで今回は、マスターノードとスレーブノードをそれぞれ複数台用意してHigh Availability(HA)構成にしてみようと思います。ワケあって前回作ったクラスタが消失したので、「HAでないクラスタをHAにする」のではなく、最初からHAでクラスタを作ります。
基本的には、playbookにHAに必要なミドルウェアやconfigのデプロイタスクを書いていくだけです。だた、一部実際にノードに入って手でコマンドを打つ必要があるので、完全にplaybookを叩くだけでHAができるというわけにはいかなかったです。
今回のゴールは、NameNodeとResourceManagerの自動Failoverが機能していることを確認できればよしとします。
2.HadoopクラスタのHA化
Hadoopクラスタのマスターノードは単一障害点となるため、ノードを複数台用意してactive/standbyのHAを構成します。
普段はマスターノードのうち一台がactiveノードとして機能し、残りはstandbyとして待機していて、activeノードが停止した際にstandbyノードをactiveノードへ昇格(failover)させることで全体が停止することを防ぎます。NameNodeやResourceManager自体には自動でfailoverする仕組みはなく、コーディネーションエンジンのZookeeperやZookeeperFailoverController(ZKFC)と組み合わせて自動failoverを実現します。
2-1.Zookeeper・ZKFC
Zookeeperは分散システムを構築するためのコーディネーションエンジンで、共有設定、分散Lock、メンバー取得等の機能を実現します。ZKFCはマスターノード上で動くプロセスで、そのノードのヘルスモニタリングや、Lockの獲得等を行います。
各ノードのZKFCとZookeeperの間にはセッションが張られていて、activeノードのZKFCはZookeeper上にLockを保持しています。このLockはセッションが消えると自動で解消されます。ZKFCが自身のノードがクラッシュしたと判断した場合はそのセッションは破棄されますが、それがactiveノードのセッションである場合、Zookeeperは他の正常なZKFCに対してfailoverの開始を通知します。
通知を受けた各ZKFCはLockを獲得しようとし、早い者勝ちで一つのノードだけがLockを獲得し、新たなactiveノードに昇格します。
2-1.NameNodeのHA
HDFSは、ファイルをblock単位に分割してそれぞれのblockを複数のDataNodeに格納し、各blockがどのDataNodeに格納されているかのメタデータをNameNodeが管理します。
NameNodeを冗長化するには、メタデータをactive/standby間で共有しておき、failoverの前後でメタデータに齟齬が生まれないようにする必要があります。
これを実現するためにJournalNodeを導入します。JournalNodeはクラスタ上の複数ノード上で起動するプロセスで、standbyノードのメタデータをactiveノードのものと同期させるためのものです。activeノードは、自身が保持するメタデータに変更を加える際、各JournalNodeに変更を伝えます。standbyノードはJournalNodeから変更を読み取り、自身が保持するメタデータを更新します。failoverの際は、activeに昇格する予定のノードがJournalNodeから全ての情報を読み取ったことを確認した後にactiveに昇格します。
Zookeeperのアドレスをcore-site.xmlに、JournalNodeに関する設定をhdfs-site.xmlにそれぞれ記載した後、NameNodeのフォーマット等の手動処理を若干行うとHA化が完了します。詳しい手順については後述します。
2-1.ResourceManagerのHA
ResouceManagerのHA化はNameNodeのそれと比べてシンプルで、yarn-site.xmlにZookeeperや各ResourceManegerのアドレスを設定するだけです。configを配布した後にResourceManagerを起動すればHA化が完了しています。
3.Ansible AWXによるHadoop HAクラスタ構成管理
今回はマスターノード2台、スレーブノード3台の計5台構成とします。各ノードに乗っているコンポーネントは以下の通りです。
Hadoopでは各コンポーネントで多くのポートを使用しますが、それらのポートをいちいち開けたり閉じたりするのは大変なので、全通しのプライベートネットワークを構築してノード同士はそこで通信させます。
また、failoverの際にマスターノード同士でssh通信を行うので、お互いの公開鍵を登録しておきます。
3-1.全体の流れ
下記の流れでクラスタの構築を進めます。ZookeeperのインストールとHDFSのセットアップはplaybookの実行だけでなく、ある程度手作業も必要でした。
3-2.システム設定
Hadoopのデプロイをする前に、システム設定に関するroleを実行します。Hadoopを動作させるのに必要な部分を抜粋して下記で説明します。
3-2-1.yumライブラリインストール
クラスタを構築するのに必要なyumライブラリをインストールするタスクです。今回はfailoverの際に使用するfuserコマンドをmasterノードにインストールします。
- name: install psmisc (for fuser command) yum: name: psmisc state: latest
3-2-2.シンボリックリンク
YARNのタスクを実行する際に、何故か頑なに/bin/javaを見にいこうとしてエラーになったので、JDKのインストール先である/etc/javaからのシンボリックリンクを予め張っておきます。(前回のブログの時はこのようなことはなかったのですが、HA化したことが原因かは不明です。。)
- name: create symlink file: src: /etc/java/bin/java dest: /bin/java state: link force: yes
3-3.JDK・Hadoopのインストール
ansible playbookで各ノードにJDKとHadoopをインストールします。JDKのインストール手順は前回の投稿と同じです。
Hadoopのインストールも基本的に前回と同じですが、新たに下記のような、hadoop-env.shをデプロイするタスクを追加し、JavaやHadoopのPATHを追記しています。
deploy-config.yml
- name: hadoop env blockinfile: path: /etc/hadoop/etc/hadoop/hadoop-env.sh create: yes insertafter: EOF marker: "# {mark} ANSIBLE Fish-shell basic setup." block: "{{item}}" with_file: - files/hadoop-env.sh
files/hadoop-env.sh
export JAVA_HOME=/etc/java export HADOOP_HOME=/etc/hadoop export PATH=${PATH}:${JAVA_HOME}/bin:${HADOOP_HOME}/bin:${HADOOP_HOME}/sbin
3-4.Zookeeperのインストール
下記のplaybookの手順でzookeeperのインストール・セットアップを行います。ただ、ZookeeperServerを起動するには、各ノードのzookeeperのデータディレクトリ(ここでは/etc/zookeeper/data/)にmyidというファイルを作り、下記のzoo.cfgに書かれているサーバー番号の数字をmyidに記載する必要があります。なので実際の手順としては、ansibleでconfig配る -> myidを作成・編集する -> 手動でZookeeperServer起動という流れになりました。
zookeeper_install.yml
- name: defrost-zip-confirm stat: path=/etc/apache-zookeeper-3.6.2-bin/ register: result - name: Defrost hadoop tar file unarchive: src: /var/lib/awx/volumes/file/gz/apache-zookeeper-3.6.2-bin.tar.gz dest: /etc when: not result.stat.exists - name: Create symbolic link to zookeeper file: src: /etc/apache-zookeeper-3.6.2-bin dest: /etc/zookeeper state: link - name: Create zookeeper data dir file: path: /opt/zookeeper/data state: directory - name: deploy zoo.cnf template: src: "conf/zoo.cfg.j2" dest: "/etc/zookeeper/conf/zoo.cfg" notify: - start_zookeeper_server_daemon
zoo.cnf.j2
tickTime=2000 dataDir=/opt/zookeeper/data clientPort=2181 initLimit=5 syncLimit=2 server.1=192.168.10.2:2888:3888 server.2=192.168.10.3:2888:3888 server.3=192.168.10.5:2888:3888
server番号とipを対応付ける必要があるので、zoo.cfgにはZookeeperServerのipをベタ書きしています。ただ、運用していてZookeeperServerの構成が変わることは殆どないので、寧ろベタ書きの方が管理しやすいかもとも思いました。
3-5.HDFSのセットアップ
configを配布し、NameNode, DataNode, JournalNodeを起動するタスクを実行しますが、途中で色々と手作業でやる部分があります。
配布するconfigファイルは前回と同等、core-site.xml、hdfs-site.xml、slavesです。前回からの差分として、HA化に伴ってJournalNodeとZookeeper関係の設定が増えています。
3-1.core-site.xml.j2
<configuration> <property> <name>fs.default.name</name> <value>hdfs://mycluster</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/var/tmp/hadoop-tmp</value> </property> ##ZK configs <property> <name>ha.zookeeper.quorum</name> {% for host in groups.ZookeeperServer %} <value>{{ host }}:2181</value> {% endfor %} </property> </configuration>
3-2.hdfs-site.xml.j2
HA構成になるに伴い、各NameNodeのホスト、JournalNodeのホスト・ポート、failoverの設定などを追加しました。
dfs.ha.fencing.ssh.private-key-filesには、NameNodeから他のNameNodeにsshするためのno-pass秘密鍵を指定します。公開鍵は、予めauthorized_keysに登録しておく必要があります。
<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.data.dir</name> <value>/opt/hdfs/data</value> </property> <property> <name>dfs.name.dir</name> <value>/opt/hdfs/name</value> </property> <property> <name>dfs.namenode.datanode.registration.ip-hostname-check</name> <value>false</value> </property> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>192.168.10.2:8020</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>192.168.10.3:8020</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>192.168.10.2:9870</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>192.168.10.3:9870</value> </property> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://192.168.10.2:8485;192.168.10.3:8485;192.168.10.5:8485;/mycluster</value> </property> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/root/.ssh/id_rsa</value> ##SSH KEY from ZKFC server to each NNs </property> <property> <name>dfs.ha.fencing.ssh.connect-timeout</name> <value>30000</value> </property> <property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property> <property> <name>dfs.journalnode.edits.dir</name> <value>/opt/hdfs/journal</value> </property> <property> <name>dfs.ha.nn.not-become-active-in-safemode</name> <value>true</value> </property> ##ZK configs <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property> </configuration>
configを配った後は、JournalNodeとNameNodeのフォーマット作業とZKFCの起動を行います。
- 1.NameNodeのうち1つのノードで$Hadoop_Home/bin/hdfs namenode -formatを実行
- 2.フォーマットしたNameNodeのデータ(ここでは/opt/hadoop/namenode/current)をもう一つのNameNodeにコピー
- 3.各JournalNodeで、JournalNodeのデータ(ここでは/opt/hadoop/journalnode/current)を削除
- 4.1でフォーマットしたものとは別のNameNodeで、$Hadoop_Home/bin/hdfs namenode -initializeShareEditsを実行
- 5.NameNodeのうち1つのノードで、$Hadoop_Home/bin/hdfs zkfc -formatZKを実行
- 6.各NameNodeで、$Hadoop_Home/bin/hdfs –daemon start zkfcを実行
これで自動failoverが有効になったNameNodeが起動します。
3-6.YARNのセットアップ
YARNのセットアップは簡単で、HAの設定を追加したconfigを配ってResourceManagerやNodeManagerを起動するだけです。
Managerの起動は前回同等、configを配った際にhandlerで行うので、roleを実行するだけでHAのYARNの出来上がりです。
yarn-site.xml.j2
HA構成に伴って追加された項目として、ResourceManagerの設定とZookeeperサーバーの指定があります。
<configuration> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <property> <name>yarn.resourcemanager.cluster-id</name> <value>yarn-cluster</value> </property> <property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>192.168.10.2</value> </property> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>192.168.10.3</value> </property> <property> <name>hadoop.zk.address</name> {% for host in groups.ZookeeperServer %} <value>{{ host }}:2181</value> {% endfor %} </property> </configuration>
4.HAの確認
上記の手順でデプロイしたクラスタで、マスターコンポーネントの自動failoverを確認します。
4-1.NameNode
haadminで確認すると、下記のようにmaster1がactive、master2がstandbyとして起動していることがわかります。
[root@master1 ~]# /etc/hadoop/bin/hdfs haadmin -getAllServiceState master1:8020 active master2:8020 standby
次に、master1のNameNodeプロセスを終了し、master2でhaadminを確認すると、master1のNameNodeとのconnectionが切れていることと、master2がactiveに昇格していることが確認できます。
[root@master2 ~]# /etc/hadoop/bin/hdfs haadmin -getAllServiceState 2021-04-01 11:04:13,505 INFO ipc.Client: Retrying connect to server: master1/192.168.10.2:8020. Already tried 0 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=1, sleepTime=1000 MILLISECONDS) master1:8020 Failed to connect: Call From master2/192.168.10.3 to master1:8020 failed on connection exception: java.net.ConnectException: 接続を拒否されました; For more details see: http://wiki.apache.org/hadoop/ConnectionRefused master2:8020
最後に、master1のNameNodeを再起動してからhaadminで、master1がstandbyとなっていることがわかります。
[root@master2 ~]# /etc/hadoop/bin/hdfs haadmin -getAllServiceState master1:8020 standby master2:8020 active
以上で、NameNodeの自動failoverが実現されていることを確認できました。
4-2.ResourceManager
ResourceManagerも同等に自動failoverを確認します。
[root@master1 ~]# /etc/hadoop/bin/yarn rmadmin -getServiceState rm1 active [root@master1 ~]# /etc/hadoop/bin/yarn rmadmin -getServiceState rm2 standby
master1のResourceManagerを停止。
[root@master2 ~]# /etc/hadoop/bin/yarn rmadmin -getServiceState rm1 2021-04-08 01:11:15,239 INFO ipc.Client: Retrying connect to server: master1/192.168.10.2:8033. Already tried 0 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=1, sleepTime=1000 MILLISECONDS) Operation failed: Call From master2/192.168.10.3 to master1:8033 failed on connection exception: java.net.ConnectException: 接続を拒否されました; For more details see: http://wiki.apache.org/hadoop/ConnectionRefused [root@master2 ~]# /etc/hadoop/bin/yarn rmadmin -getServiceState rm2 active
master1のResourceManagerを再起動。
[root@master2 ~]# /etc/hadoop/bin/yarn rmadmin -getServiceState rm1 standby [root@master2 ~]# /etc/hadoop/bin/yarn rmadmin -getServiceState rm2 active
ResourceManagerでも自動failoverが実現できています。
5.まとめ
今回はOSSでHadoopクラスタ管理を頑張る第2弾ということで、NameNodeとResourceManagerをHA構成にしてクラスタを構築するansibleプロジェクトを作成して、実際にデプロイしてみました。
ZookeeperとHDFSのセットアップの際に手動での作業が必要になるため、完全な自動化とはいきませんでした。ただ、マスターコンポーネントのHAは一度構築してしまえばあまり弄ることはないと思うので、そこまでデメリットは感じていません。
また、ホストを記載するconfigの一部で、ansibleのマジック変数を上手く使えずにホストをベタ書きしたところがありました。ただ、ここもそうそう変更する箇所ではないと思うので、無理にマジック変数を使わず、むしろベタ書きの方が管理のしやすさという視点ではいいかもと思いました。
最後に
次世代システム研究室では,データサイエンティスト/機械学習エンジニアを募集しています。ビッグデータの解析業務など次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら,ぜひ募集職種一覧からご応募をお願いします。皆さんのご応募をお待ちしています。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD