StorageClasses and dynamic provisioning
How a StorageClass creates volumes for claims, what its fields do, how the default class works, and how a claim grows.
Key ideas in this lesson · tap one for a simple explanation
What you will be able to do
- Write a StorageClass with the right provisioner, reclaim policy and binding mode, and say when its PV will appear.
- Change which class is the default, and say what happens to a claim that names no class.
- Follow a dynamically created volume from the claim to the PV to the folder on the node.
In the last lesson you wrote each PV by hand. Now imagine teams that create twenty databases a week: writing twenty PVs is slow. A does it for them. When a claim names the class, a program called a provisioner creates the storage and the PV automatically. This is called dynamic provisioning.
On the exam it shows up in three ways:
- Create a StorageClass with the settings the question gives.
- Create a claim and a that use a class.
- Make a class the cluster's default.
Each takes about two minutes once you know a class's few fields and the one annotation that marks the default.
Claim (PVC)
names a class
storageClassName: fast-retain
StorageClass
the recipe: provisioner and rules
fast-retain
Provisioner
creates the storage and a PV
rancher.io/local-path
New PV
bound to the claim
pvc-<uid>
You write only the claim (and the Pod). The provisioner named in the class creates the PV. On this lab the provisioner is rancher.io/local-path, which makes a folder on a node.
What a StorageClass looks like
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-retain
provisioner: rancher.io/local-path
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: false
parameters: {} # provisioner-specific: type: gp3, fsType: ext4, ...You may also meet the provisioner kubernetes.io/no-provisioner. It creates nothing: such a class only groups PVs made by hand. CSI drivers themselves are covered in CNI, CSI and CRI.
- There is no
kubectl create storageclass. Copy the from the docs, or start fromkubectl get sc standard -o yaml. provisioner,parameters,reclaimPolicyandvolumeBindingModecannot be changed later. To fix one, delete the class and create it again. Existing PVs keep working, because they only store the class name.- The default-class annotation and
allowVolumeExpansioncan be changed in place.
Immediate or WaitForFirstConsumer
volumeBindingMode decides when the volume is created. That matters for storage tied to one place, such as a disk on one or in one cloud zone.
- Immediate creates the volume as soon as the claim exists. But the has not picked a node for the Pod yet, so the volume may land where the Pod cannot run. Example: the disk is made in zone A, but the Pod can only run in zone B.
- WaitForFirstConsumer waits until a Pod that uses the claim is scheduled. The node is picked first, and the volume is created where the Pod is.
Claim created
Pending: no Pod uses it yet
Pod created
the scheduler picks a node
Volume created
on the Pod's node
Bound
claim Bound, Pod Running
The first step confuses many people. Until a Pod uses it, the claim shows Pending with the event waiting for first consumer to be created before binding. That is correct, not a fault. The lab's standard class works this way, like many cloud classes. So a claim alone never binds: create the Pod, and the claim binds too.
The default class
The default class is the one a claim gets when it names no class. One annotation marks it:
- The annotation is
storageclass.kubernetes.io/is-default-class: "true".kubectl get scthen shows(default)after the class name. - A claim without
storageClassNamegets the default class filled in when it is created. - A claim with
storageClassName: ""opts out. It binds only to PVs that have no class. - Keep exactly one default. If two classes have the annotation, the newest one wins. So when you switch, also clear the old one.
Switching the default takes two patches, and nothing restarts:
kubectl get sc
kubectl patch storageclass standard -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl patch storageclass st-bulk -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl get sc # exactly one "(default)"
kubectl get sc st-bulk -o jsonpath='{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}'What happens when a claim is created
- 1.You create the claim. If it names no class, the default class is filled in.
- 2.With
WaitForFirstConsumer, nothing happens until a Pod that uses the claim is scheduled to a node. - 3.The provisioner named in the class creates the storage (a disk, a folder, a file share). It also creates a PV named
pvc-<claim uid>, with the class's reclaim policy. - 4.Kubernetes binds the claim to the new PV. Both show
Bound. - 5.The on the Pod's node mounts the volume and starts the .
- 6.Later, when the claim is deleted, a
DeletePV is removed with its storage. ARetainPV becomesReleasedand keeps everything.
You can follow each step on the lab. The claim cache below is the one from the second task:
kubectl -n st-sc get pvc cache # Pending until a Pod uses it
kubectl -n st-sc describe pvc cache | tail -5 # the WaitForFirstConsumer event
kubectl -n st-sc get pvc cache -o jsonpath='{.spec.volumeName}' # pvc-<uid> once bound
kubectl get pv $(kubectl -n st-sc get pvc cache -o jsonpath='{.spec.volumeName}') -o yaml | grep -A3 -E 'hostPath:|nodeAffinity:|reclaimPolicy'
# local-path keeps the data on the node the Pod was scheduled to:
docker exec cka-worker ls /var/local-path-provisioner/ # pvc-<uid>_st-sc_cache
# The provisioner itself is an ordinary Deployment:
kubectl -n local-path-storage get podsGrowing a claim
If the class has allowVolumeExpansion: true, a claim can grow. Raise spec.resources.requests.storage on the claim, never on the PV. The driver then grows the storage. Until it finishes, status.capacity still shows the old size. A claim can never shrink below its current size.
On this lab no class allows expansion, so the commands below use example names from another cluster. Try it here and the refuses. The error says only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize.
# Example for another cluster: class fast, namespace app and claim data do not exist on this lab
kubectl get sc fast -o jsonpath='{.allowVolumeExpansion}' # must be true
kubectl -n app patch pvc data -p '{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
kubectl -n app get pvc data -o jsonpath='{.status.capacity.storage}{" "}{.status.conditions[*].type}'
# Interactive: kubectl edit pvc data -n app and change requests.storageIn the official docs· open on exam day too
- Storage Classessearch: storage classes
- Dynamic Volume Provisioningsearch: dynamic volume provisioning
- Change the default StorageClasssearch: change default storage class
Create a StorageClass
fast-retain that uses the provisioner rancher.io/local-path, keeps volumes after their claim is deleted, and delays volume binding until a Pod that uses the claim has been scheduled. It must not be the cluster's default class.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-1/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-1/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-1/cleanup | bash
Provision a volume dynamically from a class
st-sc create a PersistentVolumeClaim named cache that requests 500Mi with access mode ReadWriteOnce from the StorageClass fast-retain. Then create a Pod named cache-user with image busybox:1.36 and command sh -c 'echo cached > /cache/marker; while true; do sleep 3600; done' that mounts the claim at /cache. When you are done the claim must be Bound, and the PersistentVolume that backs it must have reclaim policy Retain.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-2/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-2/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-2/cleanup | bash
Change the default StorageClass
standard. A class named st-bulk has been added. Make st-bulk the default StorageClass so that claims which do not name a class use it, and make sure standard is no longer marked as default. Exactly one class may be marked default when you finish. Do not delete or recreate either class.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-3/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-3/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-3/cleanup | bash
Finished this lesson?
0 of 3 tasks passed. Your readiness counts tasks, so run them before you move on.