CKA Storage — the volume that survived the claim
Reuse the leftover PV. Leave one default class. The binder is literal.
<!-- hal:authoritative:yaml -->
§I — Frame
CKA on this arc is 90% if you count the 08-10 Ops storage fire, 75% on the Cert slot alone. 08-16 closed Workloads and Scheduling and left one sentence: the remaining 10% Storage can wait for a Cert revisit. k8s_day_counter is 10, even. Today is that revisit.
The exam will not ask you to explain GKE. It will hand you a leftover PersistentVolume and a StorageClass annotation. One stem says the Deployment was deleted and the data must come back. The other says create a class, then make it the only default. Both stems grade the same two facts. A PV outlives a PVC only when reclaim is Retain. A PVC that names no class lands on whichever StorageClass carries storageclass.kubernetes.io/is-default-class=true.
Call the leftover the volume that survived the claim. Q1 is that volume, reused. Q14 is the class that decides whether the next volume will survive or vanish.
08-10 already taught three Pendings on EKS. Do not spend this fire on volumeBindingMode as a Pending owner. Spend it on what happens after Bound, and on the default-class gun.
§II — Domain foundations
Three objects. Cluster-scoped PV. Namespaced PVC. Cluster-scoped StorageClass. The Pod mounts the claim. The claim binds the volume. The class, if the volume was dynamic, wrote the reclaim policy onto the PV at create.
Two reclaim policies remain. Delete removes the PV and the back-end asset when the PVC goes. Retain keeps both. Recycle is gone. If a stem shows Recycle, treat it as a distractor.
After a Retain delete the PV phase is Released. spec.claimRef still names the dead claim. The binder will not reuse the volume while that pointer exists. LabSetUp for Q1 patches it away before you sit down. A live cluster will not. If you sit a stem where the PV is still Released, the first act is that patch, then the PVC.
Capacity and access mode must match. Q1 asks 250Mi and ReadWriteOnce. A 200Mi request will not bind a 250Mi PV under the default rules. A ReadWriteMany request will not bind a ReadWriteOnce volume. storageClassName must match, including the empty string. A PVC that omits the field becomes the empty-or-default case. A PVC that sets storageClassName: "" binds only a PV whose class is also empty. Those are different.
volumeName pins a PVC to one PV. SolutionNotes calls it optional and then uses it. Use it on Q1. There is only one PV. Pinning is how you refuse a second volume you did not see.
§III — Q1 flavor: MariaDB and the retained PV
The stem is short. A user deleted the MariaDB Deployment. Persistent storage was in use. Re-establish the Deployment. Preserve the data by reusing the available PersistentVolume. Only one PV exists.
Tasks, counted:
- Create a PVC named
mariadbin namespacemariadb. Access mode ReadWriteOnce. Storage 250Mi. - Edit
~/mariadb-deploy.yamlso the volume uses that PVC. - Apply the Deployment. It must be running and stable.
LabSetUp already did the damage you are scoring. It created the namespace, a PV named mariadb-pv with persistentVolumeReclaimPolicy: Retain and 250Mi RWO, an initial PVC, a Deployment, then deleted the Deployment and the PVC. It patched away claimRef so the PV is Available. It left claimName: "" in the Deployment file.
Your job is the rebind, not the archaeology. kubectl get pv mariadb-pv should show Available and Retain. If it shows Released, the patch did not run. Remove /spec/claimRef yourself.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mariadb
namespace: mariadb
spec:
accessModes:
- ReadWriteOnce
volumeName: mariadb-pv
resources:
requests:
storage: 250Mi
SolutionNotes sets storageClassName to the cluster default when volumeName is omitted. With volumeName set, the class field can stay empty and the bind still happens by name. Without volumeName, a class mismatch against the PV is a Pending PVC and a MariaDB that never mounts.
Apply the PVC. kubectl get pvc mariadb -n mariadb must be Bound. kubectl get pv mariadb-pv must show Bound to mariadb/mariadb.
Then the Deployment. The file is at ~/mariadb-deploy.yaml. The volume already has the right structure. claimName is the empty string. Set it to mariadb. Apply. Wait for Available. If the Pod is Pending, describe it. unbound PersistentVolumeClaim means you applied the Deployment first. unbound immediate PersistentVolumeClaims means the PVC is still Pending. Go back to the PV. Do not raise replicas.
The data path is /var/lib/mysql. You do not recreate it. The retained volume still has the bytes. A new emptyDir or a new dynamically provisioned PVC is a pass on "running" and a fail on "data is preserved." Grade yourself on the PV name in kubectl get pvc -o wide, not on a Ready replica alone.
Read the PV before you write YAML. kubectl get pv mariadb-pv -o yaml is the contract. Copy capacity, accessModes, and storageClassName off that object. Do not invent 1Gi because that is what your memory uses. Do not invent ReadWriteMany because databases "should share." The stem said 250Mi and RWO. The PV already has those. Your PVC is a ticket that must match a ticket the cluster is already holding.
Namespace is part of the name. PVC mariadb in default is a different object. The Deployment is in mariadb. A Bound PVC in the wrong namespace is a green get pvc and a Pending Pod. Create -n mariadb.
The Deployment file is the third trap. LabSetUp left claimName: "". YAML will apply that. Kubernetes will create a Pod whose volume refers to a claim named empty, or will reject the apply. Either way you have not mounted mariadb. Open the file. Replace the empty string. Do not replace the volume name mariadb-storage or the mountPath. Those are already correct. The only blank is the claim.
If the Pod crash-loops after a correct bind, describe and logs. MariaDB can fail on a leftover ibdata from the previous process, or on a permission shift. That is application noise. The stem asked for running and stable and data preserved. If the PVC is Bound to mariadb-pv and the container is up, you are done. If you wipe the mount to "fix" the crash-loop, you spent the retained bytes. Stop. Read the log. Fix the env, not the volume.
§IV — Q14 flavor: the class that is the only default
Tasks, counted:
- Create a StorageClass named
local-storage. Provisionerrancher.io/local-path.volumeBindingMode: WaitForFirstConsumer. Do not make it default on the first apply. - Patch it to be the default StorageClass.
- Ensure
local-storageis the only default. Do not modify existing Deployments or PVCs.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
Apply. kubectl get sc should show local-storage without (default). The existing local-path (or standard) still wears that mark.
Then the pair of patches. SolutionNotes writes both. The second is the one candidates skip.
kubectl patch storageclass local-storage -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl get sc must show one (default). Two defaults means a PVC that omits the class has undefined behavior. Zero defaults means that PVC stays Pending. The stem said only one.
WaitForFirstConsumer is required by the stem. Immediate is the usual wrong mode because it is what standard uses on GKE (Poulton Ch. 10, p. 129) and what your fingers remember from 08-10. Write the field. Do not inherit it.
Do not make the class default in the first YAML if the stem said not to. Graders read apply order. A single apply with true already set fails task 1 even if the cluster ends correct.
Do not edit a Deployment to add storageClassName: local-storage. The stem forbade it. Existing claims keep their class. The default only fires for new PVCs that omit the field.
kubectl get sc columns are the score sheet. NAME, PROVISIONER, RECLAIMPOLICY, VOLUMEBINDINGMODE, and the (default) mark. After task 1 you should see local-storage, rancher.io/local-path, whatever reclaim the API defaulted (often Delete), WaitForFirstConsumer, and no default mark. After task 3 the same row wears (default) and no other row does.
Reclaim on Q14 is not the grade. The stem did not name it. If you set reclaimPolicy: Retain on local-storage because today's theme is Retain, you added a field the grader did not ask for. It will probably still pass. It is not the point. The point is the default annotation and the binding mode. Leave reclaim alone unless the stem names it.
Provisioner spelling is a silent fail. rancher.io/local-path is the stem. kubernetes.io/no-provisioner is a different class of local volume. pd.csi.storage.gke.io is today's Ops overlay and will not provision on the exam cluster. Copy the stem.
§V — Worked pairing: leftover plus default
The two stems compose. A default class with Delete is how you lose the volume that should have survived. A Retain class that is not default is how you keep a class nobody uses.
On GKE, Poulton's cluster has standard as default, in-tree, Immediate, Delete (Ch. 10, p. 129). A MariaDB PVC that omits storageClassName lands there. Delete the PVC and the disk is gone. Q1's static PV avoids that by existing before the claim and by carrying Retain on the PV itself. Q14's work is to take the default-class gun off the dangerous class and put it on the class the stem named.
The day's Ops lesson is Poulton's sc-fast-repl: CSI, regional PD, WaitForFirstConsumer, Retain. That class is never default in the book. You name it on the PVC. The day's Dev lesson lists Released PVs and will not patch them. On the exam you are the patch. Type it on one named PV. Do not write a loop.
If a stem hands you Released and a missing PVC, order is:
kubectl get pvand read reclaim, class, claimRef, capacity, access mode.- Patch away
claimRefif reclaim is Retain and you have been asked to reuse. - Create the PVC with matching mode, capacity, class, and
volumeName. - Fill
claimNameon the workload. - Describe the Pod only after the PVC is Bound.
If you start at the Pod you will chase scheduling for a claim that is not Bound. 08-16 already spent a fire on that mistake in the other direction.
A last trap sits in empty string versus omitted field. Omitting storageClassName means "use the default." Setting storageClassName: "" means "bind a PV that also has an empty class." Q1's static PV often carries the default class name that LabSetUp copied from the cluster. If you write storageClassName: "" on the PVC and the PV has standard, there is no bind. If you omit the field, the default class must equal the PV's class. LabSetUp copied the default onto the PV so the omit path works. volumeName makes both paths less important. Use it.
§VI — Connection to today's Ops and Dev
Ops is GKE PD CSI and the leftover disk in Compute Engine. Dev is the Python list that prints Released plus claimRef and stops. This file is the hand version of both recoveries and the default-class patch pair.
CKA blueprint on the Cert slot is now 25 + 15 + 20 + 10 + 30. Architecture, Workloads, Services, Storage, Troubleshooting. The first pass is closed. Next K8s day 2026-08-25 reads counter 11, odd, and returns to CKS. Leftover CKS domains remain Cluster Hardening 15% and System Hardening 15%.
08-16 remains taints, requests, PriorityClass, HPA. 08-10 remains the four floors. 08-04 remains RBAC. 08-19 is CKS encryption and is not this domain.
§VII — Practice
kubectl get pv mariadb-pv shows Released and Retain. claimRef names mariadb/mariadb. The PVC is gone. What is the first command, and what happens if you skip it?/spec/claimRef. Skip it and the new PVC stays Pending. The binder treats the leftover pointer as a live claim.volumeName. It Bound to a different PV that also has 250Mi RWO. MariaDB is Running. Did you preserve the data?mariadb-pv. Pin volumeName: mariadb-pv and delete the wrong bind if the stem still allows it.storageClassName. The only default class is standard with Delete. The PV you were told to reuse has storageClassName: local-path. What happens?local-path, or set volumeName and confirm the notes on your cluster. Do not change the PV class unless the stem says to.local-storage with the default annotation already true. local-path is still default. The cluster now has two defaults. What did you fail, and what is the fix?local-storage true only after the first apply, then patch local-path false. kubectl get sc must show one (default).local-storage is the only default. A pre-existing PVC has no class field and is still Bound to an old PV. Did you violate "do not modify existing PVCs"?premium-rwo, Delete, WaitForFirstConsumer. You delete the PVC. kubectl get pv is empty. Where is the data?Time box the two stems the way the blueprint weights them. Storage is 10%. Q1 plus Q14 should not eat the Troubleshooting 30%. If the PVC is Bound and the Deployment is Available, leave the room. If kubectl get sc shows one default and the mode is WaitForFirstConsumer, leave the room. The leftover pointer is a two-line patch. The default class is a two-line patch. Both are shorter than a scheduler debug.
§VIII — Close
Storage 10% is two stems and one leftover. Q1 reuses the volume that survived the claim. Q14 decides which class the next claim will silently use. Read the PV before you write the PVC. Read kubectl get sc before you delete a default. The binder is literal. So is the grader.
Examine well. Pin the name. Patch the pointer. Leave one default.
Related
- Prior arc: CKA Workloads and Scheduling (2026-08-16)
- Domain hub: Cross-References/Certifications-Roadmap
- Grounding tome: CKA Q1 MariaDB Persistent Volume