Storage is one of the most under-practiced areas of the Certified Kubernetes Administrator (CKA) exam — and one of the most reliable places to lose easy points. The concepts are small in number (a PersistentVolume, a PersistentVolumeClaim, a StorageClass, and a Pod that mounts the claim), but the failure modes are subtle: a claim that stays Pending forever, an access mode mismatch, or a reclaim policy that deletes data you needed.
This guide turns Kubernetes storage into a deterministic recipe. By the end, you will be able to read any storage question, identify the four moving parts, write the YAML from memory, and diagnose why a volume isn’t binding — all under exam time pressure.
Why Storage Matters on the CKA
Pods are ephemeral. When a Pod is rescheduled, anything written to its container filesystem is gone. For stateless apps that’s fine, but databases, message queues, and caches need data that survives Pod restarts and reschedules. Kubernetes solves this with a clean separation of concerns:
- Administrators provision storage capacity (PersistentVolumes) or define how it should be created on demand (StorageClasses).
- Developers request storage abstractly (PersistentVolumeClaims) without caring where it physically lives.
This admin-versus-consumer split is exactly the mental model the CKA tests. The exam expects you to play the administrator: create the supply, satisfy a claim, and mount it into a workload.
The Four-Resource Storage Model
Every Kubernetes storage scenario combines the same four pieces. Identify each one when you read a question and the YAML writes itself.
| Resource | Role | Who owns it |
|---|---|---|
| PersistentVolume (PV) | A piece of actual storage in the cluster | Administrator |
| StorageClass (SC) | A template for creating PVs dynamically | Administrator |
| PersistentVolumeClaim (PVC) | A request for storage by a workload | Developer |
| Pod / volume mount | Consumes the bound PVC at a mount path | Developer |
The binding flow has two paths:
- Static provisioning — an admin pre-creates a PV; a PVC binds to it if capacity, access mode, and storage class match.
- Dynamic provisioning — a PVC references a StorageClass, and Kubernetes creates the PV automatically.
The CKA tests both. Static provisioning shows up as “make this PVC bind to that PV,” and dynamic provisioning shows up as “set this StorageClass as default” or “create a PVC that provisions storage automatically.”
PersistentVolumes: The Supply Side
A PersistentVolume represents real storage — a hostPath directory, an NFS export, a cloud disk, or a CSI volume. Here is a minimal hostPath PV, the kind you can create on the exam without any cloud provider:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-data
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /mnt/data
Apply and inspect it:
kubectl apply -f pv-data.yaml
kubectl get pv
kubectl describe pv pv-data
A freshly created PV shows STATUS: Available. Once a claim binds to it, the status flips to Bound and the CLAIM column names the PVC.
Access Modes
Access modes are the single most common reason a PVC won’t bind. There are four, but only three appear in practice:
| Access mode | Short | Meaning |
|---|---|---|
ReadWriteOnce | RWO | Mounted read-write by a single node |
ReadOnlyMany | ROX | Mounted read-only by many nodes |
ReadWriteMany | RWX | Mounted read-write by many nodes |
ReadWriteOncePod | RWOP | Mounted read-write by a single Pod |
A critical nuance: ReadWriteOnce means a single node, not a single Pod. Multiple Pods on the same node can share an RWO volume. If you need to guarantee exactly one Pod, that’s what ReadWriteOncePod is for.
The binding rule is strict: a PVC requesting RWX cannot bind to a PV that only offers RWO. The requested access mode must be one the PV supports.
Reclaim Policies
The persistentVolumeReclaimPolicy decides what happens to the underlying storage when its PVC is deleted:
| Policy | Behavior |
|---|---|
Retain | PV and data are kept; the PV becomes Released and must be manually cleaned up before reuse |
Delete | The PV and the backing storage asset are deleted automatically |
Recycle | Deprecated — do not rely on it |
On the exam, if a question says “ensure data is not lost when the claim is removed,” you want Retain. Dynamically provisioned PVs usually inherit Delete from their StorageClass, so changing this is a common task.
PersistentVolumeClaims: The Demand Side
A PVC is a request. It says “I need this much storage, with these access modes, optionally from this StorageClass.” Here is a claim that will bind to the manual PV above:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: manual
resources:
requests:
storage: 500Mi
kubectl apply -f pvc-data.yaml
kubectl get pvc
Notice the claim requests 500Mi while the PV offers 1Gi. Binding still succeeds — a PVC binds to any PV that offers at least the requested capacity, with a matching access mode and storage class. The Pod then gets the full 1Gi; Kubernetes does not shrink the PV to the request.
For binding to occur, three conditions must all hold:
- Capacity: PV capacity ≥ PVC request.
- Access mode: PV supports the mode the PVC requests.
- StorageClass:
storageClassNamematches on both sides (empty string and unset are not the same — more on this below).
StorageClasses and Dynamic Provisioning
Pre-creating PVs by hand doesn’t scale. A StorageClass lets the cluster provision a PV automatically the moment a PVC needs one.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
The provisioner is the CSI driver or in-tree plugin that actually creates the volume (it varies by cluster — kubernetes.io/aws-ebs, pd.csi.storage.gke.io, rancher.io/local-path, etc.). A PVC that references this class triggers provisioning:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-dynamic
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 2Gi
No matching PV exists, but because fast has a provisioner, Kubernetes creates one and binds it.
volumeBindingMode: The Detail People Miss
WaitForFirstConsumer delays volume creation until a Pod that uses the PVC is scheduled. This matters in multi-node and multi-zone clusters: it lets the scheduler place the Pod first, then create the volume in the same zone. The alternative, Immediate, provisions as soon as the PVC is created and can strand a volume in a zone with no room for the Pod.
If your dynamically provisioned PVC sits in Pending and describe shows waiting for first consumer, that is not a bug — it’s WaitForFirstConsumer working as designed. Create a Pod that mounts the claim and the volume springs into existence.
Setting a Default StorageClass
A common exam task is “make fast the default StorageClass.” A PVC with no storageClassName uses the default. Set it with an annotation:
kubectl patch storageclass fast \
-p '{"metadata": {"annotations": {"storageclass.kubernetes.io/is-default-class": "true"}}}'
Verify with kubectl get storageclass — the default is marked (default). Only one class should be default; if two are, behavior is undefined, so unset the old one by patching its annotation to "false".
Mounting a PVC into a Pod
Storage is useless until a workload mounts it. The claim is referenced under spec.volumes, then mounted under the container’s volumeMounts:
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-data
The chain is: container volumeMounts.name → Pod volumes.name → persistentVolumeClaim.claimName → the PVC → the bound PV → real storage. If any link is misnamed, the Pod fails to start. Confirm the mount from inside the Pod:
kubectl exec -it app -- df -h /usr/share/nginx/html
kubectl exec -it app -- sh -c 'echo hello > /usr/share/nginx/html/index.html'
A Complete Exam-Style Walkthrough
Here is the kind of multi-step task the CKA strings together. Do it end-to-end until it’s muscle memory.
Create a PersistentVolume
pv-logof 2Gi with access mode ReadWriteOnce and reclaim policy Retain, backed by hostPath/var/log/app. Create a PVCpvc-logrequesting 1Gi that binds to it. Mount the PVC into a Pod namedloggerat/data.
# 1. PV
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-log
spec:
capacity:
storage: 2Gi
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
hostPath:
path: /var/log/app
EOF
# 2. PVC (empty storageClassName so it only binds to the PV above, not a default class)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-log
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ""
resources:
requests:
storage: 1Gi
EOF
# 3. Verify the binding before touching the Pod
kubectl get pvc pvc-log
Once pvc-log shows Bound, generate the Pod and add the volume:
kubectl run logger --image=busybox --dry-run=client -o yaml \
--command -- sleep 3600 > logger.yaml
# edit logger.yaml to add volumes + volumeMounts, then:
kubectl apply -f logger.yaml
Always verify the binding before you build the Pod. If the PVC is still Pending, the Pod will never start, and you’ll waste time debugging the wrong resource.
Troubleshooting Storage: A Decision Tree
When a PVC won’t bind or a Pod won’t mount, work through these in order. kubectl describe pvc <name> and the Pod’s events are your primary tools.
| Symptom | Likely cause | Fix |
|---|---|---|
PVC stuck Pending, no events | No PV matches and no StorageClass set | Create a matching PV or set storageClassName to a real class |
PVC Pending, event “waiting for first consumer” | volumeBindingMode: WaitForFirstConsumer | Schedule a Pod that uses the PVC |
PVC Pending, capacity too small on PV | PV smaller than the request | Increase PV capacity or lower the request |
PVC Pending, access mode mismatch | PVC wants RWX, PV offers RWO | Align access modes |
PVC Pending, storage class mismatch | storageClassName differs (or unset vs "") | Make both sides match exactly |
Pod ContainerCreating, “unable to mount” | Backing path missing or node affinity conflict | Check describe pod events for the real driver error |
PV Released, won’t rebind | Retain policy left stale claimRef | Edit the PV and clear spec.claimRef |
The single most common gotcha: an empty storageClassName: "" and an omitted storageClassName are different. An empty string means “do not use any class — only bind to a PV that also has an empty class.” An omitted field means “use the default StorageClass.” Mixing these is the cause of countless Pending claims on the exam.
Speed Tips for Exam Day
- Use imperative scaffolding.
kubectl run ... --dry-run=client -o yamlandkubectl create -fsave precious minutes versus typing YAML from scratch. For storage, keep a tiny PV/PVC snippet memorized. - Bookmark the docs. During the exam you may use the official Kubernetes docs. The Persistent Volumes page has copy-paste-ready manifests — search “persistent volume” and grab the example.
- Check the StorageClass first. Before writing a PVC, run
kubectl get storageclassto see what exists and which is default. It tells you whether to provision statically or dynamically. - Verify, don’t assume. After every storage step, run
kubectl get pv,pvcto confirmBoundbefore moving on.
Where This Fits in Your CKA Prep
Storage rarely lives alone on the exam — it interlocks with scheduling, workloads, and troubleshooting. Pair this guide with the CKA troubleshooting guide for diagnosing stuck Pods, and the CKA exam domains breakdown to see how storage is weighted against the other domains. If you’re still mapping out your timeline, the 30-day CKA study plan slots storage into a realistic week-by-week schedule, and the CKA kubectl cheat sheet covers the imperative commands referenced above.
The fastest way to make storage automatic is repetition under timed conditions. Working through full performance-style scenarios — bind a static PV, set a default StorageClass, fix a Pending claim, mount it into a workload — is exactly the kind of practice that turns hesitation into reflex. The Certified Kubernetes Administrator (CKA) Mock Exam Bundle is built around these hands-on, exam-style tasks with performance-focused review, so you walk into the real exam having already solved each storage pattern several times.
Frequently Asked Questions
What is the difference between a PV and a PVC?
A PersistentVolume (PV) is the actual storage resource in the cluster, provisioned by an administrator (or dynamically). A PersistentVolumeClaim (PVC) is a request for storage made by a workload. The PVC binds to a PV that satisfies its capacity, access mode, and storage class requirements. Think of the PV as supply and the PVC as demand.
Why is my PersistentVolumeClaim stuck in Pending?
The most common causes are: no PV matches the claim’s capacity or access mode, the storageClassName doesn’t match (including the empty-string-versus-omitted trap), or a dynamic StorageClass with WaitForFirstConsumer is waiting for a Pod to be scheduled. Run kubectl describe pvc <name> and read the events to identify which one applies.
Does ReadWriteOnce mean only one Pod can use the volume?
No. ReadWriteOnce (RWO) means the volume can be mounted read-write by a single node. Multiple Pods scheduled on that same node can share it. If you need to restrict access to exactly one Pod, use ReadWriteOncePod (RWOP) instead.
What is dynamic provisioning in Kubernetes?
Dynamic provisioning automatically creates a PersistentVolume when a PVC requests storage from a StorageClass, rather than requiring an administrator to pre-create PVs. The StorageClass defines a provisioner (a CSI driver) that creates the underlying disk on demand. It’s the default model in most production and cloud clusters.
How do I make a PV keep its data after the PVC is deleted?
Set persistentVolumeReclaimPolicy: Retain on the PV. When the PVC is deleted, the PV moves to a Released state and the data is preserved. You must then manually clear the PV’s claimRef before it can be reused.
Is storage heavily tested on the CKA?
Storage is part of the “Workloads & Scheduling” and “Storage” areas of the curriculum and reliably appears as one or more hands-on tasks — typically creating a PV/PVC pair, configuring a StorageClass, or mounting a claim into a Pod. It’s a high-value area to drill because the tasks are well-defined and quick to complete once the patterns are memorized.
Can a single PVC be mounted by multiple Pods?
Only if the underlying PV supports an access mode that allows it. A ReadWriteMany (RWX) volume can be mounted read-write by Pods across multiple nodes; a ReadOnlyMany (ROX) volume can be mounted read-only by many. An RWO volume is limited to Pods on a single node.