Storage Provisioning
Create a new StorageClass named local-storage with the provisioner rancher.io/local-path. Set volumeBindingMode to WaitForFirstConsumer. Do not make it the default SC.
Live lab access
This task description is public. Its live lab requires full access; six other CKA labs are available in Free Practice with AI Lab Coach.
Create an account to continue →
Explore the six free labs with AI Lab Coach →
Read the brief on any device. Use a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Create a new StorageClass named local-storage with the provisioner rancher.io/local-path. Set volumeBindingMode to WaitForFirstConsumer. Do not make it the default SC.
Patch the StorageClass to make it the default StorageClass.
Ensure local-storage is the only default class.
Do not modify any existing Deployments or PersistentVolumeClaims.
Original Prepium workshop · Tested on Kubernetes v1.35.0 on 1 October 2026 in a three-node kind cluster, which ships the rancher.io/local-path provisioner and a default class named standard. Use a disposable cluster: this workshop changes which StorageClass is the cluster default, and the cleanup step changes it back.
StorageClasses, WaitForFirstConsumer, and owning the default class
A StorageClass tells Kubernetes how to create a volume when a claim asks for one: which provisioner to call, what to do with the volume when the claim is deleted, and when to provision. The default class is simply the one whose storageclass.kubernetes.io/is-default-class annotation is "true"; claims that omit storageClassName receive it at admission.
1. Look before you change anything
kubectl get storageclass
kubectl get storageclass -o custom-columns=NAME:.metadata.name,DEFAULT:.metadata.annotations.storageclass\.kubernetes\.io/is-default-class
The (default) marker in the first command is derived from that annotation. The second command shows the raw value, which is what you edit.
2. Create a class that waits for a consumer
Save this as storageclass-wffc.yaml. It defines a new class and a claim that uses it.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: tutorial-local
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: scratch
namespace: prepium-tutorial
spec:
storageClassName: tutorial-local
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
kubectl create namespace prepium-tutorial
kubectl apply -f storageclass-wffc.yaml
kubectl -n prepium-tutorial get pvc scratch
kubectl -n prepium-tutorial describe pvc scratch
The claim is Pending, and that is correct. The event reads waiting for first consumer to be created before binding. With volumeBindingMode: WaitForFirstConsumer, provisioning is delayed until a Pod using the claim is scheduled, so the volume can be created on the node where that Pod will run. With Immediate, the volume is created first and the Pod must then fit wherever the volume landed. Do not "fix" this Pending state; give it a consumer.
kubectl -n prepium-tutorial run consumer --image=busybox:1.36 --restart=Never \
--overrides='{"spec":{"containers":[{"name":"consumer","image":"busybox:1.36","command":["sleep","3600"],"volumeMounts":[{"name":"s","mountPath":"/s"}]}],"volumes":[{"name":"s","persistentVolumeClaim":{"claimName":"scratch"}}]}}'
kubectl -n prepium-tutorial wait --for=condition=Ready pod/consumer
kubectl -n prepium-tutorial get pvc scratch
kubectl get pv "$(kubectl -n prepium-tutorial get pvc scratch -o jsonpath='{.spec.volumeName}')" -o jsonpath='{.spec.nodeAffinity}{"\n"}'
Now the claim is Bound to a dynamically created PV, and that PV carries node affinity for the node the Pod was scheduled on.
3. Change the default safely
kubectl annotate storageclass tutorial-local storageclass.kubernetes.io/is-default-class=true --overwrite
kubectl get storageclass
Both classes now show (default). Kubernetes tolerates this: since v1.26, a claim without a class gets the most recently created default, which in our run was tutorial-local. It is still a configuration error, because which class wins depends on creation order and two people reading the cluster can reach different conclusions. Remove the flag from the old class:
kubectl annotate storageclass standard storageclass.kubernetes.io/is-default-class=false --overwrite
kubectl get storageclass
Exactly one class is marked (default). Changing the default does not touch existing claims: scratch keeps its class and its volume. The default only affects claims created afterwards.
| Example check | Evidence |
|---|---|
| Class has the right provisioner and mode | PROVISIONER and VOLUMEBINDINGMODE columns |
| Exactly one default | One annotation value of "true" across all classes |
| Existing workloads untouched | Existing claims keep their storageClassName |
Cleanup (restores the original default): kubectl annotate storageclass standard storageclass.kubernetes.io/is-default-class=true --overwrite, then kubectl delete namespace prepium-tutorial and kubectl delete storageclass tutorial-local. Reference: Storage Classes and changing the default StorageClass. Next, try binding a claim to a specific PV.
What this tests
Configure persistent volumes, claims, storage classes, and volume access modes. On the CKA exam, Storage tasks are graded purely on what you build in the cluster - not multiple choice - so the only way to get faster is to do them on a real cluster against a clock.
Practice it for real
The public description lets you study the task before signing up. Full access includes every live lab. Six selected CKA scenarios are included in Free Practice, with five shared sessions each week. You solve the task in a real terminal, hit validate, and a programmatic checker scores exactly what you got right and wrong (with partial credit). You can open the solution while practising, then retry and use the failed checks to improve your score.