2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetes: NFSのCSIドライバーを動かして、CSIの動作を調べてみた

Last updated at Posted at 2024-11-09

はじめに

Kubernetes上でボリュームを調達してPodにマウントして使う場合に、CSI(Container Storage Interface)と呼ばれるインターフェースを使うことが多いです。例えばAmazon EBSを使いたい場合はAmazon EBS CSI Driver、オンプレミスのKubernetesでOpenEBSを使いたい時はOpenEBS CSI Driver等が利用可能です。CSIを使うと、Kubernetes上でStorageClass、PVC、PV等のリソースにより、様々なストレージを共通の方法で利用できるようになります。

今回はCSIがどんな動きをしているのかを、NFSのCSIドライバー(NFS CSI Driver)を使って調べてみました。利用するCSIドライバーは、Kubernetes CSIのGitHubで公開されている以下のcsi-driver-nfsです。

必要条件

以下が準備できている必要があります。本記事では、それぞれの詳細については触れません。

  • Kubernetesクラスターを構築済であること
  • Kubernetesのノードからアクセス可能なNFSサーバがあること
  • kubectlを実行するマシンに、HelmとHelmfileがインストールされていること

csi-driver-nfs のHelmチャートのインストール

以下のHelmfileを使ってインストールします。

csi-driver-nfs.yaml
repositories:

- name: csi-driver-nfs
  url: https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts

releases:

- name: csi-driver-nfs
  namespace: kube-system
  chart: csi-driver-nfs/csi-driver-nfs
  version: v4.9.0
  values:
  - storageClass:
      create: true
      name: nfs-csi
      parameters:
        server: 10.0.0.5       # 構築済みのNFSサーバのホスト名を記載
        share: /srv/nfs_share  # NFSサーバで /etc/exports で共有しているディレクトリを記載
      mountOptions:
      - nfsvers=4.1

インストールコマンド

helmfile apply -f csi-driver-nfs.yaml

作成されたリソースの確認

ワークロードリソースおよびサービスリソースは、以下の DaemonSet、Deployment が作成されています。

$ kubectl get deploy,ds -n kube-system -l helm.sh/chart=csi-driver-nfs-v4.9.0
NAMESPACE     NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/csi-nfs-controller   1/1     1            1           2m9s

NAMESPACE     NAME                          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
kube-system   daemonset.apps/csi-nfs-node   2         2         2       2            2           kubernetes.io/os=linux   2m9s

またストレージに関するリソースとして、以下の nfs-csi というStorageClass が作成されます。

 kubectl get storageclass
NAME      PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-csi   nfs.csi.k8s.io   Delete          Immediate           false                  4m43s

csi-nfs-controller と csi-nfs-node に関して、各Podにどんなコンテナが作られているかを確認すると、以下のように複数立ち上がっているのが分かります。

csi-nfs-controller(Deployment) の Pod で使われているコンテナ

$ kubectl get deploy -n kube-system csi-nfs-controller -o jsonpath='{range .spec.template.spec.containers[*]}- Name:  {.name}{"\n"}  Image: {.image}{"\n"}{end}'
- Name:  csi-provisioner
  Image: registry.k8s.io/sig-storage/csi-provisioner:v5.0.2
- Name:  csi-snapshotter
  Image: registry.k8s.io/sig-storage/csi-snapshotter:v8.0.1
- Name:  liveness-probe
  Image: registry.k8s.io/sig-storage/livenessprobe:v2.13.1
- Name:  nfs
  Image: registry.k8s.io/sig-storage/nfsplugin:v4.9.0

csi-nfs-node(DaemonSet) の Pod で使われているコンテナ

$ kubectl get ds -n kube-system csi-nfs-node -o jsonpath='{range .spec.template.spec.containers[*]}- Name:  {.name}{"\n"}  Image: {.image}{"\n"}{end}'
- Name:  liveness-probe
  Image: registry.k8s.io/sig-storage/livenessprobe:v2.13.1
- Name:  node-driver-registrar
  Image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.11.1
- Name:  nfs
  Image: registry.k8s.io/sig-storage/nfsplugin:v4.9.0

この状態を図にしてみましょう。

色分けしたそれぞれのコンテナは、以下の機能を持ちます。

Kubernetes CSI Sidecar Container(オレンジ色)

NFSドライバに限らず、他のCSIドライバでも共通に利用するサイドカーコンテナです。Kubernetes APIと通信して、その内容を後述のCSI driver containerに伝えます。正式な情報はこちらを参照ください。

Third-party CSI Driver Container(水色)

本記事の例では「NFS用のCSIプラグインとしてNFSストレージ固有に使われるコンテナ」ですが、一般的には CSI Driver と呼ばれています。サイドカーコンテナから届いたリクエストに応じて、実際のストレージにボリュームを作ったりマウントしたりします。
こちらはサイドカーコンテナとは異なり、利用するストレージによって各ベンダーから提供されるもの(例えばAmazon EBS用とかCephFS用とか)を使います。

CSIドライバーはこのように、「ストレージ固有のCSI Driver」と「Kubernetes APIと通信するサイドカーコンテナ」が連携して動作する仕組みになっています。各コンテナで役割を分けることで、CSI driverを開発するベンダーは Kubernetes API の仕様や通信を気にせずに、ストレージ固有のロジックに集中して実装できるメリットがあります。

PVCを作ってみる

それでは、さきほどインストールした NFS CSI driver を使ってPVCを作ってみましょう。
以下のマニフェストを用意します。storageClassNamenfs-csi に指定するのがポイントです。

nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
  namespace: default
spec:
  storageClassName: nfs-csi
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

実行コマンド

$ kubectl apply -f nfs-pvc.yaml
persistentvolumeclaim/nfs-pvc created

PVCだけでなく、動的プロビジョニングによりPVも作られました。

$ kubectl get pvc,pv -n default
NAME                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/nfs-pvc   Bound    pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a   1Gi        RWX            nfs-csi        <unset>                 6h9m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a   1Gi        RWX            Delete           Bound    default/nfs-pvc   nfs-csi        <unset>                          6h9m

NFSサーバが起動しているホスト上の端末でも、次のようにPVに対応するディレクトリが作られたのを確認できます。

[host-of-nfs-server]$ ls -l /srv/nfs_share/
total 4
drwxr-xr-x 2 nobody nogroup 4096 Nov  8 22:29 pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a

どのようにしてボリュームが作られたのか?

PVCが作られた際に、CSIドライバーを使ってボリュームがどのように作られるかを図にしてみました。ボリューム作成で関係するのは csi-nfs-controller のDeploymentリソースのみです。

PVCが作られると、csi-provisionerコンテナ がPVCが作られたというイベントを検知します。その内容をCSI Driverの nfsコンテナ に通知して、NFSボリュームが作られるという流れです。

上記の流れを、実際にログで確認してみましょう。

ログで確認: (1)PVC作成を検知

csi-provisionerのログを確認すると、

kubectl logs -n kube-system deploy/csi-nfs-controller -c csi-provisioner

以下ログの1行目で、PVC作成のイベントを検知しているのがわかります。

I1108 22:29:25.621276       1 event.go:389] "Event occurred" object="default/nfs-pvc" fieldPath="" kind="PersistentVolumeClaim" apiVersion="v1" type="Normal" reason="Provisioning" message="External provisioner is provisioning volume for claim \"default/nfs-pvc\""
I1108 22:29:26.169109       1 controller.go:955] successfully created PV pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a for PVC nfs-pvc and csi volume name 10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##
I1108 22:29:26.179005       1 event.go:389] "Event occurred" object="default/nfs-pvc" fieldPath="" kind="PersistentVolumeClaim" apiVersion="v1" type="Normal" reason="ProvisioningSucceeded" message="Successfully provisioned volume pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a"

ログで確認: (2)ボリューム作成リクエスト ~ (3)ボリューム作成 ~ (4)PV作成

nfsのログを確認すると、

kubectl logs -n kube-system deploy/csi-nfs-controller -c nfs

以下のログが出力されます。

I1108 22:29:25.622630       1 utils.go:110] GRPC call: /csi.v1.Controller/CreateVolume
I1108 22:29:25.622698       1 utils.go:111] GRPC request: {"capacity_range":{"required_bytes":1073741824},"name":"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a","parameters":{"csi.storage.k8s.io/pv/name":"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a","csi.storage.k8s.io/pvc/name":"nfs-pvc","csi.storage.k8s.io/pvc/namespace":"default","server":"10.0.0.5","share":"/srv/nfs_share"},"volume_capabilities":[{"AccessType":{"Mount":{"mount_flags":["nfsvers=4.1"]}},"access_mode":{"mode":5}}]}
I1108 22:29:25.623128       1 controllerserver.go:496] internally mounting 10.0.0.5:/srv/nfs_share at /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a
I1108 22:29:25.623316       1 nodeserver.go:132] NodePublishVolume: volumeID(10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##) source(10.0.0.5:/srv/nfs_share) targetPath(/tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a) mountflags([nfsvers=4.1])
I1108 22:29:25.625159       1 mount_linux.go:243] Detected OS without systemd
I1108 22:29:25.625191       1 mount_linux.go:218] Mounting cmd (mount) with arguments (-t nfs -o nfsvers=4.1 10.0.0.5:/srv/nfs_share /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a)
I1108 22:29:26.136750       1 nodeserver.go:149] skip chmod on targetPath(/tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a) since mountPermissions is set as 0
I1108 22:29:26.136795       1 nodeserver.go:151] volume(10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##) mount 10.0.0.5:/srv/nfs_share on /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a succeeded
I1108 22:29:26.140767       1 controllerserver.go:511] internally unmounting /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a
I1108 22:29:26.140808       1 nodeserver.go:172] NodeUnpublishVolume: unmounting volume 10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a## on /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a
I1108 22:29:26.140819       1 nodeserver.go:177] force unmount 10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a## on /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a
I1108 22:29:26.141001       1 mount_helper_common.go:56] unmounting "/tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a" (corruptedMount: false, mounterCanSkipMountPointChecks: true)
I1108 22:29:26.141020       1 mount_linux.go:789] Unmounting /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a
I1108 22:29:26.164058       1 mount_helper_common.go:150] Deleting path "/tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a"
I1108 22:29:26.166352       1 nodeserver.go:185] NodeUnpublishVolume: unmount volume 10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a## on /tmp/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a successfully
I1108 22:29:26.166567       1 utils.go:117] GRPC response: {"volume":{"volume_context":{"csi.storage.k8s.io/pv/name":"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a","csi.storage.k8s.io/pvc/name":"nfs-pvc","csi.storage.k8s.io/pvc/namespace":"default","server":"10.0.0.5","share":"/srv/nfs_share","subdir":"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a"},"volume_id":"10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##"}}

ログの各箇所で、以下が実行されたことを確認できます。

  • (2) ボリューム作成リクエスト : 1行目
    • csi-provisionerからgRPCで、CreateVolumeというリクエストが来ている
  • (3) ボリューム作成 : 3~15行目
    • /tmp ディレクトリに一時的にNFSをマウントして、pvc-7d13... という名前のボリュームを作成している
  • (4) PV作成 : 2,16行目
    • gRPCでPV作成のリクエストをしてレスポンスが返っている

PodにNFSボリュームをマウントしてみる

続いて、PVCを経由してPodにNFSボリュームをマウントしてみます。

以下のマニフェストを使います。

mnt-nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mnt-nfs-pod
  namespace: default
spec:
  containers:
  - name: mnt-nfs-pod
    image: busybox
    command:
    - tail
    - -f
    - /dev/null
    volumeMounts:
    - name: nfs-volume
      mountPath: /data
  volumes:
  - name: nfs-volume
    persistentVolumeClaim:
      claimName: nfs-pvc

Podを作成します。

kubectl apply -f mnt-nfs-pod.yaml

Podが作成されて、Pod内の /data ディレクトリにNFSボリュームがマウントされたことが確認できます。

$ kubectl get pod -n default
NAME          READY   STATUS    RESTARTS   AGE
mnt-nfs-pod   1/1     Running   0          41s

$ kubectl exec -n default mnt-nfs-pod -- df -h | grep /data
                         14.4G      8.8G      5.5G  62% /data

どのようにしてボリュームがPodにマウントされたのか?

こちらもCSI Driverを使ってボリュームがPodにマウントされる様子を図示してみます。Podの配置が決まったノードで起動しているkubeletと、csi-nfs-nodeのPodが今回は登場します。

Podがデプロイされるノードが決まると、そのノードで稼働しているkubeletが csi-nfs-nodenfsコンテナ経由で提供されるマウントポイントを呼び出します。それを受けて、nfsコンテナが mnt-nfs-podのPodの /data ディレクトリに対してマウントを実行します。

ログで確認: (1)マウントポイント呼出

起動したPodのNode上で kubelet のログを確認します。

journalctl -xeu kubelet

以下ログ(各行の先頭部分は非表示)の1,4行目に、NFSボリュームのマウントに関連しそうなメッセージが出ています。

I1109 04:44:35.529999     765 reconciler_common.go:245] "operationExecutor.VerifyControllerAttachedVolume started for volume \"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a\" (UniqueName: \"kubernetes.io/csi/nfs.csi.k8s.io^10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##\") pod \"mnt-nfs-pod\" (UID: \"ea906b1b-7336-406f-8bb4-d8e13ef81d04\") " pod="default/mnt-nfs-pod"
I1109 04:44:35.530088     765 reconciler_common.go:245] "operationExecutor.VerifyControllerAttachedVolume started for volume \"kube-api-access-2jjmj\" (UniqueName: \"kubernetes.io/projected/ea906b1b-7336-406f-8bb4-d8e13ef81d04-kube-api-access-2jjmj\") pod \"mnt-nfs-pod\" (UID: \"ea906b1b-7336-406f-8bb4-d8e13ef81d04\") " pod="default/mnt-nfs-pod"
I1109 04:44:35.639210     765 csi_attacher.go:380] kubernetes.io/csi: attacher.MountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping MountDevice...
I1109 04:44:35.639278     765 operation_generator.go:580] "MountVolume.MountDevice succeeded for volume \"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a\" (UniqueName: \"kubernetes.io/csi/nfs.csi.k8s.io^10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##\") pod \"mnt-nfs-pod\" (UID: \"ea906b1b-7336-406f-8bb4-d8e13ef81d04\") device mount path \"/var/lib/kubelet/plugins/kubernetes.io/csi/nfs.csi.k8s.io/5263efb4ed712e05427adcaa418778b03e13c046edc3ea4a9ab867ea11f8cd8d/globalmount\"" pod="default/mnt-nfs-pod"
I1109 04:44:38.587628     765 pod_startup_latency_tracker.go:104] "Observed pod startup duration" pod="default/mnt-nfs-pod" podStartSLOduration=2.312397567 podStartE2EDuration="3.587609387s" podCreationTimestamp="2024-11-09 04:44:35 +0000 UTC" firstStartedPulling="2024-11-09 04:44:36.81953173 +0000 UTC m=+479.945761594" lastFinishedPulling="2024-11-09 04:44:38.09474355 +0000 UTC m=+481.220973414" observedRunningTime="2024-11-09 04:44:38.586733896 +0000 UTC m=+481.712963780" watchObservedRunningTime="2024-11-09 04:44:38.587609387 +0000 UTC m=+481.713839271"

ログで確認: (2)マウント実行

csi-nfs-nodeのログを見てみます。

kubectl logs -n kube-system ds/csi-nfs-node -c nfs

NodePublishVolume というgRPCのリクエストをkubeletから受けて、-t nfsのオプションでマウントを実行しているのがわかります。

I1109 04:44:35.655828       1 utils.go:110] GRPC call: /csi.v1.Node/NodePublishVolume
I1109 04:44:35.656029       1 utils.go:111] GRPC request: {"target_path":"/var/lib/kubelet/pods/ea906b1b-7336-406f-8bb4-d8e13ef81d04/volumes/kubernetes.io~csi/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a/mount","volume_capability":{"AccessType":{"Mount":{"mount_flags":["nfsvers=4.1"]}},"access_mode":{"mode":5}},"volume_context":{"csi.storage.k8s.io/pv/name":"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a","csi.storage.k8s.io/pvc/name":"nfs-pvc","csi.storage.k8s.io/pvc/namespace":"default","server":"10.0.0.5","share":"/srv/nfs_share","storage.kubernetes.io/csiProvisionerIdentity":"1731104072010-4747-nfs.csi.k8s.io","subdir":"pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a"},"volume_id":"10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##"}
I1109 04:44:35.658545       1 nodeserver.go:132] NodePublishVolume: volumeID(10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##) source(10.0.0.5:/srv/nfs_share/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a) targetPath(/var/lib/kubelet/pods/ea906b1b-7336-406f-8bb4-d8e13ef81d04/volumes/kubernetes.io~csi/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a/mount) mountflags([nfsvers=4.1])
I1109 04:44:35.659827       1 mount_linux.go:243] Detected OS without systemd
I1109 04:44:35.660546       1 mount_linux.go:218] Mounting cmd (mount) with arguments (-t nfs -o nfsvers=4.1 10.0.0.5:/srv/nfs_share/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a /var/lib/kubelet/pods/ea906b1b-7336-406f-8bb4-d8e13ef81d04/volumes/kubernetes.io~csi/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a/mount)
I1109 04:44:36.190344       1 nodeserver.go:149] skip chmod on targetPath(/var/lib/kubelet/pods/ea906b1b-7336-406f-8bb4-d8e13ef81d04/volumes/kubernetes.io~csi/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a/mount) since mountPermissions is set as 0
I1109 04:44:36.190476       1 nodeserver.go:151] volume(10.0.0.5#srv/nfs_share#pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a##) mount 10.0.0.5:/srv/nfs_share/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a on /var/lib/kubelet/pods/ea906b1b-7336-406f-8bb4-d8e13ef81d04/volumes/kubernetes.io~csi/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a/mount succeeded
I1109 04:44:36.190503       1 utils.go:117] GRPC response: {}

ちなみにマウント先のパスが、

/var/lib/kubelet/pods/ea906b1b-7336-406f-8bb4-d8e13ef81d04/volumes/kubernetes.io~csi/pvc-7d13ebab-8f44-4d9e-bc62-9d77eae1ec2a/mount/

という非常に長い名前になっていますが、パスの途中に出てくる ea906b1b...mnt-nfs-pod のPodのUIDと一致します。

$ kubectl get po -n default mnt-nfs-pod -o jsonpath='{.metadata.uid}'
ea906b1b-7336-406f-8bb4-d8e13ef81d04

したがって、CSI経由で動的プロビジョニングによりPodにマウントされたパスは、ノード上だと

/var/lib/kubelet/pods/[pod-uid]/volumes/kubernetes.io~csi/[pv-name]/mount/

に格納されるようです。

おわりに

本記事では、CSIドライバー経由でPVCを作成してPodにマウントした場合の動きを実際にKubernetesリソースを作成して追ってみました。ただ今回の内容で、CSIドライバーで必須の全ての動作を説明しているわけではない(例えば csi-nfs-nodenode-driver-registrar コンテナの説明はスキップしている)ことにご注意ください。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?