概要
HadoopやSparkで用いられるデータを格納するストレージとしてHDFSがあります。このHDFSをKubernetes上に構築することで、Rookに代表されるCloud Native Storageの構築検証を行います。
以前、Apache Spark on Kubernetesの検証にて、Sparkの計算処理についてKubernetes上での実行を検証しました。今回構築するHDFSにより、Apache Spark on Kubernetesの検証でのspark-submit コマンドの--files
オプションで指定するHDFSもKubernetesで管理できるようになります。
(spark-submit コマンド例)
$ bin/spark-submit \
--master k8s://https://<k8s-apiserver-host>:<k8s-apiserver-port> \
--deploy-mode cluster \
--name spark-pi \
--class org.apache.spark.examples.SparkPi \
--jars https://path/to/dependency1.jar,https://path/to/dependency2.jar
--files hdfs://host:port/path/to/file1,hdfs://host:port/path/to/file2
--conf spark.executor.instances=5 \
--conf spark.kubernetes.container.image=<spark-image> \
https://path/to/examples.jar
検証環境
-
Kubernetes環境
- Kubernetes v1.11.1 (master x1, worker x2)
- Rook v0.8
-
HDFS環境
- Zookeeper (x 3)
HDFSの各nodeを管理 - Namenode (x 2)
Datanode に格納するデータのメタ情報を管理 - Journalnode (x 3)
複数Namenode が存在した場合に、コンフリクション起こしたデータの整合性を取るためのノード。多数決により
決めるため奇数個のnodeが必要 - Datanode (x 3)
データを格納。DeamonSetとして定義され、Kubernetesのnode毎にデプロイ。
- Zookeeper (x 3)
事前設定
RookのBlockStorageの設定
今回、HDFSのデータを格納するためのストレージとして、RookのBlockStorageを使います。
Rookについては、Cloud NativeなストレージRookの検証にまとめているので、ご参照ください。
RookのBlockStorageの設定として、StorageClassのリソースをデプロイします。
今回作成するStorageClassをstorageclass.yaml
として保存します。
apiVersion: ceph.rook.io/v1beta1
kind: Pool
metadata:
name: replicapool
namespace: rook-ceph
spec:
replicated:
size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
pool: replicapool
clusterNamespace: rook-ceph
kubectlコマンドを使って、storageclass.yaml
をデプロイします。
$ kubectl create -f storageclass.yaml
pool.ceph.rook.io "replicapool" created
storageclass.storage.k8s.io "rook-ceph-block" created
$ kubectl get sc
NAME PROVISIONER AGE
rook-ceph-block ceph.rook.io/block 23s
HelmのTillerのセットアップ
HDFS on Kubernetesではhelmを使ってデプロイするため、事前にhelmの環境を準備します。
Helmの詳細情報は、公式サイトをご参照ください。
$ helm init
$HELM_HOME has been configured at /Users/ysakashi/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
これにより、kube-systemネームスペースにtiller-deployのPodがデプロイされます。
検証
kubernetes-HDSのダウンロード
今回の検証では、apache-spark-on-k8s/kubernetes-HDFSにて公開されているHelm Chartを使って構築します。まず、始めにGitHubからダウンロードします。
$ git clone https://github.com/apache-spark-on-k8s/kubernetes-HDFS.git
$ cd kubernetes-HDFS/charts
以降、kubernetes-HDFS/charts
ディレクトリで検証を行います。
Zookeeper のデプロイ
ZookeeperをデプロイするためのYAMLファイルをダウンロードします。
$ wget https://raw.githubusercontent.com/kubernetes/contrib/master/statefulsets/zookeeper/zookeeper.yaml
ダウンロードしたzookeeper.yaml
を編集し、RookのBlockStorageをZookeeperのデータ格納用Volumeに割り当てるようにします。変更箇所の前後を以下に示します。
spec.volumeClaimTemplates.storageClassName
にStorageClassで設定したrook-ceph-block
を指定します。
...
volumeClaimTemplates:
- metadata:
name: datadir
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: rook-ceph-block
resources:
requests:
storage: 10Gi
編集したzookeeper.yaml
を使ってZookeeperをデプロイします。
$ kubectl create -f zookeeper.yaml
service "zk-svc" created
configmap "zk-cm" created
poddisruptionbudget "zk-pdb" created
statefulset "zk" created
JournalnodeとNamenodeのデプロイ
NamenodeをデプロイするKubernetesのnodeにラベルを設定します。
$ kubectl label nodes k8s hdfs-namenode-selector=hdfs-namenode-0
node "k8s" labeled
次に、Namenode、Datanodeの設定ファイル(core-site.xml, hdfs-site.yaml)を定義したConfigMapをデプロイします。
$ helm install -n my-hdfs-config --set fullnameOverride=hdfs-config hdfs-config-k8s
NAME: my-hdfs-config
LAST DEPLOYED: Wed Jul 25 21:04:04 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
hdfs-config 2 0s
続いて、Journalnodeをデプロイします。
まず、デプロイの前に、Journalnode のHelmテンプレートhdfs-journalnode-k8s/templates/journalnode-statefulset.yaml
を編集し、JournalnodeのデータをRookのBlockStorageを割り当てるようにします。変更箇所の前後を以下に示します。
spec.volumeClaimTemplates.storageClassName
にStorageClassで設定したrook-ceph-block
を指定します。
...
volumeClaimTemplates:
- metadata:
name: editdir
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: rook-ceph-block
resources:
requests:
storage: {{ .Values.editdataVolumeSize }}
編集したHelmテンプレートを使い、Journalnodeをデプロイします。
$ helm install -n my-hdfs-journalnode hdfs-journalnode-k8s
NAME: my-hdfs-journalnode
LAST DEPLOYED: Wed Jul 25 21:08:53 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/StatefulSet
NAME DESIRED CURRENT AGE
hdfs-journalnode 3 1 1s
==> v1beta1/PodDisruptionBudget
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
hdfs-journalnode 2 N/A 0 1s
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hdfs-journalnode ClusterIP None <none> 8485/TCP,8480/TCP 1s
次に、Namenodeをデプロイします。
デプロイの前に、Zookeeper, Journalnodeと同様にHelmテンプレートhdfs-namenode-k8s/templates/namenode-statefulset.yaml
を編集し、NamenodeのデータをRookのBlockStorageを割り当てるようにします。変更箇所の前後を以下に示します。
spec.volumeClaimTemplates.storageClassName
にStorageClassで設定したrook-ceph-block
を指定します。
...
volumeClaimTemplates:
- metadata:
name: metadatadir
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: rook-ceph-block
resources:
requests:
storage: {{ .Values.metadataVolumeSize }}
編集したHelmテンプレートを使い、Namenodeをデプロイします。
$ helm install -n my-hdfs-namenode hdfs-namenode-k8s
NAME: my-hdfs-namenode
LAST DEPLOYED: Wed Jul 25 21:23:36 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/PodDisruptionBudget
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
hdfs-namenode 1 N/A 0 1s
==> v1/ConfigMap
NAME DATA AGE
hdfs-namenode-start-scripts 3 1s
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hdfs-namenode ClusterIP None <none> 8020/TCP,50070/TCP 1s
==> v1beta1/StatefulSet
NAME DESIRED CURRENT AGE
hdfs-namenode 2 1 1s
デプロイしたJournalnode, Namenodeを確認します。
Namenodeは、hostNetwork: true
, dnsPolicy: ClusterFirstWithHostNet
として指定しているためIPはPodが割り当てられたNodeのIPが割り当てられています。これは、Namenodeが障害などによりダウンし、Podが再作成した場合でもIPが変更にならないようにするためとのことです。
また、Journalnode,Namenodeのデータ保存用のPersistentVolumeには、指定した通りにRookのBlockStorageが割り当てられています。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
hdfs-journalnode-0 1/1 Running 0 18m 10.244.1.165 k8s-node1
hdfs-journalnode-1 1/1 Running 0 18m 10.244.2.136 k8s-node2
hdfs-journalnode-2 1/1 Running 0 17m 10.244.0.179 k8s
hdfs-namenode-0 1/1 Running 0 4m 192.168.0.24 k8s-node1
hdfs-namenode-1 1/1 Running 0 3m 192.168.0.25 k8s-node2
zk-0 1/1 Running 0 27m 10.244.1.164 k8s-node1
zk-1 1/1 Running 0 27m 10.244.2.135 k8s-node2
zk-2 1/1 Running 0 26m 10.244.0.178 k8s
$ kubectl get pvc -o wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
datadir-zk-0 Bound pvc-3adffab1-9002-11e8-afe5-080027571559 10Gi RWO rook-ceph-block 28m
datadir-zk-1 Bound pvc-53492817-9002-11e8-afe5-080027571559 10Gi RWO rook-ceph-block 27m
datadir-zk-2 Bound pvc-6dfb96c9-9002-11e8-afe5-080027571559 10Gi RWO rook-ceph-block 26m
editdir-hdfs-journalnode-0 Bound pvc-7fec7a7d-9003-11e8-afe5-080027571559 20Gi RWO rook-ceph-block 19m
editdir-hdfs-journalnode-1 Bound pvc-910f3baf-9003-11e8-afe5-080027571559 20Gi RWO rook-ceph-block 18m
editdir-hdfs-journalnode-2 Bound pvc-a5422ba8-9003-11e8-afe5-080027571559 20Gi RWO rook-ceph-block 18m
metadatadir-hdfs-namenode-0 Bound pvc-8e0e3b53-9005-11e8-afe5-080027571559 100Gi RWO rook-ceph-block 4m
metadatadir-hdfs-namenode-1 Bound pvc-ac3fad9f-9005-11e8-afe5-080027571559 100Gi RWO rook-ceph-block 3m
$ kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3adffab1-9002-11e8-afe5-080027571559 10Gi RWO Delete Bound default/datadir-zk-0 rook-ceph-block 28m
pvc-53492817-9002-11e8-afe5-080027571559 10Gi RWO Delete Bound default/datadir-zk-1 rook-ceph-block 28m
pvc-6dfb96c9-9002-11e8-afe5-080027571559 10Gi RWO Delete Bound default/datadir-zk-2 rook-ceph-block 27m
pvc-7fec7a7d-9003-11e8-afe5-080027571559 20Gi RWO Delete Bound default/editdir-hdfs-journalnode-0 rook-ceph-block 19m
pvc-8e0e3b53-9005-11e8-afe5-080027571559 100Gi RWO Delete Bound default/metadatadir-hdfs-namenode-0 rook-ceph-block 4m
pvc-910f3baf-9003-11e8-afe5-080027571559 20Gi RWO Delete Bound default/editdir-hdfs-journalnode-1 rook-ceph-block 19m
pvc-a5422ba8-9003-11e8-afe5-080027571559 20Gi RWO Delete Bound default/editdir-hdfs-journalnode-2 rook-ceph-block 18m
pvc-ac3fad9f-9005-11e8-afe5-080027571559 100Gi RWO Delete Bound default/metadatadir-hdfs-namenode-1 rook-ceph-block 4m
以上で、Journalnode, Namenodeのデプロイは完了です。
Datanodeのデプロイ
続いて、データを保存するDatanodeをデプロイします。
Datanodeを、KubernetesのMaster Nodeへデプロイしたくない場合には、以下を設定します。今回は、この設定はスキップします。
kubectl label node YOUR-MASTER-NAME hdfs-datanode-exclude=yes
Datanodeをデプロイします。
Datanodeは、DeamonSetとして定義されているため、各Nodeに一個づつデプロイされます。
データの格納場所としては、各Nodeの/hdfs-data
ディレクトリをhostPathとしてPodへ割り当てます。
$ helm install -n my-hdfs-datanode hdfs-datanode-k8s
NAME: my-hdfs-datanode
LAST DEPLOYED: Wed Jul 25 21:40:31 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/DaemonSet
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
hdfs-datanode 3 3 0 3 0 <none> 0s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
hdfs-datanode-4z9v8 0/1 ContainerCreating 0 0s
hdfs-datanode-kbxgf 0/1 ContainerCreating 0 0s
hdfs-datanode-s2tb9 0/1 ContainerCreating 0 0s
kubectlを使いデプロイしたDatanodeを確認します。
Datanodeも、Namespace同様にhostNetwork: true
, dnsPolicy: ClusterFirstWithHostNet
として指定しているためIPはPodが割り当てられたNodeのIPが割り当てられています。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
hdfs-datanode-4z9v8 1/1 Running 0 6m 192.168.0.24 k8s-node1
hdfs-datanode-kbxgf 1/1 Running 0 6m 192.168.0.23 k8s
hdfs-datanode-s2tb9 1/1 Running 0 6m 192.168.0.25 k8s-node2
...
以上で、Datanodeのデプロイが完了です。
これで、HDFSがKubernetes上に構築されました。
hdfs-clientのデプロイ
構築したHDFSを操作するためのClinetツールをデプロイします。
$ helm install -n hdfs-client hdfs-client
NAME: hdfs-client
LAST DEPLOYED: Wed Jul 25 21:54:10 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
hdfs-client 1 1 1 0 0s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
hdfs-client-64588bbd96-lzdmc 0/1 ContainerCreating 0 0s
hdfs-clientのPodがデプロイされた後、kubectlコマンドでPodへログインし、ClientツールにてHDFSにアクセスすることが出来ます。Clientツールの使い方は、公式ドキュメントのFileSystem Shellを参照してください。
$ kubectl exec -ti hdfs-client-64588bbd96-lzdmc /bin/bash
root@hdfs-client-64588bbd96-lzdmc:/# hdfs dfs -ls
以上で、HDFS on Kubernetesの構築検証は完了です。
クリーンアップ
Helmでデプロイしたリソースを削除します。
$ helm delete --purge hdfs-client my-hdfs-config my-hdfs-datanode my-hdfs-journalnode my-hdfs-namenode
release "hdfs-client" deleted
release "my-hdfs-config" deleted
release "my-hdfs-datanode" deleted
release "my-hdfs-journalnode" deleted
release "my-hdfs-namenode" deleted
次に、Zookeeperを削除します。
$ kubectl delete -f zookeeper.yaml
service "zk-svc" deleted
configmap "zk-cm" deleted
poddisruptionbudget.policy "zk-pdb" deleted
statefulset.apps "zk" deleted
PersistentVolumeClaim, PersistentVolumeを一気に削除します。他に利用しているPersistenvVolumeClaim, PersistentVolumeがある場合は、ひとつづつ削除してください。
$ kubectl delete pvc --all
persistentvolumeclaim "datadir-zk-0" deleted
persistentvolumeclaim "datadir-zk-1" deleted
persistentvolumeclaim "datadir-zk-2" deleted
persistentvolumeclaim "editdir-hdfs-journalnode-0" deleted
persistentvolumeclaim "editdir-hdfs-journalnode-1" deleted
persistentvolumeclaim "editdir-hdfs-journalnode-2" deleted
persistentvolumeclaim "metadatadir-hdfs-namenode-0" deleted
persistentvolumeclaim "metadatadir-hdfs-namenode-1" deleted
$ kubectl delete pv --all
persistentvolume "pvc-3adffab1-9002-11e8-afe5-080027571559" deleted
persistentvolume "pvc-53492817-9002-11e8-afe5-080027571559" deleted
persistentvolume "pvc-6dfb96c9-9002-11e8-afe5-080027571559" deleted
persistentvolume "pvc-7fec7a7d-9003-11e8-afe5-080027571559" deleted
persistentvolume "pvc-8e0e3b53-9005-11e8-afe5-080027571559" deleted
persistentvolume "pvc-910f3baf-9003-11e8-afe5-080027571559" deleted
persistentvolume "pvc-a5422ba8-9003-11e8-afe5-080027571559" deleted
persistentvolume "pvc-ac3fad9f-9005-11e8-afe5-080027571559" deleted
次に、RookのBlockStorage用のStorage Classを削除します。
$ kubectl delete -f storageclass.yaml
pool.ceph.rook.io "replicapool" deleted
storageclass.storage.k8s.io "rook-ceph-block" deleted
最後に、各Nodeにログインし、Namenode,DatanodeからhostPathで利用したディレクトリを削除します。
下記を全Nodeで実行してください。
$ ssh k8s-node1
k8s-node1$ sudo rm -rf hdfs-data
k8s-node1$ sudo rm -rf hdfs-name
k8s-node1$ exit
感想
今回、HDFSをKubernetes上に構築しました。ひとまず動きます。ただし、今回検証で利用したkubernetes-HDFSでは、ClusterFirstWithHostNet
やDatanodeのデータ格納場所にhostPath
を使っているため、少々柔軟性(Flexibility)が欠けている印象です。これらは、ClusterFirstWithHostNet
はServiceでLoadBalancerやExternalNameを使用、hostPath
は、全て外部ストレージを使うようにすることで、柔軟性を高めることができます。ただし、データの格納場所を外部ストレージにしても、現在のままだとNamenode, Datanodeの再作成にデータフォーマットが走ってしまい、データが消えてしまいます。データフォーマットが実行されないように少々ConfigMapで設定しているスクリプトファイルを工夫してあげる必要があります。
このように、HDFS on Kubernetesの柔軟性を高めつつデータを保持し続けるために、頑張ってみるというのもアリかとは思います。しかし、使い方(ユースケース)を工夫する方が良いかもしれません。つまり、常にデータを保存し続けるためのストレージとしてのHDFSではなく、SparkやHadoopで利用する時のみデータを格納する一時的なデータ格納場所として利用するのが良いのかもしれません。これを実現するため、HDFS on Kubernetesを使いこなすには、ETLツールとの連携に取り組むのもアリかと思います。
今回は、ひとまずkubernetes-HDFSを動かすことが出来ました。ただし、kubernetes-HDFS単体での活用シーンは考え所です。