はじめに
RBF(Router-Based Federation)はApache HadoopのHDFSの新しい機能です。RBFを有効にすることで、ユーザーにとって複数のHadoopクラスタを1つのHadoopクラスタとして透過的に扱うことができます。
RBFが登場した背景や開発状況は下記の資料を参照してください。
- https://qiita.com/ajis_ka_old/items/d132c040f98836f2f3de
- https://www.slideshare.net/techblogyahoo/apache-hadoop-hdfs2018dbts2018
この記事では具体例を用いてRBFのセットアップ方法について説明します。(※Hadoopのバージョンは現在リリースVOTE中の3.2.0を用いています)
Router-Based Federationのシステム構成例
本記事では下記のシステム構成でRBFをセットアップします。
-
Router x 2
- クライアントのリクエストを受け取り、StateStoreのマウント情報を参照し、適切なNameNodeへルーティングします
-
StateStore(ZooKeeper)
- HDFSに関するHadoopクラスタのマウント情報を保存しています。つまり、複数のHadoopクラスタのHDFSを束ねて、1つの仮想的なディレクトリツリーを保持しています
- Hadoopクラスタ x 2
- Gateway(Hadoopクライアントがインストールされたノード)
クラスタ側の設定
クラスタ側ではRouterの存在を意識する必要はありません。クラスタA、Bともに下記のように通常のNameNode HAの設定になっています。(※設定のXMLはHadoopの3系から簡易フォーマットで記述できるようになりました。)
- クラスタAの
hdfs-site.xml
のHA設定
<!-- ネームサービス -->
<property name="dfs.nameservices" value="auction" />
<!-- NameNodeID -->
<property name="dfs.ha.namenodes.auction" value="nn1,nn2" />
<!-- RPCアドレス -->
<property name="dfs.namenode.rpc-address.auction.nn1" value="auction-nn1:8020" />
<property name="dfs.namenode.rpc-address.auction.nn2" value="auction-nn2:8020" />
<!-- HTTPアドレス -->
<property name="dfs.namenode.http-address.auction.nn1" value="auction-nn1:9870" />
<property name="dfs.namenode.http-address.auction.nn2" value="auction-nn2:9870" />
- クラスタBの
hdfs-site.xml
のHA設定
<!-- ネームサービス -->
<property name="dfs.nameservices" value="shopping" />
<!-- NameNodeID -->
<property name="dfs.ha.namenodes.shopping" value="nn1,nn2" />
<!-- RPCアドレス -->
<property name="dfs.namenode.rpc-address.shopping.nn1" value="shopping-nn1:8020" />
<property name="dfs.namenode.rpc-address.shopping.nn2" value="shopping-nn2:8020" />
<!-- HTTPアドレス -->
<property name="dfs.namenode.http-address.shopping.nn1" value="shopping-nn1:9870" />
<property name="dfs.namenode.http-address.shopping.nn2" value="shopping-nn2:9870" />
Routerの設定
Routerの設定値もhdfs-site.xml
に記述します。
※2019年6月追記
HDFS-14516によりhdfs-rbf-site.xml
にも記述できるようになりました。(Hadoop-3.3.0以降)
<!-- ネームサービス(クラスタ側の設定と合わせなくて良い) -->
<property name="dfs.nameservices" value="ns1,ns2" />
<!-- NameNodeID(クラスタ側の設定と合わせなくて良い) -->
<property name="dfs.ha.namenodes.ns1" value="nn1,nn2" />
<property name="dfs.ha.namenodes.ns2" value="nn1,nn2" />
<!-- クラスタA(ns1)のアドレス -->
<property name="dfs.namenode.rpc-address.ns1.nn1" value="auction-nn1:8020" />
<property name="dfs.namenode.rpc-address.ns1.nn2" value="auction-nn2:8020" />
<property name="dfs.namenode.http-address.ns1.nn1" value="auction-nn1:9870" />
<property name="dfs.namenode.http-address.ns1.nn2" value="auction-nn2:9870" />
<!-- クラスタB(ns2)のアドレス -->
<property name="dfs.namenode.rpc-address.ns2.nn1" value="shopping-nn1:8020" />
<property name="dfs.namenode.rpc-address.ns2.nn2" value="shopping-nn2:8020" />
<property name="dfs.namenode.http-address.ns2.nn1" value="shopping-nn1:9870" />
<property name="dfs.namenode.http-address.ns2.nn2" value="shopping-nn2:9870" />
<!-- デフォルトのネームサービスはクラスタAを指定 -->
<property name="dfs.federation.router.default.nameserviceId" value="ns1" />
<!-- StateStoreはZooKeeperを指定 -->
<property name="dfs.federation.router.store.driver.class"
value="org.apache.hadoop.hdfs.server.federation.store.driver.impl.StateStoreZooKeeperImpl" />
<!-- ルーティング先のNameNodeを指定 -->
<property name="dfs.federation.router.monitor.namenode" value="ns1.nn1,ns1.nn2,ns2.nn1,ns2.nn2" />
<!-- RouterとNameNodeが同じホストならtrue -->
<property name="dfs.federation.router.monitor.localnamenode.enable" value="false" />
<!-- グローバルなクォータ設定ができるようにする -->
<property name="dfs.federation.router.quota.enable" value="true" />
<!-- 複数クラスタのマウント機能を有効にする場合 -->
<property name="dfs.federation.router.file.resolver.client.class"
value="org.apache.hadoop.hdfs.server.federation.resolver.MultipleDestinationMountTableResolver" />
現在の仕様では、Routerの設定ファイルにルーティング対象の全てのNameNodeのRPCアドレスとHTTPアドレスを記述する必要があります。クラスタの数が増えるとそれも大変なので、将来的にはサービスディスカバリの機能が実装される予定です。
※2019年6月追記
HDFS-14118によりDNSラウンドロビンを使ってNameNodeやRouterの名前解決ができる機能が追加されました。これにより設定ファイルに実ホスト名を記述する必要がなくなり、ホストの追加・削除が容易になります。(Hadoop-3.3.0以降)
Routerの起動と停止
ここまでの設定に問題がなければRouterを起動することができます。
// 起動
[router]$ hdfs --daemon start dfsrouter
// 停止
[router]$ hdfs --daemon stop dfsrouter
クライアント側(Gateway)の設定
クライアント側では特に何も設定しなくても、NameNodeの代わりにRouterに直接リクエストを投げることで、Hadoopコマンドを実行することができます。
[gateway]$ hadoop fs -ls hdfs://router1:8888/
あるいは今回のように冗長化したRouterに対してNameNode HAと同様な設定を行うことで、クライアントのリクエストに対するフェイルオーバー機能を有効にできます。
<!-- Routerに向き先を設定する-->
<property name="dfs.nameservices" value="ns-fed" />
<property name="dfs.ha.namenodes.ns-fed" value="r1,r2" />
<property name="dfs.namenode.rpc-address.ns-fed.nn1" value="router1:8888" />
<property name="dfs.namenode.rpc-address.ns-fed.nn2" value="router2:8888" />
<!-- フェイルオーバーの設定 -->
<property name="dfs.client.failover.proxy.provider.ns-fed"
value="org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider" />
<property name="dfs.client.failover.random.order" value="true" />
[gateway]$ hadoop fs -ls hdfs://ns-fed/
この場合片方のRouterに障害が起きても、クライアントからのリクエストは失敗しません。
サンプルデータの準備
事前にそれぞれのクラスタでサンプルデータを作成して起きます。
// クラスタA
[gateway]$ hadoop fs -mkdir hdfs://auction-nn1:8020/foo
[gateway]$ echo bar > bar.txt && hadoop fs -put bar.txt hdfs://auction-nn1:8020/foo
hadoop fs -ls -R hdfs://auction-nn1:8020/
drwxr-xr-x - tasanuma users 0 2018-11-30 01:21 hdfs://auction-nn1:8020/foo
-rw-r--r-- 3 tasanuma users 4 2018-11-30 01:21 hdfs://auction-nn1:8020/foo/bar.txt
// クラスタB
[gateway]$ hadoop fs -mkdir hdfs://shopping-nn1:8020/fizz
[gateway]$ echo buzz > buzz.txt && hadoop fs -put buzz.txt hdfs://shopping-nn1:8020/fizz
hadoop fs -ls -R hdfs://shopping-nn1:8020/
drwxr-xr-x - tasanuma users 0 2018-11-30 01:23 hdfs://shopping-nn1:8020/fizz
-rw-r--r-- 3 tasanuma users 5 2018-11-30 01:23 hdfs://shopping-nn1:8020/fizz/buzz.txt
この時点でRouterに対してルートディレクトリをリストしてみると、クラスタAのデータを参照しています。
[gateway]$ hadoop fs -ls -R hdfs://ns-fed/
drwxr-xr-x - tasanuma users 0 2018-11-30 01:21 hdfs://ns-fed/foo
-rw-r--r-- 3 tasanuma users 4 2018-11-30 01:21 hdfs://ns-fed/foo/bar.txt
これはRouterの設定でdfs.federation.router.default.nameserviceId=ns1
としたので、フォールバックによりクラスタAを参照したためです。
マウントの管理
マウントの管理はhdfs dfsrouteradmin
コマンドで行います。(※基本的にこのコマンドはRouterが稼働しているホストで実行します。)
Usage: hdfs dfsrouteradmin :
[-add <source> <nameservice1, nameservice2, ...> <destination> [-readonly] [-order HASH|LOCAL|RANDOM|HASH_ALL] -owner <owner> -group <group> -mode <mode>]
[-update <source> <nameservice1, nameservice2, ...> <destination> [-readonly] [-order HASH|LOCAL|RANDOM|HASH_ALL] -owner <owner> -group <group> -mode <mode>]
[-rm <source>]
[-ls <path>]
[-setQuota <path> -nsQuota <nsQuota> -ssQuota <quota in bytes or quota size string>]
[-clrQuota <path>]
[-safemode enter | leave | get]
[-nameservice enable | disable <nameservice>]
[-getDisabledNameservices]
クラスタのマウント/アンマウント
// マウント
$ hdfs dfsrouteradmin -add <マウント先のパス> <ネームサービス> <マウント元のクラスタ側パス>
// アンマウント
$ hdfs dfsrouteradmin -rm <マウント先のパス>
ネームサービスはRouterの設定のdfs.nameservices
で指定した値です。
ここでは仮想的なディレクトリツリーの/auc
にクラスタA、/shp
にクラスタBのルートディレクトリをマウントしてみます。
[router]$ hdfs dfsrouteradmin -add /auc ns1 /
[router]$ hdfs dfsrouteradmin -add /shp ns2 /
// マウントテーブルの参照
[router]$ hdfs dfsrouteradmin -ls
Mount Table Entries:
Source Destinations Owner Group Mode Quota/Usage
/auc ns1->/ tasanuma users rwxr-xr-x [NsQuota: -/-, SsQuota: -/-]
/shp ns2->/ tasanuma users rwxr-xr-x [NsQuota: -/-, SsQuota: -/-]
クライアント側(Gateway)で参照できるか確認してみます。
[gateway]$ hadoop fs -ls -R hdfs://ns-fed/
drwxrwxrwx - tasanuma users 0 2018-11-30 01:46 hdfs://ns-fed/auc
drwxr-xr-x - tasanuma users 0 2018-11-30 01:21 hdfs://ns-fed/auc/foo
-rw-r--r-- 3 tasanuma users 4 2018-11-30 01:21 hdfs://ns-fed/auc/foo/bar.txt
drwxr-xr-x - tasanuma users 0 2018-11-30 01:21 hdfs://ns-fed/foo <--- hdfs://ns-fed/auc/fooと同じ
-rw-r--r-- 3 tasanuma users 4 2018-11-30 01:21 hdfs://ns-fed/foo/bar.txt
drwxrwxrwx - tasanuma users 0 2018-11-30 01:46 hdfs://ns-fed/shp
drwxr-xr-x - tasanuma users 0 2018-11-30 01:23 hdfs://ns-fed/shp/fizz
-rw-r--r-- 3 tasanuma users 5 2018-11-30 01:23 hdfs://ns-fed/shp/fizz/buzz.txt
1つのHDFSとして2つのクラスタを参照できました。
マウント時のオプションで読み込み専用やオーナー、グループ、パーミッションの指定も可能です。
[router]$ hdfs dfsrouteradmin -add /auc_data ns1 /data -readonly -owner alice -group analysts -mode 770
Successfully added mount point /auc_data
[router]$ hdfs dfsrouteradmin -ls /auc_data
Mount Table Entries:
Source Destinations Owner Group Mode Quota/Usage
/auc_data ns1->/data alice analysts rwxrwx--- [NsQuota: -/-, SsQuota: -/-]
マウントしたネームサービスの無効化/有効可
クラスタ自体をデコミッションしたいときはアンマウントするよりもネームサービスを無効化した方が楽です。
// ネームサービスの無効化
[router]$ hdfs dfsrouteradmin -nameservice disable ns1
// ネームサービスの有効化
[router]$ hdfs dfsrouteradmin -nameservice enable ns1
グローバルなクォータ設定
マウントポイントごとにネームスペースクォータ(ファイル数の制限)とスペースクォータ(データ量の制限)を設定できます。
[router]$ hdfs dfsrouteradmin -setQuota /auc -nsQuota 100 -ssQuota 1024
複数クラスタのマウント
1つのマウントポイントに複数のクラスタを紐づけることができます。
[router]$ hdfs dfsrouteradmin -add /data ns1,ns2 /data -order HASH
[router]hdfs dfsrouteradmin -ls /data
Mount Table Entries:
Source Destinations Owner Group Mode Quota/Usage
/data ns1->/data,ns2->/data tasanuma users rwxr-xr-x [NsQuota: -/-, SsQuota: -/-]
この場合/data
をリストすると、クラスタAとB両方の/data
ディレクトリ配下の全てのファイル・ディレクトリが表示されます。
RouterのWebUI
RouterはNameNodeライクなWeb UIを提供しています。デフォルトのURLはhttp://<Router hostname>:50071
です。
トップページ
ルーティング先のクラスタ情報
Router一覧
マウントテーブル
感想
クラスタ、Router、クライアントで設定値が異なるため少し注意が必要ですが、とても便利な機能だと思います。特にクライアント側に負担が少ないのが良いところです。複数のHadoopクラスタを運用している場合は是非検討してみてください。