Storage Classes and Dynamic Provisioning
In the previous lesson, you created a PersistentVolume manually before creating a PVC. That works, but in practice it creates a problem: someone has to create PVs ahead of time, predict what sizes and access modes applications will need, and manage a pool of pre-provisioned storage. In a cloud environment, where storage can be provisioned programmatically on demand, this manual approach is unnecessary overhead.
StorageClasses enable dynamic provisioning: instead of requiring a pre-existing PV, a StorageClass tells Kubernetes how to create a PV automatically when a PVC is submitted. The developer creates a PVC referencing a StorageClass, Kubernetes calls the provisioner defined in that class, and a PV is created and bound to the PVC without any administrator intervention.
A StorageClass defines a type of storage and a provisioner that knows how to create it on demand. When a PVC references a StorageClass, the PV is created automatically at the time the PVC is submitted.
How Dynamic Provisioning Works
The difference from static provisioning is in who creates the PV and when. In static provisioning, an administrator creates PVs before any application needs them. In dynamic provisioning, the provisioner creates the PV at the moment a PVC requests it. The developer never writes a PV manifest and never needs to know what physical storage backs the claim.
graph LR
SC["StorageClass<br>fast-ssd"]
PVC["PersistentVolumeClaim<br>storageClassName: fast-ssd"]
PROV["Cloud Provisioner"]
PV["PersistentVolume<br>(created automatically)"]
Pod["Pod"]
PVC -->|"triggers"| PROV
PROV -->|"creates"| PV
PVC -->|"bound to"| PV
Pod -->|"uses"| PVC
A StorageClass Manifest
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: fast-ssdprovisioner: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumerreclaimPolicy: DeleteThe provisioner field is where the integration point lives. On AWS, it would be ebs.csi.aws.com. On GCP, pd.csi.storage.gke.io. On Azure, disk.csi.azure.com. The provisioner is a controller that runs inside the cluster and knows how to communicate with the cloud provider’s storage API to create and delete volumes.
reclaimPolicy controls what happens to the PV when the PVC that bound to it is deleted. Delete means the PV and the underlying cloud disk are both deleted - the data is gone. Retain means the PV is kept after the PVC is deleted, marked as Released, and must be manually cleaned up by an administrator before it can be reused. For production databases, Retain is usually the safer choice: accidental deletion of a PVC shouldn’t mean permanent data loss.
Referencing a StorageClass in a PVC
Adding storageClassName to a PVC tells Kubernetes which StorageClass to use for dynamic provisioning:
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: my-dynamic-pvcspec: storageClassName: fast-ssd accessModes: - ReadWriteOnce resources: requests: storage: 10GiKubernetes passes this request to the fast-ssd provisioner, which creates a 10Gi SSD volume in the cloud, wraps it in a PV object, and binds the PVC to it. No PV manifest was ever written.
The Default StorageClass
Most clusters have a default StorageClass. When a PVC doesn’t specify storageClassName at all, Kubernetes uses the default. This is convenient: for most workloads, developers don’t need to know about StorageClasses unless they have specific requirements. They just request storage and get it.
You can identify the default StorageClass from its annotation:
kubectl get storageclass# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE# standard (default) kubernetes.io/... Delete ImmediateThe (default) annotation means any PVC without an explicit storageClassName will use this class.
Hands-On Practice
1. List the available StorageClasses and inspect the default one:
kubectl get storageclasskubectl describe storageclass standardLook at the Provisioner, ReclaimPolicy, and VolumeBindingMode fields.
2. Create a PVC without specifying a StorageClass:
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: dynamic-pvcspec: accessModes: - ReadWriteOnce resources: requests: storage: 100Mikubectl apply -f dynamic-pvc.yamlkubectl get pvc dynamic-pvckubectl describe pvc dynamic-pvcCheck the STORAGECLASS column, the default StorageClass is applied automatically. Check STATUS, it should show Bound. Then look at the automatically created PV:
Check the STORAGECLASS column, the default StorageClass is applied automatically. With a default class using WaitForFirstConsumer, STATUS usually stays Pending until a Pod consumes the claim.
kubectl get pvAt this step, it is normal to see no bound PV yet. Provisioning and binding happen when the first consumer Pod is scheduled.
3. Mount the provisioned storage in a Pod:
apiVersion: v1kind: Podmetadata: name: dynamic-storage-demospec: volumes: - name: storage persistentVolumeClaim: claimName: dynamic-pvc containers: - name: app image: busybox:1.36 args: ['sleep', '3600'] volumeMounts: - name: storage mountPath: /datakubectl apply -f dynamic-pod.yamlkubectl get pod dynamic-storage-demokubectl exec dynamic-storage-demo -- touch /data/test.txtkubectl exec dynamic-storage-demo -- ls /data/test.txtkubectl get pvc dynamic-pvckubectl get pvOnce the Pod exists, the claim can transition to Bound and a matching PV appears. The Pod writes inside the PVC-backed mount, proving the claim is usable by workloads.
4. Clean up:
kubectl delete pod dynamic-storage-demokubectl delete pvc dynamic-pvcDeleting the PVC triggers the reclaim policy. Since the default class uses Delete, the dynamically provisioned PV is deleted as well. Confirm with kubectl get pv.