PersistentVolumes and PersistentVolumeClaims

PersistentVolumes and claims: the fields that decide whether a claim binds, access modes, reclaim policies, and how to reuse a Released volume.

45 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 hostPath PersistentVolume and a matching claim, mount the claim in a Pod and check that they are bound.
  • Read a Pending claim and create exactly the PersistentVolume it needs.
  • Explain Retain and Delete, spot a Released volume and make it usable again without losing its data.

Your database needs a disk that survives restarts, and even the deletion of its . A is that disk, and a is how a Pod asks for it. This lesson shows both, and the few fields that must match between them.

The exam asks about them in three ways, and the three tasks at the end practice each one:

  • Create a PV, a claim and a Pod that uses the claim.
  • Make a waiting (Pending) claim bind.
  • Reuse a volume after its claim was deleted.

PV, PVC and StorageClass: three names, three jobs

These three names look alike and are easy to mix up:

NameWhat it isWho makes itExample
PersistentVolume (PV)A piece of real storage: a disk or a folder. Cluster-wide, no .An admin by hand, or a automaticallyst-data-pv: 1Gi in /opt/st-data
PersistentVolumeClaim (PVC)A request for storage: a size and an access mode. Lives in a namespace.The app team, next to the appdata-claim: "I need 1Gi I can write to"
StorageClassA recipe that creates PVs when claims ask for themAn admin, oncestandard on the lab (next lesson)
How a Pod gets persistent storage

Pod

mounts the claim by name

claimName: data-claim

PersistentVolumeClaim

the request: 1Gi, read-write

data-claim

PersistentVolume

the real storage

st-data-pv

The Pod names the claim, and the claim is bound to one PV. The Pod never names the PV.

The PersistentVolume

A PersistentVolume (PV) describes one piece of storage: its size, how it may be used, and where it lives. An admin can create PVs by hand, which is called static provisioning. Or a StorageClass creates them on demand, which is dynamic provisioning (next lesson).

A static PersistentVolume backed by a folder on the node
apiVersion: v1
kind: PersistentVolume
metadata:
  name: st-data-pv
  labels:
    tier: data                  # lets a claim select this PV by label
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: st-manual
  hostPath:
    path: /opt/st-data
    type: DirectoryOrCreate

The fields to know:

  • capacity.storage: the size. Claims are compared against it.
  • storageClassName: on a PV you make by hand, this is just a name to match. No StorageClass with that name has to exist, but the claim must use the same string.
  • The source, here hostPath: where the data really is. Other sources are local, nfs and csi.

A local PV is the stricter cousin of hostPath. It names the one that has the disk, in nodeAffinity. So every Pod that uses it runs on that node.

The PersistentVolumeClaim

A PersistentVolumeClaim (PVC) is a request for storage: a size and an access mode. The app team writes it in the app's namespace. Kubernetes then binds the claim to one PV.

Why the extra step? The Pod names the claim, never the PV. So the same Pod works with a folder on a lab node or a disk in a cloud. Only the PV changes.

A claim that picks the PV above by its label
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-claim
  namespace: st-pv
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  storageClassName: st-manual
  resources:
    requests:
      storage: 1Gi
  selector:
    matchLabels:
      tier: data
A Pod that mounts the claim
apiVersion: v1
kind: Pod
metadata:
  name: data-pod
  namespace: st-pv
spec:
  containers:
    - name: web
      image: nginx:1.27
      volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data-claim

Access modes

An access mode says how many nodes may mount the volume at once, and whether they may write to it.

ModeShortMeaning
ReadWriteOnceRWORead-write from one node. Several Pods on that same node may share it.
ReadOnlyManyROXRead-only from many nodes.
ReadWriteManyRWXRead-write from many nodes. Needs shared storage, such as NFS or a cloud file share.
ReadWriteOncePodRWOPRead-write from exactly one Pod in the whole cluster. Only for volumes, and never mixed with another mode.
  • A PV lists every mode it supports. A claim lists the modes it needs, and each one must be on the PV.
  • Modes are a matching rule, not a lock. A hostPath PV may say ReadWriteMany, yet nothing makes two nodes see the same folder.
  • On the exam, translate the words: "one node" means ReadWriteOnce, and "many nodes" means ReadWriteMany.

Reclaim policies

The reclaim policy, persistentVolumeReclaimPolicy, decides what happens to a PV when its claim is deleted:

PolicyWhat happens when the claim is deletedUse it when
RetainThe PV and its data stay. The PV becomes Released, and an admin decides what to do with it.The data matters
DeleteThe PV is deleted, and the storage behind it too.The data can be thrown away
  • A PV you create by hand gets Retain if you leave the field out. A PV made by a StorageClass gets the class's policy, as the next lesson shows.
  • You can change the policy on a live PV. To keep the data if the claim is deleted: kubectl patch pv <name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'.

How a claim finds its PV

Kubernetes binds a claim to a PV only when every check below passes. If one fails, the claim stays Pending.

  • Size: the PV's capacity.storage is at least the claim's requests.storage. Kubernetes picks the smallest PV that fits. So a 1Gi claim may get a 5Gi PV, and then it shows 5Gi.
  • Access modes: every mode the claim asks for is on the PV.
  • Class: storageClassName is the same string on both. A claim that leaves it out gets the default class filled in. On this lab that is standard, which makes a brand-new volume instead of using yours. A claim with storageClassName: "" binds only to PVs that have no class.
  • Selector: if the claim has one, the PV's must match it. A claim with a never gets a new volume from a StorageClass.
  • Free: the PV is Available, not bound to another claim. A claim can also ask for one PV by name, with spec.volumeName.

Once bound, the pair stays bound until the claim is deleted. When a claim stays Pending, run kubectl describe pvc <name>. Its events say why. The fix is almost always a PV with the right class, size and access modes.

PV phases and reusing a Released volume

The STATUS column of kubectl get pv shows a PV's phase:

PhaseMeaningWhat to do
AvailableFree. No claim is bound to it.Nothing, or create a matching claim.
BoundBound to one claim, named in spec.claimRef.Mount the claim from a Pod.
ReleasedThe claim was deleted. With Retain, the data and the old claimRef are still there.Clear claimRef to reuse it, or delete the PV.
FailedKubernetes could not delete the storage automatically.Read the events, then fix or delete it by hand.
The life of a Retain PV

Available

free, waiting for a claim

Bound

in use by one claim

Released

claim deleted, data kept

Available again

after you clear claimRef

claimRef: null

With Delete instead, the PV and its storage are removed when the claim goes, so there is no Released step.

A Released PV still remembers its old claim in spec.claimRef, so no new claim can bind to it. Remove that reference and the PV is free again. The data on it is not touched.

Bring a Released Retain volume back and let a waiting claim bind
kubectl get pv st-keep-pv                      # STATUS Released, CLAIM still shows the old claim
kubectl get pv st-keep-pv -o jsonpath='{.spec.claimRef}'

# Drop the reference to the deleted claim. The data on the volume is untouched.
kubectl patch pv st-keep-pv -p '{"spec":{"claimRef":null}}'
# Interactive alternative: kubectl edit pv st-keep-pv and delete the whole claimRef: block.

kubectl get pv st-keep-pv                      # Available
# A Pending claim that matches is picked up on the controller's next pass (within ~15s).
kubectl -n st-pv wait --for=jsonpath='{.status.phase}'=Bound pvc/keep-claim-v2 --timeout=90s

In the official docs· open on exam day too

Hands-on task 1 of 3st-pv-1
~8 min5% weightStorage

A hostPath PV, a claim and a Pod that mounts it

Create a PersistentVolume named st-data-pv with 1Gi of capacity, access mode ReadWriteOnce, reclaim policy Retain and storage class name st-manual, backed by the host directory /opt/st-data (create it if it does not exist). In namespace st-pv create a PersistentVolumeClaim named data-claim that requests 1Gi from that volume, and a Pod named data-pod using image nginx:1.27 that mounts the claim at /usr/share/nginx/html. The claim must be Bound to st-data-pv and the Pod must be running.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

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

Make a Pending claim bind

The PersistentVolumeClaim logs-claim in namespace st-pv has been Pending since it was created. Create a PersistentVolume named st-logs-pv that satisfies it, backed by the host directory /opt/st-logs-pv and with reclaim policy Retain. Do not modify or recreate the claim. When you are done the claim must be Bound to st-logs-pv.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-pv-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-pv-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-pv-2/cleanup | bash
Hands-on task 3 of 3st-pv-3
~5 min4% weightStorage

Reuse a Released Retain volume

The PersistentVolume st-keep-pv belonged to a claim that has since been deleted. It still holds data that must be kept, and it is now in phase Released. A new claim keep-claim-v2 in namespace st-pv is waiting for it. Make st-keep-pv bindable again so that keep-claim-v2 binds to it. Do not delete or recreate the PersistentVolume, and keep its reclaim policy.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/st-pv-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-pv-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-pv-3/cleanup | bash

Finished this lesson?

0 of 3 tasks passed. Your readiness counts tasks, so run them before you move on.