Kubernetesでのcsi-driver-nfsを用いたNFS利用 - おうちでGitOpsシリーズ投稿6/6
こちらはおうちラボでGitOpsシリーズの6つ目のポストです。
以前投稿したシリーズで取り上げたDockerの場合は、比較的サラッとデータ保持についてカバーできました。Kubernetesクラスタの場合は少し具合が違うので紹介させてください。
なお大事なことなので冒頭と締めで二回書きますが、ConfigMaps
やSecrets
というもので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)
- serving
- 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
- they are all
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にあり、このセットアップをしていきます。
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
以上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-system
やflux get helmreleases -n kube-system
、kubectl 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 pods
でnginx-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に設定ファイルなどを渡すにはConfigMaps
やSecrets
があるのでそれらを利用しましょう。
https://kubernetes.io/docs/concepts/configuration/configmap/
シリーズの締めに
読んで頂きどうもありがとうございます!
今回のシリーズは6投稿で一旦おしまいです。おうちの環境でGitOpsを試せるようになるまでの構築や、Kubernetesクラスタ上に立ち上げたサービスへの、クラスタ外からのアクセスの用意、NFSのCSIを用いたPV利用と、Kubernetes本家のドキュメントではあまりカバーしていないところをある程度紹介できたかと思います。
次は懲りなくおうちラボで、なんらかの構築シリーズなりシリーズ番外なりで、LAN上にコンテナレジストリを設けたり、マルチプラットフォームなカスタムイメージをビルドしてローカルのレジストリに登録したり、それをDockerなりKubernetesなりで利用するといったあたりを紹介しようかと考えています。
Discussion