K8s Storage Basics
This section covers the core Kubernetes storage objects - StorageClasses, PersistentVolumeClaims, and PersistentVolumes - through small, runnable manifests you can apply to any cluster.
Prerequisites
- A cluster running Kubernetes 1.36.2 with a CSI driver installed (EKS ships the EBS CSI driver, GKE ships PD CSI, AKS ships Azure Disk CSI).
kubectlmatching your control plane minor version.- A default StorageClass, or a named one you can reference. Check with the command below.
kubectl get storageclassBasic Examples
1. Inspect the default StorageClass
Every dynamic PVC without an explicit class uses the one marked default.
kubectl get storageclass -o wide- The class flagged
(default)backs any PVC that omitsstorageClassName. PROVISIONERshows which CSI driver fulfills claims, for exampleebs.csi.aws.com.RECLAIMPOLICYofDeletemeans the disk is destroyed with the PVC.- If two classes are marked default, PVC creation becomes ambiguous and should be fixed.
2. Create a simple PVC
A PVC is the request an application makes for storage.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10GiaccessModesofReadWriteOncemeans a single node mounts it read-write.requests.storageis the minimum capacity; the driver may round up.- Omitting
storageClassNameuses the default class. - Applying this leaves the PVC
Pendinguntil a Pod consumes it, if the class usesWaitForFirstConsumer.
3. Watch a claim get bound
Binding is where the request meets a real disk.
kubectl apply -f pvc.yaml
kubectl get pvc data -w- The claim moves from
PendingtoBoundonce a PV is provisioned and matched. - The
VOLUMEcolumn shows the auto-generated PV name. -wstreams status changes so you see the transition live.- A claim stuck in
Pendingalmost always means no provisioner or no schedulable consumer.
4. Mount a PVC into a Pod
Storage is only provisioned and mounted when a Pod references the claim.
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo hi > /data/file && sleep 3600"]
volumeMounts:
- name: vol
mountPath: /data
volumes:
- name: vol
persistentVolumeClaim:
claimName: datavolumesnames the PVC;volumeMountsplaces it at a path in the container.- The file written to
/datasurvives Pod restarts because it lives on the PV. mountPathmust be an absolute path inside the container.- The Pod schedules only onto a node where the volume can attach.
5. Confirm data persists across restarts
Persistence is the entire point, so prove it.
kubectl delete pod writer
kubectl apply -f writer.yaml
kubectl exec writer -- cat /data/file- Deleting the Pod detaches but does not delete the volume.
- Recreating the Pod reattaches the same PVC and PV.
- The
catshows the earlier content, confirming durability. - The PVC and PV are untouched by Pod deletion.
6. Read a PersistentVolume
The PV is the cluster-side record of the real disk.
kubectl get pv
kubectl describe pv <name>describeshows capacity, access modes, reclaim policy, and the backing volume handle.Claimlinks back to the PVC that owns it.StorageClassrecords which class provisioned it.- The
Sourcesection names the CSI driver and vendor volume ID.
7. Choose a named StorageClass
Explicitly selecting a class avoids relying on the default.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fast-data
spec:
storageClassName: fast-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20GistorageClassNamepins the tier so the claim is never at the mercy of the cluster default.- Different classes map to different disk types and performance profiles.
- A non-existent class name leaves the PVC
Pendingforever. - Teams should be given a short menu of approved classes.
Intermediate Examples
8. Define a custom StorageClass
A StorageClass encodes provisioning parameters for a disk tier.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "250"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Deleteparametersare driver-specific; here they set an AWS gp3 disk with fixed IOPS and throughput.WaitForFirstConsumerdelays provisioning so the disk lands in the Pod's zone.allowVolumeExpansion: truepermits later growth of bound PVCs.reclaimPolicy: Deletecleans up the disk with the claim.
9. Expand a PVC online
Most CSI drivers grow volumes without downtime.
kubectl patch pvc data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl get pvc data- Expansion requires
allowVolumeExpansion: trueon the class. - The driver grows the disk, then the filesystem is resized, sometimes on next mount.
- Shrinking a PVC is never supported.
- Watch the
CAPACITYcolumn to confirm the new size.
10. Give each replica its own volume
StatefulSets template a PVC per Pod for clustered data.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cache
spec:
serviceName: cache
replicas: 3
selector:
matchLabels: { app: cache }
template:
metadata:
labels: { app: cache }
spec:
containers:
- name: redis
image: redis:7
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi- Each replica gets a stable PVC named
data-cache-0,data-cache-1, and so on. - A rescheduled Pod reclaims its exact prior volume.
- Scaling down leaves the PVCs behind by default, protecting data.
- This is the standard shape for databases and stateful caches.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).