こんにちは、ナナオです。

k8sを運用していて、永続ストレージが欲しくなるケース、ありますよね。

今回はそんな時に役立つ動的プロビジョニングを実施してみたいと思います。

動的プロビジョニングとは

k8sで永続ストレージを使用するには、PVとPVCを作成する必要があります。

PVはストレージの接続方法やディレクトリパスを指定するリソースで、PVCはストレージとデプロイメントやポッドを繋ぐためのインターフェースのような役割を果たすリソースです。

ただ、ストレージが必要になるたびに毎回PVを作ってPVCを作って…とするのは面倒です。

その手間を減らしてくれるのが動的プロビジョニングになります。

動的プロビジョニングでは、一度ストレージの接続方法とディレクトリパスを指定してあげれば、あとは勝手にそのディレクトリ内でPVを作ってPVCと紐づけてくれます。

実践

早速実践してみましょう。

動的プロビジョニングを行うために以下のツールをhelmを使用してインストールします。

GitHub - kubernetes-sigs/nfs-subdir-external-provisioner: Dynamic sub-dir volume provisioner on a remote NFS server.

helmは以下のコマンドでダウンロード可能です。

brew install helm

また、helmによるインストールの管理にはhelmfileというツールを使用します。

GitHub - helmfile/helmfile: Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases. Generate all-in-one manifests for use with ArgoCD.

こちらもダウンロードはbrewコマンドで一発で行えます。

brew install helmfile

準備が整いました。

以下のようにプロビジョナーを設定します。

nfs:
  server: 192.168.0.94
  path: /k8s-data
storageClass:
  name: nfs-client
  defaultClass: true # デフォルトのストレージクラスにする
  reclaimPolicy: Retain # PVC削除時にNASのデータを残すか(Retainなら残る)

先ほど実装したプロビジョナーの設定を参照するhelmfileを実装します。

repositories:
  - name: nfs-subdir-external-provisioner
    url: https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

releases:
  - name: nfs-provisioner
    namespace: kube-system
    chart: nfs-subdir-external-provisioner/nfs-subdir-external-provisioner
    version: 4.0.18
    values:
      - ./values/nfs-provisioner.yaml

実装したhelmfileを適用します。

helmfile apply -f helm/helmfile.yaml

実行したところエラーになりました。

❯ helmfile apply -f helm/helmfile.yaml
Adding repo nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
"nfs-subdir-external-provisioner" has been added to your repositories

Comparing release=nfs-provisioner, chart=nfs-subdir-external-provisioner/nfs-subdir-external-provisioner, namespace=kube-system
in helm/helmfile.yaml: command "/home/linuxbrew/.linuxbrew/bin/helm" exited with non-zero status:

PATH:
  /home/linuxbrew/.linuxbrew/bin/helm

ARGS:
  0: helm (4 bytes)
  1: diff (4 bytes)
  2: upgrade (7 bytes)
  3: --allow-unreleased (18 bytes)
  4: nfs-provisioner (15 bytes)
  5: nfs-subdir-external-provisioner/nfs-subdir-external-provisioner (63 bytes)
  6: --version (9 bytes)
  7: 4.0.18 (6 bytes)
  8: --kube-version (14 bytes)
  9: 1.34.3+k3s1 (11 bytes)
  10: --namespace (11 bytes)
  11: kube-system (11 bytes)
  12: --values (8 bytes)
  13: /tmp/helmfile247608998/kube-system-nfs-provisioner-values-66cd9877fc (68 bytes)
  14: --reset-values (14 bytes)
  15: --detailed-exitcode (19 bytes)

ERROR:
  exit status 1

EXIT STATUS
  1

STDERR:
  Error: unknown command "diff" for "helm"
  Run 'helm --help' for usage.

COMBINED OUTPUT:
  Error: unknown command "diff" for "helm"
  Run 'helm --help' for usage.

helm diffコマンドが存在しないといわれています。

どうやら以下のプラグインを導入する必要があるようです。

GitHub - databus23/helm-diff: A helm plugin that shows a diff explaining what a helm upgrade would change

以下のコマンドでプラグインをインストールします。

helm plugin install https://github.com/databus23/helm-diff --verify=false

再度実行してみます。

❯ helmfile apply -f helm/helmfile.yaml
Adding repo nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
"nfs-subdir-external-provisioner" already exists with the same configuration, skipping

Comparing release=nfs-provisioner, chart=nfs-subdir-external-provisioner/nfs-subdir-external-provisioner, namespace=kube-system
kube-system, leader-locking-nfs-provisioner-nfs-subdir-external-provisioner, Role (rbac.authorization.k8s.io) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/role.yaml
+ kind: Role
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+   name: leader-locking-nfs-provisioner-nfs-subdir-external-provisioner
+ rules:
+   - apiGroups: [""]
+     resources: ["endpoints"]
+     verbs: ["get", "list", "watch", "create", "update", "patch"]
kube-system, leader-locking-nfs-provisioner-nfs-subdir-external-provisioner, RoleBinding (rbac.authorization.k8s.io) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/rolebinding.yaml
+ kind: RoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+   name: leader-locking-nfs-provisioner-nfs-subdir-external-provisioner
+ subjects:
+   - kind: ServiceAccount
+     name: nfs-provisioner-nfs-subdir-external-provisioner
+     namespace: kube-system
+ roleRef:
+   kind: Role
+   name: leader-locking-nfs-provisioner-nfs-subdir-external-provisioner
+   apiGroup: rbac.authorization.k8s.io
kube-system, nfs-client, StorageClass (storage.k8s.io) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/storageclass.yaml
+ apiVersion: storage.k8s.io/v1
+ kind: StorageClass
+ metadata:
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+   name: nfs-client
+   annotations:
+     storageclass.kubernetes.io/is-default-class: "true"
+ provisioner: cluster.local/nfs-provisioner-nfs-subdir-external-provisioner
+ allowVolumeExpansion: true
+ reclaimPolicy: Retain
+ volumeBindingMode: Immediate
+ parameters:
+   archiveOnDelete: "true"
kube-system, nfs-provisioner-nfs-subdir-external-provisioner, Deployment (apps) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/deployment.yaml
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+   name: nfs-provisioner-nfs-subdir-external-provisioner
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+ spec:
+   replicas: 1
+   strategy:
+     type: Recreate
+   selector:
+     matchLabels:
+       app: nfs-subdir-external-provisioner
+       release: nfs-provisioner
+   template:
+     metadata:
+       annotations:
+       labels:
+         app: nfs-subdir-external-provisioner
+         release: nfs-provisioner
+     spec:
+       serviceAccountName: nfs-provisioner-nfs-subdir-external-provisioner
+       securityContext:
+         {}
+       containers:
+         - name: nfs-subdir-external-provisioner
+           image: "registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2"
+           imagePullPolicy: IfNotPresent
+           securityContext:
+             {}
+           volumeMounts:
+             - name: nfs-subdir-external-provisioner-root
+               mountPath: /persistentvolumes
+           env:
+             - name: PROVISIONER_NAME
+               value: cluster.local/nfs-provisioner-nfs-subdir-external-provisioner
+             - name: NFS_SERVER
+               value: 192.168.0.94
+             - name: NFS_PATH
+               value: /k8s-data
+       volumes:
+         - name: nfs-subdir-external-provisioner-root
+           nfs:
+             server: 192.168.0.94
+             path: /k8s-data
kube-system, nfs-provisioner-nfs-subdir-external-provisioner, ServiceAccount (v1) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/serviceaccount.yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+   name: nfs-provisioner-nfs-subdir-external-provisioner
kube-system, nfs-provisioner-nfs-subdir-external-provisioner-runner, ClusterRole (rbac.authorization.k8s.io) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/clusterrole.yaml
+ kind: ClusterRole
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+   name: nfs-provisioner-nfs-subdir-external-provisioner-runner
+ rules:
+   - apiGroups: [""]
+     resources: ["nodes"]
+     verbs: ["get", "list", "watch"]
+   - apiGroups: [""]
+     resources: ["persistentvolumes"]
+     verbs: ["get", "list", "watch", "create", "delete"]
+   - apiGroups: [""]
+     resources: ["persistentvolumeclaims"]
+     verbs: ["get", "list", "watch", "update"]
+   - apiGroups: ["storage.k8s.io"]
+     resources: ["storageclasses"]
+     verbs: ["get", "list", "watch"]
+   - apiGroups: [""]
+     resources: ["events"]
+     verbs: ["create", "update", "patch"]
kube-system, run-nfs-provisioner-nfs-subdir-external-provisioner, ClusterRoleBinding (rbac.authorization.k8s.io) has been added:
-
+ # Source: nfs-subdir-external-provisioner/templates/clusterrolebinding.yaml
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+   labels:
+     chart: nfs-subdir-external-provisioner-4.0.18
+     heritage: Helm
+     app: nfs-subdir-external-provisioner
+     release: nfs-provisioner
+   name: run-nfs-provisioner-nfs-subdir-external-provisioner
+ subjects:
+   - kind: ServiceAccount
+     name: nfs-provisioner-nfs-subdir-external-provisioner
+     namespace: kube-system
+ roleRef:
+   kind: ClusterRole
+   name: nfs-provisioner-nfs-subdir-external-provisioner-runner
+   apiGroup: rbac.authorization.k8s.io

Upgrading release=nfs-provisioner, chart=nfs-subdir-external-provisioner/nfs-subdir-external-provisioner, namespace=kube-system
Release "nfs-provisioner" does not exist. Installing it now.
NAME: nfs-provisioner
LAST DEPLOYED: Tue Jan 13 00:54:01 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None

Listing releases matching ^nfs-provisioner$
nfs-provisioner kube-system     1               2026-01-13 00:54:01.223708054 +0900 JST deployed        nfs-subdir-external-provisioner-4.0.18      4.0.2


UPDATED RELEASES:
NAME              NAMESPACE     CHART                                                             VERSION   DURATION
nfs-provisioner   kube-system   nfs-subdir-external-provisioner/nfs-subdir-external-provisioner   4.0.18          2s

今度は成功しました!

感想

とても簡単にプロビジョナーを導入できました。

今後はPVCを作るだけになるので、作業がますます捗ります。

参考

Artboard

ボリュームの動的プロビジョニング(Dynamic Volume Provisioning) | Kubernetes

自動プロビジョニングできるKubernetesのNFSストレージクラスの作成 #kubernetes - Qiita