TL;DR (Quick Summary)#
- PersistentVolume (PV): A cluster-wide storage resource provisioned by an administrator or dynamically created by a CSI plugin (AWS EBS, GCP PD, NFS).
- PersistentVolumeClaim (PVC): A request for storage by a user/Pod (specifying size, access modes, and StorageClass).
- StorageClass: Enables dynamic provisioning of PVs on-demand when a PVC is submitted.
- Access Modes:
ReadWriteOnce(RWO): Volume can be mounted read-write by a single Node.ReadOnlyMany(ROX): Volume can be mounted read-only by many Nodes.ReadWriteMany(RWX): Volume can be mounted read-write by many Nodes (e.g., NFS, Ceph, EFS).
1. Storage Abstraction Architecture#
graph TD
Developer["Developer / Pod Manifest"] -->|Requests 10Gi RWO| PVC["PersistentVolumeClaim (PVC)
(Claim: app-storage-pvc)"]
PVC -->|Binds to| PV["PersistentVolume (PV)
(10Gi RWO, Bound)"]
SC["StorageClass (gp3 / standard)
(Provisioner: ebs.csi.aws.com)"] -.->|Dynamically Creates| PV
PV -->|Mounts underlying storage| Disk["Cloud Disk / Storage LUN
(AWS EBS Volume vol-0a1b2c)"]
2. Dynamic Storage Provisioning with StorageClass#
In modern Kubernetes clusters, administrators configure a default StorageClass so users don’t have to manually pre-provision static PVs.
Inspect cluster StorageClasses:
kubectl get storageclassExpected Terminal Output:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
standard (default) k8s.io/minikube-hostpath Delete Immediate true
gp3-csi ebs.csi.aws.com Delete WaitForFirstConsumer true3. Step-by-Step Stateful Storage Workflow#
Step 1: Submit a PersistentVolumeClaim (PVC)#
Create pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard # Uses cluster default if omittedApply PVC:
kubectl apply -f pvc.yaml
kubectl get pvc postgres-data-pvcExpected Terminal Output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-data-pvc Bound pvc-8d7e9f1a-2b3c-4d5e-6f7a-8b9c0d1e2f3a 5Gi RWO standard 12sNotice how the STATUS instantly changed to Bound because the StorageClass dynamically provisioned a matching PV!
Step 2: Attach PVC to a Pod#
Create pod-storage.yaml:
apiVersion: v1
kind: Pod
metadata:
name: postgres-pod
spec:
containers:
- name: postgres
image: postgres:15-alpine
env:
- name: POSTGRES_PASSWORD
value: "SecretDBPass123"
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-persistent-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-persistent-storage
persistentVolumeClaim:
claimName: postgres-data-pvcApply manifest:
kubectl apply -f pod-storage.yaml4. Reclaim Policies (Retain vs Delete)#
What happens to the underlying PV and cloud storage disk when a user deletes a PVC?
Delete(Default for cloud StorageClasses): Deleting the PVC automatically destroys the bound PV and erases the underlying cloud volume (EBS/GCP PD).Retain: Deleting the PVC leaves the PV intact (Releasedstatus). Data on disk is preserved so an administrator can manually recover or re-bind it.
To change reclaim policy on an existing PV:
kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'5. Summary & Next Steps#
Persistent Volumes ensure your data survives Pod deletions and node crashes. However, deploying databases using a standard Deployment resource can lead to split-brain scenarios or volume attachment errors during node failures.
In Episode 09: StatefulSets & Stateful Database Deployments, we will master StatefulSets to manage stateful workloads requiring unique network identities, ordered deployments, and persistent storage per replica!

