iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…