CKA — Storage Lab
Lab Overview
In this hands-on lab, you will practice the core storage tasks that appear on the CKA exam. By the end, you will have created PersistentVolumes, PersistentVolumeClaims, StorageClasses, mounted volumes in Pods, tested access modes, and expanded PVCs.
What you need:
- A running Kubernetes cluster (minikube, kind, or kubeadm)
kubectlconfigured and working- Cluster admin access
Time estimate: 30–40 minutes
Task 1: Create a PersistentVolume
Create a PV with the following specifications:
- Name:
pv-lab - Capacity: 5Gi
- Access mode: ReadWriteOnce
- Reclaim policy: Retain
- StorageClass:
manual - hostPath:
/data/pv-lab
Step 1: Create the YAML file:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-lab
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /data/pv-lab
Step 2: Apply it:
kubectl apply -f pv-lab.yaml
Step 3: Verify:
kubectl get pv pv-lab
Expected Output
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-lab 5Gi RWO Retain Available manual 5s
The PV should be in Available status — ready to be claimed.
Task 2: Create a PersistentVolumeClaim
Create a PVC that binds to the PV you just created:
- Name:
pvc-lab - Access mode: ReadWriteOnce
- Storage request: 3Gi
- StorageClass:
manual
Step 1: Create the YAML file:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-lab
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
storageClassName: manual
Step 2: Apply it:
kubectl apply -f pvc-lab.yaml
Step 3: Verify binding:
kubectl get pvc pvc-lab
kubectl get pv pv-lab
Expected Output
PVC output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-lab Bound pv-lab 5Gi RWO manual 5s
PV output:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-lab 5Gi RWO Retain Bound default/pvc-lab manual 60s
Both should show Bound status. Notice the PV’s CLAIM column shows default/pvc-lab.
Task 3: Mount PVC in a Pod
Create a Pod that uses the PVC, writes data, and verify the data persists.
Step 1: Create a Pod that writes data:
apiVersion: v1
kind: Pod
metadata:
name: writer-pod
spec:
containers:
- name: writer
image: busybox
command: ["sh", "-c", "echo 'Hello from CKA Storage Lab!' > /data/message.txt && sleep 3600"]
volumeMounts:
- mountPath: /data
name: lab-storage
volumes:
- name: lab-storage
persistentVolumeClaim:
claimName: pvc-lab
Step 2: Apply and wait for the Pod to be running:
kubectl apply -f writer-pod.yaml
kubectl get pod writer-pod -w
Step 3: Verify the data was written:
kubectl exec writer-pod -- cat /data/message.txt
Expected Output
Hello from CKA Storage Lab!
Step 4: Delete the Pod and create a reader to verify persistence:
kubectl delete pod writer-pod
apiVersion: v1
kind: Pod
metadata:
name: reader-pod
spec:
containers:
- name: reader
image: busybox
command: ["sh", "-c", "cat /data/message.txt && sleep 3600"]
volumeMounts:
- mountPath: /data
name: lab-storage
volumes:
- name: lab-storage
persistentVolumeClaim:
claimName: pvc-lab
kubectl apply -f reader-pod.yaml
kubectl logs reader-pod
Expected Output
Hello from CKA Storage Lab!
The data persisted even after the original Pod was deleted — this is the power of PersistentVolumes.
Clean up:
kubectl delete pod reader-pod
Task 4: StorageClass
Create a StorageClass and use it with a PVC.
Step 1: Create a StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-fast
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
kubectl apply -f local-fast-sc.yaml
Step 2: Create a PV using this StorageClass:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-fast
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-fast
hostPath:
path: /data/pv-fast
kubectl apply -f pv-fast.yaml
Step 3: Create a PVC referencing the StorageClass:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-fast
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-fast
kubectl apply -f pvc-fast.yaml
Step 4: Check the PVC status:
kubectl get pvc pvc-fast
Expected Output
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-fast Pending local-fast 5s
The PVC is in Pending state because the volumeBindingMode is WaitForFirstConsumer — it will not bind until a Pod uses it.
Step 5: Create a Pod that uses this PVC:
apiVersion: v1
kind: Pod
metadata:
name: fast-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: fast-storage
volumes:
- name: fast-storage
persistentVolumeClaim:
claimName: pvc-fast
kubectl apply -f fast-pod.yaml
Step 6: Verify binding now happens:
kubectl get pvc pvc-fast
kubectl get pv pv-fast
Expected Output
PVC output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-fast Bound pv-fast 2Gi RWO local-fast 30s
The PVC is now Bound because a Pod triggered the binding.
Task 5: Access Modes
Test different access modes to understand their behavior.
Step 1: Create a PV with ReadWriteOnce:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rwo
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /data/pv-rwo
Step 2: Create a matching PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-rwo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: manual
kubectl apply -f pv-rwo.yaml
kubectl apply -f pvc-rwo.yaml
Step 3: Create two Pods on the same node using this PVC:
apiVersion: v1
kind: Pod
metadata:
name: rwo-pod-1
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "echo pod1 > /data/pod1.txt && sleep 3600"]
volumeMounts:
- mountPath: /data
name: shared
volumes:
- name: shared
persistentVolumeClaim:
claimName: pvc-rwo
---
apiVersion: v1
kind: Pod
metadata:
name: rwo-pod-2
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "cat /data/pod1.txt && sleep 3600"]
volumeMounts:
- mountPath: /data
name: shared
volumes:
- name: shared
persistentVolumeClaim:
claimName: pvc-rwo
kubectl apply -f rwo-pods.yaml
Step 4: Verify both Pods can access the data (on a single-node cluster):
kubectl exec rwo-pod-1 -- cat /data/pod1.txt
kubectl logs rwo-pod-2
Expected Output
pod1
Both Pods can read the data because RWO allows multiple Pods on the same node. Remember, RWO restricts to a single node, not a single Pod.
Step 5: Check the access mode in PV output:
kubectl get pv pv-rwo
Expected Output
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-rwo 1Gi RWO Retain Bound default/pvc-rwo manual 60s
The RWO abbreviation appears in the ACCESS MODES column.
Task 6: Volume Expansion
Expand an existing PVC to a larger size.
Step 1: Create a StorageClass that allows expansion:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: expandable
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
kubectl apply -f expandable-sc.yaml
Step 2: Create a PV and PVC using this class:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-expand
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: expandable
hostPath:
path: /data/pv-expand
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-expand
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: expandable
kubectl apply -f pv-pvc-expand.yaml
Step 3: Create a Pod to trigger binding (WaitForFirstConsumer):
apiVersion: v1
kind: Pod
metadata:
name: expand-pod
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- mountPath: /data
name: expandable-storage
volumes:
- name: expandable-storage
persistentVolumeClaim:
claimName: pvc-expand
kubectl apply -f expand-pod.yaml
Step 4: Check current PVC size:
kubectl get pvc pvc-expand
Expected Output
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-expand Bound pv-expand 10Gi RWO expandable 30s
Step 5: Expand the PVC from 2Gi to 5Gi:
kubectl patch pvc pvc-expand -p '{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
Step 6: Verify the expansion:
kubectl get pvc pvc-expand
kubectl describe pvc pvc-expand | grep -i capacity
Expected Output
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-expand Bound pv-expand 10Gi RWO expandable 2m
Note: With
hostPathandno-provisioner, the actual underlying storage does not resize dynamically. In a real cluster with a CSI driver, the capacity would update to reflect the new request. The key CKA skill is knowing thekubectl patchcommand and theallowVolumeExpansionrequirement.
Cleanup
Remove all resources created in this lab:
# Delete Pods
kubectl delete pod writer-pod reader-pod fast-pod rwo-pod-1 rwo-pod-2 expand-pod --ignore-not-found
# Delete PVCs
kubectl delete pvc pvc-lab pvc-fast pvc-rwo pvc-expand --ignore-not-found
# Delete PVs
kubectl delete pv pv-lab pv-fast pv-rwo pv-expand --ignore-not-found
# Delete StorageClasses
kubectl delete sc local-fast expandable --ignore-not-found
Verify everything is cleaned up:
kubectl get pv,pvc,sc,pod
Expected Output
No resources found
Or only default cluster resources should remain.
Congratulations! You have completed the Storage lab. You practiced creating PVs, PVCs, StorageClasses, mounting volumes, testing access modes, and expanding PVCs — all skills that are tested on the CKA exam.