17.7 StorageClasses and Dynamic Provisioning
Right, so you’ve manually created a PersistentVolume and bound it to a PersistentVolumeClaim. It works. It’s also a colossal pain in the neck. You had to get your ops team to pre-provision that 100GB of storage on some NFS server or in your cloud account, write a YAML manifest pointing to it, and hope the PVC you eventually create matches its specs. This is the infrastructure equivalent of hand-knitting your own ethernet cables. It’s static provisioning, and it’s fine for a pet project, but it falls apart completely when you need to scale.
Enter StorageClasses and dynamic provisioning. This is Kubernetes saying, “Oh, you need storage? Why didn’t you just say so? Here you go.” Instead of pre-provisioning volumes, you define a class of storage. Then, when you create a PVC that requests that class, Kubernetes automatically calls out to the cloud provider (or your on-prem CSI driver) and provisions the volume on the fly. It’s magic, but the good kind—the kind with detailed, auditable logs.
The StorageClass: Blueprint for On-Demand Storage
Think of a StorageClass as a recipe or a blueprint. It doesn’t create any storage itself; it just defines how to create it when someone comes knocking with a PVC. The most crucial parameter is the provisioner, which determines who actually does the provisioning.
Here’s a classic example for AWS EBS gp3 volumes:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd # This is the name you'll reference in your PVCs
provisioner: ebs.csi.aws.com # The magic wand-wielder
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true" # Because you're not a maniac
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
The parameters section is where you get specific, and they are entirely dependent on the provisioner you’re using. The AWS EBS CSI driver cares about type, iops, and throughput. The Azure Disk driver cares about skuName. The NFS provisioner might care about server and path. You have to read the docs for your specific provisioner, a shocking concept, I know.
Why volumeBindingMode is a Secret Weapon
See that volumeBindingMode: WaitForFirstConsumer line? This is one of the most important and often-overlooked settings. The default is Immediate. Let me tell you why Immediate is frequently a bad idea.
In Immediate mode, the moment you create the PVC, Kubernetes calls the cloud provider and provisions the volume. The volume is then immediately bound to the PVC. So what? Well, cloud volumes are typically zonal resources. An EBS volume lives in a single Availability Zone (AZ). If you create a PVC with Immediate binding in a multi-zone cluster, the provisioner might create that volume in us-east-1a. But if your Pod that uses this PVC gets scheduled to us-east-1b… it can’t attach the volume. The Pod is stuck pending forever.
WaitForFirstConsumer fixes this brilliantly. When you set this mode, the StorageClass tells Kubernetes to wait until a Pod is scheduled that uses the PVC. Kubernetes then looks at which AZ that Pod is scheduled to, and then it calls the provisioner, telling it to create the volume in the correct AZ. It’s a simple switch that prevents a world of scheduling pain. Always use it unless you have a very specific reason not to.
Let’s See This in Action
You don’t create StorageClasses all the time. You create a few good ones as a cluster admin, and then developers just reference them in their PVCs.
First, create the StorageClass (if it’s not already there, which it often is—cloud providers usually install a few default ones):
kubectl apply -f storageclass-fast-ssd.yaml
Now, a developer can create a PVC that requests this class:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd # This is the magic key!
resources:
requests:
storage: 100Gi
The moment you kubectl apply this, the PVC goes into a Pending state. The CSI driver sees it, recognizes the fast-ssd class, and makes an API call to AWS to create a 100Gi gp3 volume with the exact parameters we specified. Seconds later, the PVC status flips to Bound and a shiny new PersistentVolume object is automatically created and bound to it. You can now use this PVC in a Pod, and Kubernetes will handle all the nitty-gritty of attaching the cloud volume to your node.
The Default StorageClass and Pitfalls
Most clusters have a default StorageClass. If you create a PVC and omit the storageClassName field entirely, the cluster will use this default. You can see which one it is by looking for the storageclass.kubernetes.io/is-default-class annotation.
kubectl get storageclass
The output will show which one is marked as (default).
This is convenient, but it’s a classic pitfall. You might think you’re getting the “standard” disk, but the default could be set to expensive SSD storage. Always explicitly define the storageClassName in your PVCs. It’s self-documenting and prevents expensive surprises.
Another best practice: set allowVolumeExpansion: true on your StorageClasses whenever possible. It future-proofs your storage. If you later need to bump that 100Gi PVC to 200Gi, you can edit the PVC spec and Kubernetes and the underlying cloud provider can often handle the resize without downtime. If this is set to false, you’re stuck. You’ll have to snapshot, create a new, bigger volume, restore, and redeploy—a process so tedious it makes me want to become a lumberjack.