Cloud AI Campus
  • Career paths
  • Learning paths
  • Hands-on Labs
Log in Sign up

๐Ÿงช Hands-on lab · 40 min

Persistent volumes and StatefulSets

  1. 1. Bind a PersistentVolumeClaim
  2. 2. Mount it in a pod and write to it
  3. 3. Verify the data survives pod deletion
  4. 4. Move to a StatefulSet
  5. 5. Stable pod names + per-pod identity

Bind a PersistentVolumeClaim

Containers are stateless by design. To keep data across restarts you need a PersistentVolume (PV) โ€” a piece of real storage โ€” and a PersistentVolumeClaim (PVC) โ€” a request for storage that pods can mount.

In production a cluster admin pre-provisions PVs, or a dynamic provisioner (the storage class) creates them on demand when a PVC is submitted. This sandbox runs k3s, which ships a built-in local-path provisioner. Confirm it:

kubectl get storageclass

local-path is marked (default).

Look at the PVC manifest:

cat storage/pvc-demo.yaml

It asks for 1Gi of ReadWriteOnce storage with no explicit storageClassName, so it lands on local-path.

Apply it:

kubectl apply -f storage/pvc-demo.yaml

Watch the binding:

kubectl get pvc

STATUS flips from Pending to Bound once the provisioner has created a PV and stitched them together. Look at the PV that just appeared:

kubectl get pv

Click Verify step when the PVC is Bound.

Hint

`kubectl apply -f storage/pvc-demo.yaml` requests 1Gi of ReadWriteOnce storage; k3s's local-path provisioner auto-creates a matching PV.

Mount it in a pod and write to it

A PVC by itself is just an intent. To actually use the storage, a pod has to mount it.

Look at the pod manifest:

cat storage/pod-volume-demo.yaml

The volumes: section references the PVC by name; the volumeMounts: section attaches it inside the nginx container at /var/www/html.

Apply:

kubectl apply -f storage/pod-volume-demo.yaml

Watch it become Ready (this also triggers the local-path provisioner to finally create the PV):

kubectl get pvc,pod

Once pvc-demo-pod is Running, write a file into the mounted volume:

kubectl exec pvc-demo-pod -- sh -c 'echo "Persistent storage works" > /var/www/html/index.html'

Read it back through nginx (the volume is mounted at the nginx docroot):

kubectl exec pvc-demo-pod -- curl -s http://127.0.0.1/

You should see your message.

Click Verify step.

Hint

`kubectl apply -f storage/pod-volume-demo.yaml`, then `kubectl exec pvc-demo-pod -- sh -c 'echo hello > /var/www/html/index.html'`.

Verify the data survives pod deletion

The whole point of a PV: the data outlives any individual pod.

Delete the pod:

kubectl delete pod pvc-demo-pod

Wait for it to be gone:

kubectl get pods

The PVC, however, still exists and is still bound to the same PV:

kubectl get pvc,pv

Recreate the pod (same manifest, same PVC reference):

kubectl apply -f storage/pod-volume-demo.yaml

Wait for it to be Running:

kubectl wait --for=condition=Ready pod/pvc-demo-pod --timeout=60s

Read the file you wrote in step 2:

kubectl exec pvc-demo-pod -- cat /var/www/html/index.html

The same message comes back. The pod was destroyed; the data wasn't.

Click Verify step.

Hint

Delete the pod, recreate it, then `kubectl exec` and `cat /var/www/html/index.html`. The file should still be there.

Move to a StatefulSet

A Deployment treats its pods as interchangeable cattle. For storage systems โ€” databases, queues, file servers โ€” you usually need stable identity: each replica should have a predictable name and its own dedicated storage. That's a StatefulSet.

First, free up the PVC by removing the pod that owns it:

kubectl delete pod pvc-demo-pod

Now apply the StatefulSet:

cat storage/statefulset-demo.yaml
kubectl apply -f storage/statefulset-demo.yaml

Watch the pods come up:

kubectl get pods -l app=MyApp -w

(Ctrl-C to stop watching.)

Two things to notice:

  1. Sequential names: statefulset-demo-0, statefulset-demo-1, statefulset-demo-2. Not random hashes.
  2. One PVC per pod: from the volumeClaimTemplates field.
kubectl get pvc

You'll see hello-web-disk-statefulset-demo-0, hello-web-disk-statefulset-demo-1, hello-web-disk-statefulset-demo-2.

Click Verify step when all three StatefulSet pods are Running.

Hint

`kubectl apply -f storage/statefulset-demo.yaml` creates three ordered pods, each with its own PVC.

Stable pod names + per-pod identity

Each StatefulSet pod has its own PVC. Storage is not shared across replicas โ€” each one is private.

Write a unique file into pod 0 only:

kubectl exec statefulset-demo-0 -- sh -c 'echo "hi from pod 0" > /var/www/html/index.html'

Read it back:

kubectl exec statefulset-demo-0 -- cat /var/www/html/index.html

Now look at pod 1 and pod 2 โ€” they should have empty docroots:

kubectl exec statefulset-demo-1 -- ls -la /var/www/html
kubectl exec statefulset-demo-2 -- ls -la /var/www/html

Each pod's /var/www/html is its own PVC's content, not pod 0's.

Test the rebuild guarantee. Delete pod 0:

kubectl delete pod statefulset-demo-0

The StatefulSet recreates a pod with the same name:

kubectl get pods -l app=MyApp

When statefulset-demo-0 is Running again, read the file:

kubectl exec statefulset-demo-0 -- cat /var/www/html/index.html

hi from pod 0 โ€” the new pod attached to the same PVC and the data was waiting for it.

Real-world: this is how stateful databases survive node failures. The replica that picks up db-0's identity (and PVC) thinks it is db-0 and continues from its on-disk state.

Click Verify step.

Hint

`kubectl exec statefulset-demo-0 -- sh -c 'echo hi from 0 > /var/www/html/index.html'` โ€” the file is private to pod 0's PVC.

© 2026 Cloud AI Campus