Home › CKA › Topics › Practice tasks › Persistent Volumes & Claims
CKA · Storage

Persistent Volumes & Claims

A MariaDB Deployment in namespace mariadb was deleted by mistake. A retained PV named mariadb-pv (300Mi, storageClass: standard) already exists.

Check live lab access → All CKA tasks

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

A MariaDB Deployment in namespace mariadb was deleted by mistake. A retained PV named mariadb-pv (300Mi, storageClass: standard) already exists.

Task 1: Create a PVC in namespace mariadb:

Task 2: Update ~/mariadb-deployment.yaml to mount the PVC at /var/lib/mysql and apply it.

Original Prepium workshop · Tested on Kubernetes v1.35.0 on 1 October 2026 in a three-node kind cluster whose default StorageClass is standard. Use a disposable cluster. The example PersistentVolume is a hostPath volume, which is fine for learning binding rules but stores data on one node only.

Bind a PersistentVolumeClaim to one specific PersistentVolume, and reuse it after a Retain

A PersistentVolume (PV) is cluster-scoped storage; a PersistentVolumeClaim (PVC) is a namespaced request for storage. The control plane binds a claim to a volume only when the StorageClass name, the access modes and the size are all compatible. Setting spec.volumeName on the claim pins it to one named PV, but it does not relax any of those rules.

PV archive-pv (1Gi, RWO, class manual, Retain)  ←── bound ───  PVC archive-claim (500Mi, RWO, class manual)
                                                                  ↑ mounted by Pod archive-reader at /data

1. Create a static PV

Save this as pv-static.yaml. The manual class does not need to exist as a StorageClass object; for static volumes it is just a label that claims must repeat.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: archive-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /tmp/prepium-archive
    type: DirectoryOrCreate
kubectl create namespace prepium-tutorial
kubectl apply -f pv-static.yaml
kubectl get pv archive-pv

The PV should be Available, with no claim.

2. Make the classic mistake first

Create a claim that names the volume but leaves out storageClassName:

cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: wrong-class, namespace: prepium-tutorial}
spec:
  volumeName: archive-pv
  accessModes: [ReadWriteOnce]
  resources: {requests: {storage: 500Mi}}
EOF
kubectl -n prepium-tutorial get pvc wrong-class -o jsonpath='{.spec.storageClassName}{"\n"}'
kubectl -n prepium-tutorial describe pvc wrong-class

The claim comes back with storageClassName: standard even though you never wrote it. When a cluster has a default StorageClass, the DefaultStorageClass admission plugin fills the field in for any claim that omits it. The claim then stays Pending with VolumeMismatch: Cannot bind to requested volume "archive-pv": storageClassName does not match. Omitting the field is not the same as matching a PV that has a class. Write the PV's class explicitly (or storageClassName: "" for a PV that has none).

kubectl -n prepium-tutorial delete pvc wrong-class

3. Bind correctly and mount it

Save this as pvc-archive.yaml. It contains the claim and a Pod that appends a timestamp to a file on the volume.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: archive-claim
  namespace: prepium-tutorial
spec:
  storageClassName: manual
  volumeName: archive-pv
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
---
apiVersion: v1
kind: Pod
metadata:
  name: archive-reader
  namespace: prepium-tutorial
spec:
  containers:
    - name: shell
      image: busybox:1.36
      command: ["/bin/sh", "-c", "date >> /data/visits.txt; sleep 3600"]
      volumeMounts:
        - name: archive
          mountPath: /data
  volumes:
    - name: archive
      persistentVolumeClaim:
        claimName: archive-claim
kubectl apply -f pvc-archive.yaml
kubectl -n prepium-tutorial get pvc archive-claim
kubectl -n prepium-tutorial wait --for=condition=Ready pod/archive-reader
kubectl -n prepium-tutorial exec archive-reader -- cat /data/visits.txt

The claim is Bound and its CAPACITY column says 1Gi, not the 500Mi you asked for. Binding is one-to-one: a claim gets the whole volume, and the request is only a minimum.

4. Delete the claim and reuse the retained volume

kubectl -n prepium-tutorial delete pod archive-reader
kubectl -n prepium-tutorial delete pvc archive-claim
kubectl get pv archive-pv

With persistentVolumeReclaimPolicy: Retain the data is kept and the PV moves to Released. Its CLAIM column still shows prepium-tutorial/archive-claim. Apply pvc-archive.yaml again and the new claim stays Pending: the PV still holds a claimRef with the old claim's UID, so it refuses a new claim even one with the same name.

kubectl get pv archive-pv -o jsonpath='{.spec.claimRef.uid}{"\n"}'
kubectl patch pv archive-pv --type=json -p '[{"op":"remove","path":"/spec/claimRef"}]'
kubectl -n prepium-tutorial get pvc archive-claim

Removing the stale claimRef returns the PV to Available, and the waiting claim binds within seconds. Clearing it is a deliberate administrator action: you are confirming that whoever owned the old data is finished with it. In our run the reader was scheduled onto the same node again and printed both timestamps; with hostPath on a different node you would see a fresh file, which is why real clusters use network or CSI volumes for data that must follow a Pod.

Example checkEvidence
Claim bound to the intended volumePVC VOLUME column names the PV
Class and mode matchSame storageClassName and access mode on PV and PVC
Workload uses the claimPod volume references the claim and the mount path is readable

Cleanup: kubectl delete namespace prepium-tutorial, then kubectl delete pv archive-pv (a PV is not namespaced, so deleting the namespace leaves it behind). Reference: Persistent Volumes. Next, try StorageClasses and dynamic provisioning.

Exam
CKA
Domain
Storage
Grading
Programmatic · partial credit

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.

Related CKA tasks