CKA — Storage Lab

6 minute read

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)
  • kubectl configured 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 hostPath and no-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 the kubectl patch command and the allowVolumeExpansion requirement.

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.

Updated: