🚀

Kubernetesでのcsi-driver-nfsを用いたNFS利用 - おうちでGitOpsシリーズ投稿6/6

2023/07/05に公開

こちらはおうちラボでGitOpsシリーズの6つ目のポストです。

以前投稿したシリーズで取り上げたDockerの場合は、比較的サラッとデータ保持についてカバーできました。Kubernetesクラスタの場合は少し具合が違うので紹介させてください。

なお大事なことなので冒頭と締めで二回書きますが、ConfigMapsSecretsというものでpodに利用させるちょっとした設定ファイルやTLS証明書・キーなどは渡せます。今回カバーするのはそれ以外のデータ保持です。例えばJupyterNotebookのpodを作った時に、サービスのクラッシュなりノードのリスタートなり何らかの理由でpodが作り直された際に、何も特別な設定をしていない場合は以前のpod上で作ったデータは保持されません。

同一ノード上でpodが作り直された場合でもデータ保持されませんが、絵にすると若干わかりやすさが上がると思うので「別ノードでpodが作り直されたら」というシナリオで図とともに少し説明します。

仮にrpi4bpノード上であるサービスのpodが走り、そのサービス用のデータがMY_DATAとしてrpi4bpノードに作られるとします。ノードがダウンして別のrpi4ノードに改めてそのサービス用のpodが走り始めたとして、同じMY_DATAがrpi4ノードにはありません。

データをKubernetesクラスタとは別のところに置いておくというのが今回のポストでカバーする内容で、以下のような絵になります。

本シリーズのお約束

  • Kubernetes Cluster on 3 nodes (2 raspberry pi4 - arm64, and 1 amd64 fanless mini PC)
    • flannel for networking
    • flux as a GitOps tool
    • metallb for LoadBalancer service type implementation
  • 1 or more machine running Docker
    • serving GitLab (Used for GitOps central repository)
    • serving Nginx (Reverse proxy to provide access to GitLab and other web services to be created on Kubernetes Cluster)
    • serving Unbound (DNS resolver for machines on LAN)
  • public DNS domain + SSL/TLS certification (for example, Let's Encrypt) recommended
    • they are all mydomain.net in the series
    • replace mydomain.net with your own DNS domain to follow through

More on Docker - Series Top: Dockerで作るおうちLAN遊び場 シリーズ1/7

Interested in getting your own DNS domain?

流れ

  • NFSサーバのセットアップ
  • Helm sourceとreleaseでNFSを扱えるCSIドライバnfs.csi.k8s.ioをクラスタにインストール
  • このドライバを使うStorageClassを作成
  • 作成したStorageClassを指定してPVC

NFSサーバのセットアップ

私はKubernetesクラスタ参加ノードのうちの1台、rpi4に外付けディスクをマウントし、そのディスクをNFSサーバとして利用するようにしました。手順を以下のリストで簡単に紹介します。

  • USB外付けディスクを接続
  • sudo blkidで対象のUUIDを確認
  • sudo mkdir -p /media/usbdriveなどで好きな場所にディスクを載せるディレクトリを作成
  • sudo mkdir /exports/nfsなどで好きな場所にNFSサーバ用のディレクトリを作成
  • /etc/fstabファイルに以下のような行を追記
    • UUID={UUID for the disk} /media/usbdrive ext4 defaults 0 0
  • 再起動などしてディスクがマウントされること確認
  • sudo mkdir -p /media/usbdrive/k8spv/nfsなどディスク上にNFSサーバに使用させるディレクトリを作成
  • sudo chmod -R a+rwx /media/usbdrive/k8spv/nfsで権限設定
  • /etc/fstabに更に追記
    • /media/usbdrive/k8spv/nfs /exports/nfs none bind 0 0
  • NFSサーバをインストール
    • sudo apt install nfs-kernel-server
  • /etc/exportsにNFSサーバでサーブするディレクトリについて追記し、sudo exportfs -raで反映
    • /exports/nfs *(rw,nohide,no_subtree_check)

NFS CSI

CSIはContainer Storage Interfaceです。NFSサーバで提供されるリモートのディスクスペースをコンテナのストレージとして利用するためのドライバがこちらのURLにあり、このセットアップをしていきます。

https://github.com/kubernetes-csi/csi-driver-nfs

csi-driver-nfsのインストール

インストールにはもちろんGitOps用に用意した専用のレポジトリ、flux-configでやっていきます。またhelmでファイルを用意します。Weave GitOps Dashboardを用意した時はgitops createコマンドでマニフェストを用意したわけですが、その時作られたのはflux用のHelmRepositoryという作成レシピレポジトリを指定するものと、HelmReleaseというdeployするためのものです。今回も同じ様なものを作ります。

https://github.com/kubernetes-csi/csi-driver-nfs/tree/master/charts

https://fluxcd.io/flux/cmd/flux_create_source_helm/

https://fluxcd.io/flux/cmd/flux_create_helmrelease/

以上3URLを参考に、以下のようになりました。flux-configレポジトリ上、/cluster/my-cluster/csi-driver-nfs/csi-driver-nfs.yamlを作成し、commit/pushしあとはfluxに処理してもらいKubernetesクラスタ上にcsi-driver-nfsをインストールしてもらう、という流れになります。

# flux-config repository
cd cluster/my-cluster
mkdir csi-driver-nfs
cd csi-driver-nfs

# create flux helm source
flux create source helm csi-driver-nfs \
  --url https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts \
  --namespace kube-system \
  --export > csi-driver-nfs.yaml

# generate and add flux helm release to the same file
flux create helmrelease csi-driver-nfs \
--chart csi-driver-nfs \
--source HelmRepository/csi-driver-nfs \
--chart-version v4.2.0 \
--namespace kube-system \
--export >> csi-driver-nfs.yaml

flux get sources helm -n kube-systemflux get helmreleases -n kube-systemkubectl get csidriversなどで以下のように表示されると思います。

またkubectl get pods -n kube-system, kubectl get deploy -n kube-system, kubectl get rs -n kube-system
などでも実際にcsi-driver-nfsのインストールによって作られたものが簡易確認できますし、kubectl describe deploy/csi-nfs-controller -n kube-systemなどで詳細が確認できます。

❯ flux get sources helm -n kube-system
NAME          	REVISION       	SUSPENDED	READY	MESSAGE
csi-driver-nfs	sha256:03208d1f	False    	True 	stored artifact: revision 'sha256:03208d1f'
❯ flux get helmreleases -n kube-system
NAME          	REVISION	SUSPENDED	READY	MESSAGE
csi-driver-nfs	v4.2.0  	False    	True 	Release reconciliation succeeded

❯ kubectl get csidrivers
NAME             ATTACHREQUIRED   PODINFOONMOUNT   STORAGECAPACITY   TOKENREQUESTS   REQUIRESREPUBLISH   MODES        AGE
nfs.csi.k8s.io   false            false            false             <unset>         false               Persistent   43d
❯ kubectl describe csidrivers/nfs.csi.k8s.io
Name:         nfs.csi.k8s.io
Namespace:
Labels:       app.kubernetes.io/managed-by=Helm
              helm.toolkit.fluxcd.io/name=csi-driver-nfs
              helm.toolkit.fluxcd.io/namespace=kube-system
Annotations:  meta.helm.sh/release-name: csi-driver-nfs
              meta.helm.sh/release-namespace: kube-system
API Version:  storage.k8s.io/v1
Kind:         CSIDriver
Metadata:
  Creation Timestamp:  2023-05-18T07:54:26Z
  Resource Version:    1664666
  UID:                 d74bb25e-2a76-4473-b18f-ceb64bc28caf
Spec:
  Attach Required:     false
  Fs Group Policy:     File
  Pod Info On Mount:   false
  Requires Republish:  false
  Se Linux Mount:      false
  Storage Capacity:    false
  Volume Lifecycle Modes:
    Persistent
Events:  <none>

Storage Classの用意

使い方としては二通りドキュメントで紹介されています。

https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/example/README.md

一つの方法はStatic Provisioningとドキュメントで説明されていますが、マニュアルでディスクサイズや取ってくる元を指定したPVを作成し、PVCでそのPVからスペースを取っていく形です。

対してDynamicだとマニュアルでPVを用意せずとも、Storage Classを利用してPVCでディスクスペースを取っていけます。

今回はDynamic Provisioningで利用していきます。

/cluster/my-cluster/csi-driver-nfs/storageclass-nfs.yamlとして以下の内容のファイルを用意します。parameters > serverのところでNFSサーバをIPアドレスなりDNSなりで指定します。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs.mydomain.net
  share: /exports/nfs
  mountPermissions: "777"
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
  - hard
  - nfsvers=4.2

このファイルももちろんレポジトリにcommit/pushし、fluxが処理するに任せます。出来上がると以下のように確認ができると思います。

❯ kubectl get storageclass
NAME      PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-csi   nfs.csi.k8s.io   Delete          Immediate           false                  43d
❯ kubectl describe storageclass/nfs-csi
Name:                  nfs-csi
IsDefaultClass:        No
Annotations:           <none>
Provisioner:           nfs.csi.k8s.io
Parameters:            mountPermissions=777,server=nfs.mydomain.net,share=/exports/nfs
AllowVolumeExpansion:  <unset>
MountOptions:
  hard
  nfsvers=4.2
ReclaimPolicy:      Delete
VolumeBindingMode:  Immediate
Events:             <none>

試してみよう!!

NFSサーバ、CSIドライバインストール、Storage Classの用意ができました!あとはpodなりdeploymentなりでPVC要求、そのボリュームをマウントする行を加えると、podの作り直しなどを経ても保持されるディスク容量が利用できるようになります。

具体的な例ですが、シリーズ第三弾で作ったweb-app-manifestsの方で試してみましょう。

最初に、pod上でファイルを作ってpodを削除すると、復旧された別podでは先に作成したファイルが存在していないというところを確認します。

kubectl get podsnginx-deployment-...といったpodがまだ実行されていると思います。試しに/root/helloというファイルなど作ってみます。

❯ kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
jlab-7b99c869df-zdntp              1/1     Running   0          33d   10.244.2.14   node1   <none>           <none>
nginx-deployment-cbdccf466-z4bvm   1/1     Running   0          34d   10.244.3.12   node2     <none>           <none>

$ kubectl exec --stdin --tty nginx-deployment-cbdccf466-z4bvm -- /bin/bash

root@nginx-deployment-cbdccf466-z4bvm:/# touch /root/hello
root@nginx-deployment-cbdccf466-z4bvm:/# ls -l /root
total 0
-rw-r--r-- 1 root root 0 Jul  5 03:47 hello
root@nginx-deployment-cbdccf466-z4bvm:~# exit
exit

ではpod削除して/root/helloファイルが維持されていないことを確認しましょう。

❯ kubectl delete pod nginx-deployment-cbdccf466-z4bvm
pod "nginx-deployment-cbdccf466-z4bvm" deleted

❯ kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
jlab-7b99c869df-zdntp              1/1     Running   0          33d   10.244.2.14   node1   <none>           <none>
nginx-deployment-cbdccf466-dqt78   1/1     Running   0          79s   10.244.1.49   node3     <none>           <none>

❯ kubectl exec --stdin --tty nginx-deployment-cbdccf466-dqt78 -- /bin/bash
root@nginx-deployment-cbdccf466-dqt78:/# ls -l /root
total 0
root@nginx-deployment-cbdccf466-dqt78:/# exit
exit

既存のnginx-deployment.yamlはそのままに、一つtest.yamlを用意してテストしてみましょう。今回はserviceは省き、deploymentと、新たに設けたnfs-csiというストレージクラスを利用するpvcだけで用意してみます。Replicasは2としておきます。

以下が実際のファイルです。commit/pushし、fluxによってKubernetesクラスタ上にワークロードが作成されるのを待ちましょう。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-nginx
  labels:
    app: test-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test-nginx
  template:
    metadata:
      labels:
        app: test-nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /root
          name: test-nginx-data
      volumes:
      - name: test-nginx-data
        persistentVolumeClaim:
          claimName: test-nginx-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-nginx-pvc
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs-csi

以下が新たに作成されたtest-nginx deploymentであれこれ確認しているところのログです。

  • 作成されたpodの確認
  • 一つのpodで/root/hello.txtを作り、もう一方のpod上にも同じファイルができていることの確認
  • 更にpod2つとも手動で削除し、deploymentで復旧された新しいpodでも/root/hello.txtがあることを確認
# confirm that 2 pods are running
❯ kubectl get pods -l app=test-nginx
NAME                          READY   STATUS    RESTARTS   AGE
test-nginx-5cc4bd4464-fgjf7   1/1     Running   0          4m7s
test-nginx-5cc4bd4464-rsdjg   1/1     Running   0          4m7s

# create /root/hello.txt in one pod
❯ kubectl exec --stdin --tty test-nginx-5cc4bd4464-fgjf7 -- /bin/bash
root@test-nginx-5cc4bd4464-fgjf7:/# ls root
root@test-nginx-5cc4bd4464-fgjf7:/# echo "hello" > /root/hello.txt
root@test-nginx-5cc4bd4464-fgjf7:/# ls -l /root
total 4
-rw-r--r-- 1 nobody nogroup 6 Jul  5 06:05 hello.txt
root@test-nginx-5cc4bd4464-fgjf7:/# exit
exit

# confirm the same /root/hello.txt file exists on the other pod
❯ kubectl exec --stdin --tty test-nginx-5cc4bd4464-rsdjg -- /bin/bash
root@test-nginx-5cc4bd4464-rsdjg:/# ls -l /root
total 4
-rw-r--r-- 1 nobody nogroup 6 Jul  5 06:05 hello.txt
root@test-nginx-5cc4bd4464-rsdjg:/# cat /root/hello.txt
hello
root@test-nginx-5cc4bd4464-rsdjg:/# exit
exit

# delete both pods
❯ kubectl delete pod/test-nginx-5cc4bd4464-fgjf7
pod "test-nginx-5cc4bd4464-fgjf7" deleted
❯ kubectl delete pod/test-nginx-5cc4bd4464-rsdjg
pod "test-nginx-5cc4bd4464-rsdjg" deleted

# confirm the new pods are being created
❯ kubectl get pods -l app=test-nginx
NAME                          READY   STATUS              RESTARTS   AGE
test-nginx-5cc4bd4464-8x6jj   1/1     Running             0          5s
test-nginx-5cc4bd4464-jbxf5   0/1     ContainerCreating   0          11s

# /root/hello.txt is there on these new pods as well
❯ kubectl exec test-nginx-5cc4bd4464-8x6jj -- cat /root/hello.txt
hello
❯ kubectl exec test-nginx-5cc4bd4464-jbxf5 -- cat /root/hello.txt
hello

なおNFSサーバ側ですが、/exports/nfs以下にディレクトリができていると思います。複数ある場合は、どれかにhello.txtができていると思うので探してみてください。

pwd
/exports/nfs
❯ ll pvc-c3f8a170-7787-44c8-b3b0-a016d4ac96b2
total 4
-rw-r--r-- 1 nobody nogroup 6 Jul  5 06:05 hello.txt
❯ cat pvc-c3f8a170-7787-44c8-b3b0-a016d4ac96b2/hello.txt
hello

完成!!

NFSサーバに用意したディスクスペースを利用できることも確認できました。

そして最後に。冒頭の繰り返しとなりますが、同じ「異なるpod間で保持されなくてはならないデータ」でもpodに設定ファイルなどを渡すにはConfigMapsSecretsがあるのでそれらを利用しましょう。

https://kubernetes.io/docs/concepts/configuration/configmap/

https://kubernetes.io/docs/concepts/configuration/secret/

シリーズの締めに

読んで頂きどうもありがとうございます!

今回のシリーズは6投稿で一旦おしまいです。おうちの環境でGitOpsを試せるようになるまでの構築や、Kubernetesクラスタ上に立ち上げたサービスへの、クラスタ外からのアクセスの用意、NFSのCSIを用いたPV利用と、Kubernetes本家のドキュメントではあまりカバーしていないところをある程度紹介できたかと思います。

次は懲りなくおうちラボで、なんらかの構築シリーズなりシリーズ番外なりで、LAN上にコンテナレジストリを設けたり、マルチプラットフォームなカスタムイメージをビルドしてローカルのレジストリに登録したり、それをDockerなりKubernetesなりで利用するといったあたりを紹介しようかと考えています。

Discussion