Skip to main content

CentOS Stream 10: Dynamic Provisioning (NFS)

Configure dynamic volume provisioning using NFS as the storage backend in Kubernetes on CentOS Stream 10.

May 24, 2026 8 min read
centoscentos-stream-10kubernetesk8scluster

To use Dynamic Volume Provisioning feature when using Persistent Storage,

it's possible to create PV (Persistent Volume) dynamically without creating PV manually by Cluster Administrator

when created PVC (Persistent Volume Claim) by users.

This example is based on the environment like follows.

+----------------------+   +----------------------+

|  [ ctrl.srv.world ]  |   |   [ dlp.srv.world ]  |

|     Manager Node     |   |     Control Plane    |

+-----------+----------+   +-----------+----------+

        eth0|10.0.0.25             eth0|10.0.0.30

            |                          |

------------+--------------------------+-----------

            |                          |

        eth0|10.0.0.51             eth0|10.0.0.52

+-----------+----------+   +-----------+----------+

| [ node01.srv.world ] |   | [ node02.srv.world ] |

|     Worker Node#1    |   |     Worker Node#2    |

+----------------------+   +----------------------+

For example, configure dynamic volume provisioning with NFS provisioner that NFS storage is provided from [nfs.srv.world (10.0.0.35)].

Step 1

Run NFS Server.

On this example, configure [/home/nfsshare] directory as NFS share.

Step 2

Worker Nodes need to be able to mount NFS share on NFS server.

Step 3

Install NFS Client Provisioner with Helm.

[cent@ctrl ~]$ helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

nfs.server = (NFS server's hostname or IP address)

nfs.path = (NFS share Path)

[cent@ctrl ~]$ helm install nfs-client -n kube-system --set nfs.server=10.0.0.35 --set nfs.path=/home/nfsshare nfs-subdir-external-provisioner/nfs-subdir-external-provisioner

NAME: nfs-client

LAST DEPLOYED: Tue May 20 13:28:56 2025

NAMESPACE: kube-system

STATUS: deployed

REVISION: 1

TEST SUITE: None

[cent@ctrl ~]$ kubectl get deployment -n kube-system

NAME                                         READY   UP-TO-DATE   AVAILABLE   AGE

calico-kube-controllers                      1/1     1            1           3h49m

coredns                                      2/2     2            2           3h50m

metrics-server                               1/1     1            1           9m31s

nfs-client-nfs-subdir-external-provisioner   1/1     1            1           19s

Step 4

This is an example to use dynamic volume provisioning by a Pod.

[cent@ctrl ~]$ kubectl get pv

No resources found in default namespace.

[cent@ctrl ~]$ kubectl get pvc

No resources found in default namespace.

[cent@ctrl ~]$ kubectl get storageclass

NAME         PROVISIONER                                                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE

nfs-client   cluster.local/nfs-client-nfs-subdir-external-provisioner   Delete          Immediate           true                   57s

create PVC

[cent@ctrl ~]$ vi my-pvc.yml

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

  name: my-provisioner

spec:

  accessModes:

    - ReadWriteOnce

  <span class="color2"># specify StorageClass name</span>

  storageClassName: nfs-client

  resources:

    requests:

      <span class="color2"># volume size</span>

      storage: 5Gi

[cent@ctrl ~]$ kubectl apply -f my-pvc.yml

persistentvolumeclaim/my-provisioner created

[cent@ctrl ~]$ kubectl get pvc

NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE

my-provisioner   Bound    pvc-b21cbb22-e9e8-4b58-abfc-320b0584db71   5Gi        RWO            nfs-client     <unset>                 4s

PV is generated dynamically

[cent@ctrl ~]$ kubectl get pv

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE

pvc-b21cbb22-e9e8-4b58-abfc-320b0584db71   5Gi        RWO            Delete           Bound    default/my-provisioner   nfs-client     <unset>                          33s

[cent@ctrl ~]$ vi my-pod.yml

apiVersion: apps/v1

kind: Deployment

metadata:

  name: my-nginx

spec:

  selector:

    matchLabels:

      run: my-nginx

  replicas: 1

  template:

    metadata:

      labels:

        run: my-nginx

    spec:

      containers:

      - name: my-nginx

        image: nginx

        ports:

        - containerPort: 80

        volumeMounts:

        - mountPath: /usr/share/nginx/html

          name: nginx-pvc

      volumes:

        - name: nginx-pvc

          persistentVolumeClaim:

            <span class="color2"># PVC name you created</span>

            claimName: my-provisioner

[cent@ctrl ~]$ kubectl apply -f my-pod.yml

deployment.apps/my-nginx created

[cent@ctrl ~]$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE

my-nginx-6b6dcc5896-szlgv   1/1     Running   0          8s

[cent@ctrl ~]$ kubectl exec my-nginx-6b6dcc5896-szlgv -- df /usr/share/nginx/html

Filesystem                                                                               1K-blocks  Used Available Use% Mounted on

10.0.0.35:/home/nfsshare/default-my-provisioner-pvc-b21cbb22-e9e8-4b58-abfc-320b0584db71 164028416     0 155623424   0% /usr/share/nginx/html

verify accessing to create test index file

[cent@ctrl ~]$ echo "Nginx Index" > index.html

[cent@ctrl ~]$ kubectl cp index.html my-nginx-6b6dcc5896-szlgv:/usr/share/nginx/html/index.html

[cent@ctrl ~]$ kubectl expose deployment my-nginx --type="NodePort" --port 80

[cent@ctrl ~]$ kubectl port-forward service/my-nginx --address 127.0.0.1 8082:80 &

[cent@ctrl ~]$ curl localhost:8082

Handling connection for 8082

Nginx Index

when removing, to remove PVC, then PV is also removed dynamically

[cent@ctrl ~]$ kubectl delete deployment my-nginx

deployment.apps "my-nginx" deleted

[cent@ctrl ~]$ kubectl delete pvc my-provisioner

persistentvolumeclaim "my-provisioner" deleted

[cent@ctrl ~]$ kubectl get pv

No resources found

Step 5

To use StatefulSet, it's possible to specify [volumeClaimTemplates].

[cent@ctrl ~]$ kubectl get pv

No resources found in default namespace.

[cent@ctrl ~]$ kubectl get pvc

No resources found in default namespace.

[cent@ctrl ~]$ kubectl get storageclass

NAME         PROVISIONER                                                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE

nfs-client   cluster.local/nfs-client-nfs-subdir-external-provisioner   Delete          Immediate           true                   5m15s

[cent@ctrl ~]$ vi statefulset.yml

apiVersion: apps/v1

kind: StatefulSet

metadata:

  name: my-mginx

spec:

  serviceName: my-mginx

  replicas: 1

  selector:

    matchLabels:

      app: my-mginx

  template:

    metadata:

      labels:

        app: my-mginx

    spec:

      containers:

      - name: my-mginx

        image: nginx

        volumeMounts:

        - name: data

          mountPath: /usr/share/nginx/html

  volumeClaimTemplates:

  - metadata:

      name: data

    spec:

      <span class="color2"># specify StorageClass name</span>

      storageClassName: nfs-client

      accessModes: [ "ReadWriteOnce" ]

      resources:

        requests:

          storage: 5Gi

[cent@ctrl ~]$ kubectl apply -f statefulset.yml

statefulset.apps/my-mginx created

[cent@ctrl ~]$ kubectl get statefulset

NAME       READY   AGE

my-mginx   1/1     8s

[cent@ctrl ~]$ kubectl get pods

NAME         READY   STATUS    RESTARTS   AGE

my-mginx-0   1/1     Running   0          21s

[cent@ctrl ~]$ kubectl get pvc

NAME              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE

data-my-mginx-0   Bound    pvc-d68145c1-8a77-4ccb-88f4-1741cb2317f9   5Gi        RWO            nfs-client     <unset>                 34s

[cent@ctrl ~]$ kubectl get pv

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE

pvc-d68145c1-8a77-4ccb-88f4-1741cb2317f9   5Gi        RWO            Delete           Bound    default/data-my-mginx-0   nfs-client     <unset>                          50s