PV / PVC
PersistentVolumeClaim is how a Pod asks the cluster for durable storage without knowing about the underlying provider. The control plane satisfies the claim by binding it to a PersistentVolume — either pre-provisioned by an admin or dynamically created by a StorageClass. The Pod sees a mounted directory; everything else is plumbing.
StorageClass + PVC + StatefulSet that survives restarts
EXAMPLE
# 1) StorageClass (cluster-wide; admins usually create one per backend)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard-rwo
provisioner: ebs.csi.aws.com # or pd.csi.storage.gke.io, disk.csi.azure.com, etc.
parameters:
type: gp3
fsType: ext4
encrypted: 'true'
reclaimPolicy: Delete # use Retain if data should outlive the PVC
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# 2) Standalone PVC for a single Pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: shop
spec:
accessModes: [ReadWriteOnce] # RWO is right for block storage / EBS
storageClassName: standard-rwo
resources:
requests:
storage: 20Gi
---
# 3) Pod that mounts the PVC
apiVersion: v1
kind: Pod
metadata: { name: pg, namespace: shop }
spec:
containers:
- name: pg
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom: { secretKeyRef: { name: pg-secret, key: password } }
volumeMounts:
- { name: data, mountPath: /var/lib/postgresql/data }
volumes:
- name: data
persistentVolumeClaim: { claimName: postgres-data }
---
# 4) StatefulSet with per-Pod PVCs — the production pattern
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: pg, namespace: shop }
spec:
serviceName: pg-headless
replicas: 3
selector: { matchLabels: { app: pg } }
template:
metadata: { labels: { app: pg } }
spec:
containers:
- name: pg
image: postgres:16
ports: [{ containerPort: 5432 }]
volumeMounts:
- { name: data, mountPath: /var/lib/postgresql/data }
volumeClaimTemplates:
- metadata: { name: data }
spec:
accessModes: [ReadWriteOnce]
storageClassName: standard-rwo
resources: { requests: { storage: 50Gi } }
# 5) Resize an existing PVC (if allowVolumeExpansion: true)
kubectl -n shop patch pvc postgres-data \
--type merge -p '{"spec":{"resources":{"requests":{"storage":"40Gi"}}}}'
# 6) Inspect: who is bound to what, and is anything pending?
kubectl get pvc -A
kubectl describe pvc postgres-data -n shop
kubectl get pv
Why it matters
WaitForFirstConsumer binds the PV in the same zone as the Pod that consumes it — the alternative (Immediate) can place the volume in zone A and then make the Pod unschedulable because Pod went to zone B. Default to WaitForFirstConsumer on any multi-AZ cluster.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
kind: PersistentVolumeClaim
spec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 10Gi } } }
Try it Yourself »
Discussion
Loading…