StorageClasses & Dynamic Provisioning
Static provisioning requires a cluster admin to pre-create a PersistentVolume before any developer can claim storage. For clusters with dozens of teams and hundreds of services, that doesn't scale. StorageClasses automate this: a developer submits a PVC, Kubernetes calls the provisioner specified in the StorageClass, a real cloud disk (or other storage) is created, a PV is registered, and the PVC is bound — all without admin intervention.
What Is a StorageClass?
A StorageClass is a cluster-scoped resource that defines a "class" of storage with specific characteristics: which provisioner creates the volumes, what parameters it uses, how fast the disks are, what the reclaim policy is. Developers reference a StorageClass by name in their PVC spec — they don't need to know how the storage is implemented.
storageClassName: fast-ssd, 50Gi, RWO
fast-ssd, finds provisioner
Provisioner
The provisioner field identifies which plugin creates volumes. It's either a built-in provisioner or a CSI (Container Storage Interface) driver deployed in the cluster.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com # CSI driver for AWS EBS
parameters:
type: gp3
iops: "3000"
throughput: "125"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
Common provisioners:
| Cloud / backend | Provisioner name |
|---|---|
| AWS EBS | ebs.csi.aws.com |
| GCP Persistent Disk | pd.csi.storage.gke.io |
| Azure Disk | disk.csi.azure.com |
| Azure Files | file.csi.azure.com |
| Local (on-prem) | kubernetes.io/no-provisioner |
| NFS (CSI driver) | nfs.csi.k8s.io |
| Longhorn | driver.longhorn.io |
| Ceph / Rook | rook-ceph.rbd.csi.ceph.com |
Parameters
The parameters block is provisioner-specific. Kubernetes passes these key-value pairs directly to the CSI driver when creating a volume. Common AWS EBS parameters:
# AWS EBS (ebs.csi.aws.com)
parameters:
type: gp3 # gp2, gp3, io1, io2, sc1, st1
iops: "3000" # gp3/io1/io2 only
throughput: "125" # gp3 only, MiB/s
encrypted: "true"
kmsKeyId: arn:aws:kms:... # optional: customer-managed key
# GCP Persistent Disk (pd.csi.storage.gke.io)
parameters:
type: pd-ssd # pd-standard, pd-ssd, pd-balanced, pd-extreme
# Azure Disk (disk.csi.azure.com)
parameters:
skuName: Premium_LRS # Standard_LRS, Premium_LRS, UltraSSD_LRS
reclaimPolicy
When a dynamically provisioned PV's PVC is deleted, the reclaimPolicy on the StorageClass determines what happens to the underlying disk:
Delete(default for most cloud StorageClasses) — the cloud disk is deleted. Use for transient or reconstructible data.Retain— the cloud disk is preserved. The PV moves toReleasedstate and must be cleaned up manually. Use for production databases.
EKS, GKE, and AKS all default to Delete. Accidentally deleting a PVC on a database StatefulSet in production will also delete the disk — and the data. Explicitly create a StorageClass with reclaimPolicy: Retain for any persistent data you care about, and reference it in your StatefulSet's volumeClaimTemplates.
volumeBindingMode
Controls when a PVC gets bound to a PV:
| Mode | When bound | Use case |
|---|---|---|
Immediate | As soon as the PVC is created | Storage that doesn't depend on node locality (NFS, cloud file systems) |
WaitForFirstConsumer | When a pod using the PVC is scheduled | Block storage (EBS, PD, Azure Disk) — disk must be in the same AZ as the node |
WaitForFirstConsumer is essential for zonal block storage. With Immediate, the EBS volume might be created in us-east-1a but the pod gets scheduled to us-east-1b — the pod fails to start because EBS volumes can't cross AZ boundaries.
Default StorageClass
A StorageClass can be marked as the cluster default — PVCs that omit storageClassName automatically use it. Only one StorageClass should be marked default at a time.
# Mark a StorageClass as default
metadata:
name: standard
annotations:
storageclass.kubernetes.io/is-default-class: "true"
# Check which StorageClass is the default (look for "(default)" in the NAME column)
kubectl get storageclass
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
# standard (default) ebs.csi.aws.com Delete WaitForFirstConsumer
Cloud Provider Examples
# AWS — general purpose SSD, AZ-aware
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: aws-gp3
provisioner: ebs.csi.aws.com
parameters:
type: gp3
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# GCP — SSD persistent disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gcp-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# Azure — premium managed disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: azure-premium
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
Local StorageClass
For on-premises clusters or when you need NVMe performance with no network overhead, use a local StorageClass. Local volumes bind a PVC to a specific node — pods using them must be scheduled to that node.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-nvme
provisioner: kubernetes.io/no-provisioner # no dynamic provisioning
volumeBindingMode: WaitForFirstConsumer # required for local volumes
reclaimPolicy: Retain
Local volumes still require manually creating the PV (pointing at a directory on a specific node). Tools like the local-static-provisioner automate PV creation for local disks.
Volume Expansion
When allowVolumeExpansion: true is set on a StorageClass, you can grow a PVC after creation by editing its spec.resources.requests.storage. Shrinking is not supported.
# Expand a PVC from 50Gi to 100Gi
kubectl patch pvc postgres-data -n production \
-p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'
# Check expansion status
kubectl describe pvc postgres-data -n production
# Look for: "Normal ExternalExpanding Waiting for an external controller to expand this PVC"
# Then: "Normal Resizing External resizer is resizing volume"
# Finally: "Normal FileSystemResizeRequired Require file system resize"
# (file system resize happens automatically when the pod restarts)
kubectl Commands
# List all StorageClasses
kubectl get storageclass
kubectl get sc # shorthand
# Describe a StorageClass (shows provisioner, parameters, binding mode)
kubectl describe storageclass fast-ssd
# Create a StorageClass
kubectl apply -f storageclass.yaml
# Change the default StorageClass (remove annotation from old, add to new)
kubectl annotate sc old-default storageclass.kubernetes.io/is-default-class-
kubectl annotate sc new-default storageclass.kubernetes.io/is-default-class=true
# List PVCs and their StorageClass
kubectl get pvc -A -o wide
# Check what StorageClass a PV was created with
kubectl get pv -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.storageClassName}{"\n"}{end}'