1. ysakashita

    Posted

    ysakashita
Changes in title
+KubernetesにおけるContainer Storage Interface (CSI)の概要と検証
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,514 @@
+# CSIの概要
+ Kubernetes v1.9より***Container Storage Interface*** (CSI)がAlphaバージョンとしてサポートが開始され、v1.10にてBetaバージョンとしてサポートされています。v1.8 までのKubernetesのストレージ関連の機能は、Kubernetesのソースに直に組み込まれる実装("in-tree")で提供されていました。そのため、外部ストレージの開発を行う3rd パーティベンダは、Kubernetes のソースコードへアップストリームする必要があり、リリースのタイミングをKubernetesの開発チームと歩調をあわせる必要がありました。また、v1.8にて追加された[Flexvolume](https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md)では、Kubernetesの Node, MasterのRoot FileSystem にアクセスが必要であり、デプロイが煩雑でした。
+ Kubernetesでは、CSIをサポートすることで、Kubernetesのソースに組み込まず3rdパーティベンダが独自に実装できる("out-of-tree")にて提供することが可能となりました。さらに、Kubernetesでは以前より備えているStorageClass, PersistentVolume, PersistentVlumeClaimを引き続きCSIでも使うことで、コンテナをデプロイしようとするユーザから見て、v1.8以前と同様の操作でCSI経由で外部ストレージを利用することが可能となります。
+ このCSIは,Kubernetes, Mesos, Docker, CloudFoundry など幅広くコンテナ環境で利用できるようにすべく、Kubernetesとは独立し、[Container Storage Interface コミュニティ](https://github.com/container-storage-interface)にて仕様が策定されています。
+
+## KubernetesにおけるCSIのアーキテクチャ
+KubernetesにおけるCSIのアーキテクチャ図を示します。
+![csi-arch.png](https://qiita-image-store.s3.amazonaws.com/0/229880/34cafde0-4fa2-d58a-4f7f-02d020b3ec33.png)
+
+3rdパーティベンダは、Kubeletとやり取りを行うNode Pluginと、API Serverからのリクエストに対応するController Pluginの2つのCSI Volume Driverを用意する必要があります。
+Node PluginはKubelet毎(Node毎)に1つのPodが必要なため、DaemonSet Podとして構築します。
+Controller Pluginは、Nodeに縛られずKubernetes Cluster上に存在すれば良いので、StatefulSetにて構築します。
+
+## CSI Volumeの状態遷移
+次に、CSI Volumeの状態について状態遷移図を用い説明します。
+Kubernetesでは、Controller PluginからのリクエストにてCSI Volumeの状態が遷移します。
+![csi-vol.png](https://qiita-image-store.s3.amazonaws.com/0/229880/1182189d-9ddf-6434-d551-a10a4c974462.png)
+まず、`CreateVolume`リクエストによりCSI Volumeが作成されます。その後、NODE_READY, VOL_READYの状態を経て、Pod(Container)が利用できる状態のPUBLISHEDへ状態遷移します。
+CSIでは、この状態遷移に対応したAPIが定義されています。この状態遷移図にないAPIは、`ListVolumes`などの参照系のAPIになります。
+また、上記に示した状態遷移が基本となっていますが、3rdパーティベンダの実装によっては、NODE_READY, VOL_READYが省略されるケースもあるようです。
+
+## KubernetesでCSIを使うための設定
+v1.9ではCSIはAlphaバージョンのため、以下のフラグを設定する必要があります。
+
+- API server binary
+ - --feature-gates=CSIPersistentVolume=true
+ - --runtime-config=storage.k8s.io/v1alpha1=true
+- API server binary and kubelet binaries
+ - --feature-gates=MountPropagation=true
+ - --allow-privileged=true
+
+v1.10ではCSIはBetaバージョンのため、feature-gates, runtime-configの設定は不要です。
+
+# 検証
+検証は、以下を用いて行います。
+- Kubernetes v1.10 (kubeadmを使いMacのVirtualBox上に構築)
+- [CSI Drivers](https://github.com/kubernetes-csi/drivers)
+- 外部ストレージ: NFS Server(Mac macOS(10.12.6)のNFS Serverを利用)
+
+※ kubeadmを使ったKubernetes v1.10の構築手順については割愛します。kubeadmを使った構築は、[こちらのドキュメント](https://kubernetes.io/docs/setup/independent/install-kubeadm/)を参照ください。
+
+## 外部ストレージの準備
+まず初めに、外部ストレージとしてNFS Serverを準備します。
+共有するディレクトリを作成し、NFS Serverの設定ファイル(/etc/exports)を作成します。
+今回は、192.168.0.0/24のネットワーク上で検証を行うため,下記例では192.168.0.0 のネットワークからのアクセスを許可しています.
+
+```
+$ sudo mkdir /share
+$ sudo chmod 777 /share
+$ sudo vi /etc/exports
+/share -mapall=nobody:wheel -network 192.168.0.0 -mask 255.255.255.0
+```
+次に,NFS Serverのデーモンを起動します.
+
+```
+$ sudo nfsd start
+$ sudo nfsd update
+$ sudo showmount -e
+Exports list on localhost:
+/share 192.168.0.0
+```
+
+NFSでシェアしたディレクトリがKubernetesのKubeletが動作するNodeのマシンから利用できるか確認します。
+KubernetesのNodeのマシンにSSHなどでログインし、シェアしたディレクトリをマウントしてみます。
+
+```
+$ sudo mount -t nfs 192.168.0.11:/share /mnt
+```
+上記の192.168.0.11は母艦のmacOSのIPアドレスです。各自の環境に合わせて変更してください。
+もし、mount コマンドにて以下のエラーが出る人は、NodeのマシンにNFS Clientがインストールされていない可能性がありますので、apt-getコマンドなどを使いNFS Clientをインストールしてください(筆者はしばしハマりました)。
+
+```
+$ sudo mount -t nfs 192.168.0.11:/share /mnt
+mount: wrong fs type, bad option, bad superblock on 192.168.0.11:/share,
+ missing codepage or helper program, or other error
+ (for several filesystems (e.g. nfs, cifs) you might
+ need a /sbin/mount.<type> helper program)
+
+ In some cases useful info is found in syslog - try
+ dmesg | tail or so.
+
+$ sudo apt-get update
+$ sudo apt-get install nfs-common -y
+```
+
+df コマンドを使い、NFSでシェアされたディレクトリ/shareが/mntにマウントされているのを確認します。
+
+```
+$ df -h
+Filesystem Size Used Avail Use% Mounted on
+<snip>
+192.168.0.11:/share 931G 524G 408G 57% /mnt
+
+```
+NodeのマシンからNFS Serverでシェアしたディレクトリが利用できることが確認できたので、マウントを外しておきます。
+
+```
+$ sudo umount /mnt
+$ exit
+```
+
+# CSI Driversのダウンロード
+本検証では、利用するCSIとして、CSIの実装サンプルとして提供している[CSI Drivers](https://github.com/kubernetes-csi/drivers)を利用します。
+
+```
+$ git clone https://github.com/kubernetes-csi/drivers.git
+$ cd drivers/pkg/nfs/deploy/kubernetes
+```
+以降、`drivers/pkg/nfs/deploy/kubernetes`ディレクトリで作業を行います。
+
+## CSI Volume Driver Container(Node Plugin)の構築
+`drivers/pkg/nfs/deploy/kubernetes`ディレクトリにあるNode Pluginの構成定義`csi-nodeplugin-nfsplugin.yaml`と、そのRBACの設定定義`csi-nodeplugin-rbac.yaml`を使って構築します。
+以下に、Node Pluginの構成定義を示します。
+
+```csi-nodeplugin-nfsplugin.yaml
+kind: DaemonSet
+apiVersion: apps/v1beta2
+metadata:
+ name: csi-nodeplugin-nfsplugin
+spec:
+ selector:
+ matchLabels:
+ app: csi-nodeplugin-nfsplugin
+ template:
+ metadata:
+ labels:
+ app: csi-nodeplugin-nfsplugin
+ spec:
+ serviceAccount: csi-nodeplugin
+ hostNetwork: true
+ containers:
+ - name: driver-registrar
+ image: quay.io/k8scsi/driver-registrar:v0.2.0
+ args:
+ - "--v=5"
+ - "--csi-address=$(ADDRESS)"
+ env:
+ - name: ADDRESS
+ value: /plugin/csi.sock
+ - name: KUBE_NODE_NAME
+ valueFrom:
+ fieldRef:
+ fieldPath: spec.nodeName
+ volumeMounts:
+ - name: plugin-dir
+ mountPath: /plugin
+ - name: nfs
+ securityContext:
+ privileged: true
+ capabilities:
+ add: ["SYS_ADMIN"]
+ allowPrivilegeEscalation: true
+ image: quay.io/k8scsi/nfsplugin:v0.2.0
+ args :
+ - "--nodeid=$(NODE_ID)"
+ - "--endpoint=$(CSI_ENDPOINT)"
+ env:
+ - name: NODE_ID
+ valueFrom:
+ fieldRef:
+ fieldPath: spec.nodeName
+ - name: CSI_ENDPOINT
+ value: unix://plugin/csi.sock
+ imagePullPolicy: "IfNotPresent"
+ volumeMounts:
+ - name: plugin-dir
+ mountPath: /plugin
+ - name: pods-mount-dir
+ mountPath: /var/lib/kubelet/pods
+ mountPropagation: "Bidirectional"
+ volumes:
+ - name: plugin-dir
+ hostPath:
+ path: /var/lib/kubelet/plugins/csi-nfsplugin
+ type: DirectoryOrCreate
+ - name: pods-mount-dir
+ hostPath:
+ path: /var/lib/kubelet/pods
+ type: Directory
+```
+Node Pluginは、Kubelet毎(Node毎)に必要となるため、Node毎に1つづつデプロイされるようにDaemonSetを使って構築します。また、UDS(Unix Domain Socket)のunix://plugin/csi.sockおよび/var/lib/kubeletのVolumeを使いKubeletとやり取りを行います。
+
+kubectlコマンドを用い、上記のNode Pluginと、そのRBAC設定定義をデプロイします。
+
+```
+$ kubectl create -f csi-nodeplugin-nfsplugin.yaml
+daemonset.apps "csi-nodeplugin-nfsplugin" created
+
+$ kubectl create -f csi-nodeplugin-rbac.yaml
+serviceaccount "csi-nodeplugin" created
+clusterrole.rbac.authorization.k8s.io "csi-nodeplugin" created
+clusterrolebinding.rbac.authorization.k8s.io "csi-nodeplugin" created
+```
+Node Pluginがデプロイされていることを確認します。
+
+```
+$ kubectl get ds
+NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
+csi-nodeplugin-nfsplugin 1 1 1 1 1 <none> 1m
+```
+
+## CSI Volume Driver Container(Controller Plugin)の構築
+続いて、Controller Pluginを、Controller Pluginの構成定義`csi-attacher-nfsplugin.yaml`とそのRBAC設定定義`csi-attacher-rbac.yaml`を使って構築します。
+以下に、Controller PluginとアクセスするためのServiceの構成定義を示します。
+
+```csi-attacher-nfsplugin.yaml
+kind: Service
+apiVersion: v1
+metadata:
+ name: csi-attacher-nfsplugin
+ labels:
+ app: csi-attacher-nfsplugin
+spec:
+ selector:
+ app: csi-attacher-nfsplugin
+ ports:
+ - name: dummy
+ port: 12345
+
+---
+kind: StatefulSet
+apiVersion: apps/v1beta1
+metadata:
+ name: csi-attacher-nfsplugin
+spec:
+ serviceName: "csi-attacher"
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app: csi-attacher-nfsplugin
+ spec:
+ serviceAccount: csi-attacher
+ containers:
+ - name: csi-attacher
+ image: quay.io/k8scsi/csi-attacher:v0.2.0
+ args:
+ - "--v=5"
+ - "--csi-address=$(ADDRESS)"
+ env:
+ - name: ADDRESS
+ value: /var/lib/csi/sockets/pluginproxy/csi.sock
+ imagePullPolicy: "IfNotPresent"
+ volumeMounts:
+ - name: socket-dir
+ mountPath: /var/lib/csi/sockets/pluginproxy/
+
+ - name: nfs
+ image: quay.io/k8scsi/nfsplugin:v0.2.0
+ args :
+ - "--nodeid=$(NODE_ID)"
+ - "--endpoint=$(CSI_ENDPOINT)"
+ env:
+ - name: NODE_ID
+ valueFrom:
+ fieldRef:
+ fieldPath: spec.nodeName
+ - name: CSI_ENDPOINT
+ value: unix://plugin/csi.sock
+ imagePullPolicy: "IfNotPresent"
+ volumeMounts:
+ - name: socket-dir
+ mountPath: /plugin
+ volumes:
+ - name: socket-dir
+ emptyDir:
+```
+Controller Pluginでは、socket-dirとしてemptyDirをマウントし、UDSのunix://plugin/csi.sockを経由し、csi-attacherとやり取りを行います。
+
+kubectlコマンドを用い、上記のController PluginとService及び、RBAC設定定義をデプロイします。
+
+```
+$ kubectl create -f csi-attacher-nfsplugin.yaml
+service "csi-attacher-nfsplugin" created
+statefulset.apps "csi-attacher-nfsplugin" created
+
+$ kubectl create -f csi-attacher-rbac.yaml
+serviceaccount "csi-attacher" created
+clusterrole.rbac.authorization.k8s.io "external-attacher-runner" created
+clusterrolebinding.rbac.authorization.k8s.io "csi-attacher-role" created
+```
+Controller Pluginと、アクセスするためのServiceがデプロイされていることを確認します。
+
+```
+$ kubectl get sts
+NAME DESIRED CURRENT AGE
+csi-attacher-nfsplugin 1 1 1m
+
+$ kubectl get svc
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+csi-attacher-nfsplugin ClusterIP 10.110.73.102 <none> 12345/TCP 3m
+kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4d
+```
+ここまでのデプロイで、CSI を使うためのCSI Volume Driverの設定が完了しました。
+
+## CSI Volumeを使ったPodの構築
+次は、デプロイしたCSI Volume Driverを通じ、PodからNFS Serverにてシェアされたディレクトリを利用します。
+サンプルのPodの定義として、[CSI Drivers](https://github.com/kubernetes-csi/drivers)が用意しているnginxの構成定義を使います。
+
+```
+$ /home/sakasita/csi/drivers/pkg/nfs/examples/kubernetes
+$ vi nginx.yaml
+```
+サンプルが保存されているディレクトリへ移動後、viなどのエディタを用い`nginx.yaml`を編集します。
+編集箇所を以下に示します。
+
+```
+$ git diff
+diff --git a/pkg/nfs/examples/kubernetes/nginx.yaml b/pkg/nfs/examples/kubernetes/nginx.yaml
+index 8048e9c..28a9c23 100644
+--- a/pkg/nfs/examples/kubernetes/nginx.yaml
+@@ -13,8 +13,8 @@ spec:
+ driver: csi-nfsplugin
+ volumeHandle: data-id
+ volumeAttributes:
+- server: 127.0.0.1
+- share: /export
++ server: 192.168.0.11
++ share: /share
+ ---
+ apiVersion: v1
+ kind: PersistentVolumeClaim
+```
+`volumeAttributes`に母艦のmacOS上のNFS Serverでシェアしたディレクトリを指定します。
+変更後の`nginx.yaml`を以下に示します。`nginx.yaml`ではCSIを使うためのPersistenvVolume, PersistentVolumeClaimおよびnginxの構成定義が含まれています。
+
+
+```nginx.yaml
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: data-nfsplugin
+ labels:
+ name: data-nfsplugin
+spec:
+ accessModes:
+ - ReadWriteMany
+ capacity:
+ storage: 100Gi
+ csi:
+ driver: csi-nfsplugin
+ volumeHandle: data-id
+ volumeAttributes:
+ server: 192.168.0.11
+ share: /share
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: data-nfsplugin
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 100Gi
+ selector:
+ matchExpressions:
+ - key: name
+ operator: In
+ values: ["data-nfsplugin"]
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: my-nginx
+spec:
+ containers:
+ - image: maersk/nginx
+ imagePullPolicy: Always
+ name: nginx
+ ports:
+ - containerPort: 80
+ protocol: TCP
+ volumeMounts:
+ - mountPath: /var/www
+ name: data-nfsplugin
+ volumes:
+ - name: data-nfsplugin
+ persistentVolumeClaim:
+ claimName: data-nfsplugin
+```
+kubectlコマンドにて上記PersistenvVolume, PersistentVolumeClaimおよびnginxをデプロイします。
+
+```
+$ kubectl create -f nginx.yaml
+persistentvolume "data-nfsplugin" created
+persistentvolumeclaim "data-nfsplugin" created
+pod "nginx" created
+```
+
+## 確認
+CSIを利用し、NFS ServerでシェアされたディレクトリがPodでマウントされていることを確認します。
+まず、PersistentVolumeClaim, PersistentVolumeを確認します。
+
+```
+$ kubectl get pvc
+NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
+data-nfsplugin Bound data-nfsplugin 100Gi RWX 8m
+
+$ kubectl get pv
+NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
+data-nfsplugin 100Gi RWX Retain Bound default/data-nfsplugin 8m
+
+$ kubectl describe pv data-nfsplugin
+Name: data-nfsplugin
+Labels: name=data-nfsplugin
+Annotations: pv.kubernetes.io/bound-by-controller=yes
+Finalizers: [kubernetes.io/pv-protection]
+StorageClass:
+Status: Bound
+Claim: default/data-nfsplugin
+Reclaim Policy: Retain
+Access Modes: RWX
+Capacity: 100Gi
+Node Affinity: <none>
+Message:
+Source:
+ Type: CSI (a Container Storage Interface (CSI) volume source)
+ Driver: csi-nfsplugin
+ VolumeHandle: data-id
+ ReadOnly: false
+Events: <none>
+```
+PersistetVolumeClaim, PersistentVolumeが正しくデプロイされているのが確認できます。また、kubectl describeで作成されたPersistentVolumeを見てみると、Source.TypeにてCSIのVolumeであることが確認できます。
+続いて、nginxのPodを確認します。
+
+```
+$ kubectl get pods
+NAME READY STATUS RESTARTS AGE
+csi-attacher-nfsplugin-0 2/2 Running 0 1h
+csi-nodeplugin-nfsplugin-4k924 2/2 Running 0 1h
+nginx 1/1 Running 0 17m
+
+$ kubectl exec -ti nginx /bin/bash
+root@nginx:/# df -h
+Filesystem Size Used Avail Use% Mounted on
+none 30G 4.8G 24G 17% /
+tmpfs 2.0G 0 2.0G 0% /dev
+tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
+/dev/mapper/k8s--vg-root 30G 4.8G 24G 17% /etc/hosts
+192.168.0.11:/share 931G 523G 408G 57% /var/www
+shm 64M 0 64M 0% /dev/shm
+tmpfs 2.0G 12K 2.0G 1% /run/secrets/kubernetes.io/serviceaccount
+tmpfs 2.0G 0 2.0G 0% /sys/firmware
+```
+nginxのPodにログインし、dfコマンドで見てみると、NFS Serverでシェアされた/shareディレクトリが、正しくマウントされているのが確認できます。
+このように、CSIを通じて外部ストレージを利用する際、PersistentVolumeはCSIであることを意識しますが、PersistentVolumeClaim, Podの構成定義では意識しません。
+これにより、Podを作成するKubernetesの利用者(ユーザ)は、CSIかどうかを意識することなく、Kuberneetesの管理者が定義したPersistentVolumeを利用するだけで、外部ストレージを利用できます。
+
+## クリーンアップ
+検証にてデプロイしたPersistentVolume,PersistentVolumeClaim,nginxのPodを削除します。
+
+```
+$ kubectl delete -f nginx.yaml
+persistentvolume "data-nfsplugin" deleted
+persistentvolumeclaim "data-nfsplugin" deleted
+pod "nginx" deleted
+```
+次に、CSI Volume Driver Container(Controller Plugin)を削除します。
+
+```
+$ cd ../../deploy/kubernetes
+$ kubectl delete -f csi-attacher-nfsplugin.yaml
+service "csi-attacher-nfsplugin" deleted
+statefulset.apps "csi-attacher-nfsplugin" deleted
+
+$ kubectl delete -f csi-attacher-rbac.yaml
+serviceaccount "csi-attacher" deleted
+clusterrole.rbac.authorization.k8s.io "external-attacher-runner" deleted
+clusterrolebinding.rbac.authorization.k8s.io "csi-attacher-role" deleted
+```
+続いて、CSI Volume Driver Container(Node Plugin)を削除します。
+
+```
+$ kubectl delete -f csi-nodeplugin-nfsplugin.yaml
+daemonset.apps "csi-nodeplugin-nfsplugin" deleted
+
+$ kubectl delete -f csi-nodeplugin-rbac.yaml
+serviceaccount "csi-nodeplugin" deleted
+clusterrole.rbac.authorization.k8s.io "csi-nodeplugin" deleted
+clusterrolebinding.rbac.authorization.k8s.io "csi-nodeplugin" deleted
+```
+母艦のmacOSのコンソールに移り、macOS上のNFS Serverの設定を削除します。
+
+```
+$ sudo vi /etc/exports
+
+$ sudo cat /etc/exports
+$
+```
+最後に、macOS上のNFS Serverのデーモンを停止し、検証に利用した/shareディレクトリを削除します。
+
+```
+$ sudo nfsd update
+$ sudo showmount -e
+Exports list on localhost:
+
+$ sudo rmdir /share
+$ sudo nfsd stop
+```
+
+
+# 感想
+ Kubernetesでは、3rdパーティベンダが独自に開発が進められるようにout of treeとしてCSIが提供され始めました。これは、かつて仮想化環境の世界において、OpenStackがCinderを提供し、3rdパーティベンダがCinder Driverを開発していることに似ています。コンテナ環境向けに提供されているのがCSIとなります。違いとしては、Cinder Driverは、OpenStackに限定されていましたが、CSIは、Kubernetes, Mesos, Docker, CloudFoundryなどの幅広いコンテナ環境で利用できる点です。この点は3rdパーティベンダにとって好ましい点だと思います。
+ しかし、CSIのVolumeの状態遷移にて示したように、現時点のCSIは非常にシンプルかつ基本機能しかありません。SnapshotやBackupといった高度なストレージの機能については、まだ姿形もありません。CSIで、これらの高度なストレージ機能が登場するまでは、管理者は外部ストレージの管理I/Fを利用し、SnapshotやBackupなどを利用することになります。もしくは、CSIのCreateVolume(もしくは、Node Publish Volume)が呼び出されると同時にSnapshotやBackupを自動設定してくれるようなCSI Driverを、3rdパーティベンダが開発してくれるのを待つかです。いずれにしても、CSIが登場したことで3rdパーティベンダがコンテナ向けのストレージを開発する「場」ができました。まだまだベイビーフェーズのCSIですが、3rdパーティベンダの開発が加速し徐々に機能が拡充されてくることに期待します。
+
+# 参考情報
+- [Introducing Container Storage Interface (CSI) Alpha for Kubernetes](http://blog.kubernetes.io/2018/01/introducing-container-storage-interface.html)
+- [Kubernetes CSI Documentation](https://kubernetes-csi.github.io/docs/Home.html)
+- [Container Storage Interface(CSI)](https://github.com/container-storage-interface/spec/blob/master/spec.md)
+ - [CSI Spec](https://github.com/container-storage-interface/spec)
+- [CSI Drivers](https://github.com/kubernetes-csi/drivers)