Home About Me

Using StorageClass in Kubernetes to Automate Persistent Storage

When you need persistent storage in Kubernetes, the usual building blocks are PVs and PVCs. That works, but creating them manually quickly becomes tedious. The pain is even more obvious with StatefulSet workloads, where each Pod may need its own PVC and matching PV. In that kind of setup, managing storage by hand is cumbersome, which is exactly where StorageClass becomes useful.

What StorageClass solves

A simple way to think about this setup is:

  • NFS is the actual backend storage.
  • nfs-client acts as the provisioner that dynamically creates PVs and PVCs.
  • A StorageClass points to that provisioner.
  • A StatefulSet or another workload can then use it through storageClassName.

In this example, NFS is used as the storage backend, but the same idea applies if you are using another storage system.

Preparation

Before deploying anything, you need a working NFS service or another storage backend. Here the configuration is based on NFS.

Deploying the provisioner

Create the ServiceAccount and RBAC

Because the provisioner needs to work with PVs and PVCs, it needs the appropriate permissions first:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: my-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
  namespace: my-system
rules:
  - 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: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: my-system
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

Deploy nfs-client

Next, create a Deployment for the provisioner. Its job is to allocate the storage resources that workloads request dynamically.

The important part here is that NFS_SERVER and NFS_PATH must be changed to match your own NFS server.

Also note the image choice in this example: using quay.io/external_storage/nfs-client-provisioner:latest can trigger the error unexpected error getting claim reference: selfLink was empty, can't make reference, so the following image is used instead.

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
  namespace: my-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          #image: quay.io/external_storage/nfs-client-provisioner:latest,使用这个会报错 unexpected error getting claim reference: selfLink was empty, can't make reference,故使用下面的镜像
          image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              # 这个名称你可以自定义,需要记住,下面还要用
              value: linkinstars.com/nfs
            - name: NFS_SERVER
              value: 172.16.1.1
            - name: NFS_PATH
              value: /kubernetes/test
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 200m
              memory: 512Mi
      volumes:
        - name: nfs-client-root
          nfs:
            server: 172.16.1.1
            path: /kubernetes/test

PROVISIONER_NAME can be customized, but you need to remember it because the StorageClass must use the same value.

Create the StorageClass

Once the provisioner is running, define a StorageClass that points to it:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: my-nfs-storage
  namespace: my-system
provisioner: linkinstars.com/nfs # 这里的名字需要和上面配置的 PROVISIONER_NAME 名称一致

The value of provisioner must match the PROVISIONER_NAME configured in the Deployment.

Verify that it works

A quick way to test the setup is to create a PVC. If the PVC is created successfully and a PV is provisioned automatically without errors, the deployment is working as expected.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
  namespace: my-system
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
  storageClassName: my-nfs-storage

Using StorageClass with StatefulSet

StorageClass is especially useful with StatefulSet, because StatefulSet commonly relies on volumeClaimTemplates to generate the PVCs it needs dynamically.

volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 32Mi
      storageClassName: my-nfs-storage

The configuration is straightforward: in practice, you mainly need to set storageClassName.

In the example here, Consul is deployed with a StatefulSet.

Consul StatefulSet storage example

Automatically created PVs and PVCs

From the result, you can see that the PVs and PVCs are created automatically, which makes storage management much easier. With that in place, Consul's data-dir can be persisted without manually preparing a PV and PVC for every Pod.

StorageClass is one of those Kubernetes features that saves a lot of repetitive operational work. For stateful workloads in particular, letting the cluster create storage dynamically is far more practical than provisioning each PV by hand.