LoginSignup
17
9

More than 5 years have passed since last update.

HDFS on Kubernetesの検証

Last updated at Posted at 2018-07-26

概要

 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毎にデプロイ。

事前設定

RookのBlockStorageの設定

 今回、HDFSのデータを格納するためのストレージとして、RookのBlockStorageを使います。
Rookについては、Cloud NativeなストレージRookの検証にまとめているので、ご参照ください。
RookのBlockStorageの設定として、StorageClassのリソースをデプロイします。
今回作成するStorageClassをstorageclass.yamlとして保存します。

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を指定します。

zookeeper.yaml(変更箇所)
...
 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を指定します。

hdfs-journalnode-k8s/templates/journalnode-statefulset.yaml(変更箇所)
... 
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を指定します。

hdfs-namenode-k8s/templates/namenode-statefulset.yaml(変更箇所)
...
  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単体での活用シーンは考え所です。

参考情報

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9