Kubernetes Storage: PVC, PV and StorageClass
Prove a container's filesystem is disposable, then attach storage that survives, and meet the access mode that blocks a rollout.
- Time
- 50 min
- Level
- Intermediate
- Objectives
- 4 objectives
- Cost
- Free
Where this fits in the platform
Already built
This lab adds
- A volume that outlives the pod using it
Which lets you
Before you start
You will need
- kind or minikube
- kubectl 1.28+
You do not need these already — the lab environment below provides them.
You will be able to
- Explain the StorageClass → PV → PVC chain and who creates what
- Recognise why ReadWriteOnce blocks a multi-replica Deployment
- Know what `reclaimPolicy` does to your data
Cost — Free
on kind or minikube. On a cloud cluster each PVC provisions a real disk billed per GB-month — see cleanup.
Nothing to pay in the browser. Open the terminal runs this against a simulated cloud — the same API calls and the same commands, with no account and no bill. The figure above applies only if you build it in your own.
The scenario#
A container's filesystem dies with the container. Most workloads do not care; a database very much does.
This lab shows the difference concretely, then walks into the access mode that stops a Deployment scaling — which is one of the more confusing first encounters with Kubernetes storage.
This lab deletes Pods and volumes on purpose. Run it on a throwaway cluster, never against anything holding data you need.
Hands-on environment
Run this lab in a real terminal, free and in your browser. The environment is temporary and yours alone — break it as much as you like.
Open the terminalOpens in Killercoda, in a new tab — keep this page open for the steps.
Run it on your own machine
Run this lab on your own machine. One command starts the environment, with everything the lab needs already installed:
You will need:
- docker
- kubectl
- kind
git clone https://github.com/EgyKode/EgyKode-lab.git
cd EgyKode-lab
./egykode start k8s
./egykode shellYou need Docker and Git installed. Everything else runs inside the environment. The first start downloads it and takes a few minutes; later starts are seconds.
Not sure what you already have? Run: npm run doctor — it checks and changes nothing.
Anything you tick here is your own record. EgyKode cannot see inside that terminal, so the success criteria stay self-assessed even when the environment checks your work for you.
Prove the filesystem is disposable
Step 1 of 5
What you are proving: You can demonstrate that a container filesystem is disposable rather than assume it
Marking this settles success criterion 1.
Destructive — This removes real resources. Check which environment you are in first.
kubectl run scratch --image=busybox --restart=Never -- sh -c "sleep 3600"
kubectl exec scratch -- sh -c "echo 'important' > /data.txt; cat /data.txt"
kubectl delete pod scratch
kubectl run scratch --image=busybox --restart=Never -- sh -c "sleep 3600"
kubectl exec scratch -- cat /data.txt # No such fileThe writable layer belongs to the container, and it goes when the container does.
What you are proving: You can name the three storage objects and what each one is responsible for
This step settles no success criterion on its own.
| Object | Says | Created by |
|---|---|---|
StorageClass | how to provision — which disk type | The platform team, once |
PersistentVolume | a specific piece of storage that exists | Usually automatically |
PersistentVolumeClaim | "I need 1Gi" | The application author |
kubectl get storageclassA Pod references a PVC; the PVC binds to a PV; the PV is created on demand from the StorageClass. Application authors write only the middle one.
What you are proving: You can mount a claim and show the same data surviving a Pod deletion
Marking this settles success criterion 2.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: dataDestructive — This removes real resources. Check which environment you are in first.
kubectl apply -f storage.yaml
kubectl get pvc,pv
kubectl exec writer -- sh -c "echo 'survives' > /data/file.txt"
kubectl delete pod writer
kubectl apply -f storage.yaml
kubectl exec writer -- cat /data/file.txt # survivesWhat you are proving: You can explain why a three-replica Deployment on one ReadWriteOnce claim leaves Pods Pending
Marking this settles success criterion 3.
kubectl create deployment web --image=nginx --replicas=3
# then patch it to mount the same ReadWriteOnce PVC
kubectl get podsSome Pods stay Pending. kubectl describe pod <name> says the volume is
already attached elsewhere.
ReadWriteOnce means one node may mount it — which is what a cloud block
device physically is. Three replicas spread across nodes cannot share one. The
options:
ReadWriteOnce+ one replica — correct for most databases.- A StatefulSet with
volumeClaimTemplates— each replica gets its own volume. ReadWriteMany— needs a file system such as NFS or EFS, not a block device.
What you are proving: You can state what happens to the underlying disk when a claim is deleted, for your StorageClass
Marking this settles success criterion 4.
kubectl get pv -o custom-columns=NAME:.metadata.name,RECLAIM:.spec.persistentVolumeReclaimPolicyDelete— the default on most cloud StorageClasses. Deleting the PVC destroys the underlying disk and the data on it.Retain— the PV survives inReleasedstate for manual recovery, and keeps billing until you remove it.
Check which one your cluster uses before you need to know.
Two behaviours worth remembering:
- Deleting a StatefulSet does not delete its PVCs. This protects your data, and it means a "clean" reinstall silently reuses the old disks.
helm uninstalldoes not remove PVCs either — which is how a deleted monitoring stack keeps billing for volumes nobody can see.
PVC stays Pending
No StorageClass can satisfy it. kubectl describe pvc names the reason — often no default StorageClass, or a class that provisions nothing on this cluster.
Pods Pending with 'volume is already exclusively attached'
A ReadWriteOnce volume with more than one replica. Scale to 1, or move to a StatefulSet with volumeClaimTemplates.
Data vanished after deleting the PVC
The reclaim policy was Delete. That is the default and it is working as designed — check before deleting, not after.
PV stuck Terminating
A finalizer is waiting on something still using it. Confirm no Pod mounts it, then inspect kubectl get pv <name> -o yaml for the finalizer.
Clean up#
Run this even if you did not finish.
Destructive — This removes real resources. Check which environment you are in first.
kubectl delete deployment,pod --all
kubectl delete pvc --all # PVCs are NOT removed with the workload
kubectl get pv # confirm nothing is Released and lingering
# On a cloud cluster, unattached volumes keep billing:
aws ec2 describe-volumes --filters Name=status,Values=available --query 'Volumes[].[VolumeId,Size]' --output tableCost of this lab: Free on kind or minikube. On a cloud cluster each PVC provisions a real disk billed per GB-month — see cleanup.
Success criteria
0 of 4
The concept behind it
Next up
Lab 34 of 59 on the project path