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.

35 min 3 hands-on tasksStorage · 10% of the exam

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.

Dynamic provisioning

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

A class that keeps volumes and waits for a Pod before provisioning
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, ...
FieldDefaultWhat it controls
provisionerrequiredThe program that creates the volumes. On this lab: rancher.io/local-path. In a cloud: a driver such as ebs.csi.aws.com.
parametersnoneOptions for the provisioner, different for each one: disk type, speed, filesystem.
reclaimPolicyDeleteCopied onto every PV the class creates: Delete or Retain.
volumeBindingModeImmediateWhen the volume is created: at once (Immediate), or when a Pod needs it (WaitForFirstConsumer).
allowVolumeExpansionfalseWhether claims of this class may grow later.

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 from kubectl get sc standard -o yaml.
  • provisioner, parameters, reclaimPolicy and volumeBindingMode cannot 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 allowVolumeExpansion can 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.
WaitForFirstConsumer, step by step

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 sc then shows (default) after the class name.
  • A claim without storageClassName gets 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:

Switch the default class and confirm
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. 1.You create the claim. If it names no class, the default class is filled in.
  2. 2.With WaitForFirstConsumer, nothing happens until a Pod that uses the claim is scheduled to a node.
  3. 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. 4.Kubernetes binds the claim to the new PV. Both show Bound.
  5. 5.The on the Pod's node mounts the volume and starts the .
  6. 6.Later, when the claim is deleted, a Delete PV is removed with its storage. A Retain PV becomes Released and keeps everything.

You can follow each step on the lab. The claim cache below is the one from the second task:

Trace a volume on the lab, from claim to node directory
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 pods

Growing 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.

Grow a claim where the class allows it (example names, not the lab)
# 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.storage

In the official docs· open on exam day too

Hands-on task 1 of 3st-sc-1
~4 min3% weightStorage

Create a StorageClass

Create a StorageClass named 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. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-1/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-1/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-1/cleanup | bash
Hands-on task 2 of 3st-sc-2
~7 min5% weightStorage

Provision a volume dynamically from a class

In namespace 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. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-2/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-2/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-2/cleanup | bash
Hands-on task 3 of 3st-sc-3
~4 min3% weightStorage

Change the default StorageClass

The cluster's default StorageClass is 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. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-3/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-sc-3/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 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.