When you need storage that survives Pod restarts, rescheduling, and even deletion — you need PersistentVolumes. The PV/PVC system decouples storage provisioning (admin concern) from storage consumption (developer concern), just like Nodes decouple compute provisioning from Pod scheduling.

1. The PV/PVC Model

Pod uses volume PVC "I need 10Gi RWO" Namespaced (developer) PV "I have 10Gi RWO on EBS" Cluster-scoped (admin) EBS disk ref BIND Pod references PVC by name → PVC is bound to PV → PV maps to real storage
ResourceScopeCreated ByPurpose
PersistentVolume (PV)ClusterAdmin (or dynamic provisioner)Represents a piece of real storage
PersistentVolumeClaim (PVC)NamespaceDeveloperRequest for storage (size, access mode, class)
The abstraction: Developers create PVCs ("I need 10Gi of fast storage"). They don't know or care whether it's an EBS volume, NFS share, or local SSD. The PV system matches the claim to real storage — either pre-provisioned (static) or created on-demand (dynamic).

How Binding Works

  1. Developer creates a PVC with requirements (size, access mode, StorageClass)
  2. The PV controller finds a matching PV (or dynamic provisioner creates one)
  3. PVC and PV are bound (1:1 relationship — a PV can only bind to one PVC)
  4. Pod references the PVC by name in its volume spec
  5. kubelet mounts the underlying storage into the Pod

2. PersistentVolume Spec

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-ebs-01                  # Cluster-scoped (no namespace)
spec:
  capacity:
    storage: 50Gi                  # Size of the volume
  accessModes:
    - ReadWriteOnce                # How many nodes can mount it
  persistentVolumeReclaimPolicy: Retain   # What happens when released
  storageClassName: gp3            # Links to a StorageClass (for binding)
  csi:                             # Backend-specific (CSI driver)
    driver: ebs.csi.aws.com
    volumeHandle: vol-0abc123def456
    fsType: ext4
  nodeAffinity:                    # Which nodes can access this volume
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values: [us-east-1a]

Access Modes

ModeAbbreviationMeaningSupported By
ReadWriteOnceRWOMounted read-write by one nodeMost block storage (EBS, GCE PD, Azure Disk)
ReadOnlyManyROXMounted read-only by many nodesNFS, CephFS, cloud file shares
ReadWriteManyRWXMounted read-write by many nodesNFS, CephFS, EFS, Azure Files
ReadWriteOncePodRWOPMounted read-write by one Pod (K8s 1.27+)CSI drivers that support it
RWO means one NODE, not one Pod. Multiple Pods on the same node can mount the same RWO volume simultaneously. Only one node can mount it. For true single-Pod exclusivity, use ReadWriteOncePod (RWOP). This is a common exam trap.

Reclaim Policies

PolicyWhen PVC is DeletedUse Case
RetainPV becomes Released — data preserved, admin must manually clean upProduction data you can't afford to lose
DeletePV AND underlying storage are deletedDynamic provisioning (default for most StorageClasses)
Recycle❌ Deprecated — performs rm -rf and makes PV available againDon't use
In production databases, always use Retain. If someone accidentally deletes the PVC, the underlying disk survives. You can manually rebind it to a new PVC. With Delete, the disk is gone forever — there's no undo. Dynamic provisioners default to Delete, so override for critical workloads.

3. PersistentVolumeClaim Spec

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3             # Must match PV's storageClassName
  resources:
    requests:
      storage: 50Gi                 # Minimum size needed

Binding Rules

A PVC binds to a PV when all these match:

  1. StorageClass — PVC's storageClassName matches PV's (or both empty)
  2. Access mode — PV supports at least the modes requested
  3. Capacity — PV's capacity ≥ PVC's request
  4. Selector — if PVC has a selector, PV must match its labels
# PVC with label selector (static provisioning):
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 20Gi
  selector:
    matchLabels:
      environment: production
      tier: database

Using a PVC in a Pod

apiVersion: v1
kind: Pod
metadata:
  name: postgres
spec:
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: db-data          # Reference the PVC by name
  containers:
    - name: postgres
      image: postgres:16
      volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data

4. PV/PVC Lifecycle

Available Free, not bound PVC binds Bound In use by PVC PVC deleted Released Data exists, no PVC Retain → manual cleanup Delete → disk gone

The "Released" Problem

When a PVC is deleted and the PV has Retain, the PV enters Released state. It still contains data but cannot be bound to a new PVC automatically. To reuse it:

# Option 1: Remove the claimRef to make it Available again:
kubectl patch pv pv-ebs-01 -p '{"spec":{"claimRef": null}}'
# Now it can bind to a new PVC

# Option 2: Create a PVC that matches exactly (same name, same spec)
# This is how you recover a StatefulSet's data after accidental PVC deletion
CKA exam: if you're asked to make a Released PV available for binding again, patch out the spec.claimRef. This clears the old PVC reference and the PV becomes Available.

5. Static vs Dynamic Provisioning

Static ProvisioningDynamic Provisioning
PV creationAdmin pre-creates PVs manuallyProvisioner creates PV automatically when PVC is created
WorkflowAdmin creates PV → Dev creates PVC → bindDev creates PVC → provisioner creates PV → bind
ScaleDoesn't scale (admin bottleneck)Self-service (developers get storage on demand)
Use casePre-existing storage, specific hardware, complianceCloud environments, self-service platforms
# Static: Admin creates PV pointing to existing EBS volume
# Dynamic: Developer creates PVC with StorageClass → provisioner creates EBS + PV

# Dynamic provisioning is the norm in production.
# StorageClasses (next lesson) enable it.
Dynamic provisioning = self-service storage. Developers create a PVC, and a provisioner (CSI driver) automatically creates the underlying disk, the PV, and binds them. No admin intervention. This is how all cloud K8s platforms work by default.

6. Volume Expansion (Resize)

# PVC can be expanded (if StorageClass allows it):
kubectl edit pvc db-data
# Change: resources.requests.storage: 50Gi → 100Gi

# Or patch:
kubectl patch pvc db-data -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'

Requirements for Expansion

  • StorageClass must have allowVolumeExpansion: true
  • Only growing is supported — you cannot shrink a PVC
  • For file-system based volumes: the Pod may need to restart for the filesystem to resize (depends on CSI driver — many support online expansion)
# Check if expansion is supported:
kubectl get sc gp3 -o yaml | grep allowVolumeExpansion
# allowVolumeExpansion: true

# After patching, check PVC conditions:
kubectl get pvc db-data -o yaml | grep -A5 conditions
# - type: FileSystemResizePending
#   status: "True"
# → Pod restart needed to resize filesystem
# (or it happens online if the CSI driver supports it)
Always enable allowVolumeExpansion: true on production StorageClasses. Databases grow — and re-provisioning a larger volume means data migration. With expansion, you just patch the PVC size. Most CSI drivers (EBS, GCE PD, Azure Disk) support online expansion without Pod restart since K8s 1.24+.

Summary

ConceptKey Point
PVCluster-scoped, represents actual storage. Created by admin or provisioner.
PVCNamespaced request for storage. Created by developer.
Binding1:1 match by StorageClass + access mode + capacity. PV can't bind to two PVCs.
Access modesRWO (one node), ROX (many read), RWX (many read-write), RWOP (one Pod)
RWOOne NODE, not one Pod — multiple Pods on same node can share it
Reclaim: RetainPV preserved after PVC deletion — manual cleanup required
Reclaim: DeletePV + underlying disk deleted when PVC is deleted
Released statePV still has data but can't auto-bind. Patch out claimRef to reuse.
ExpansionIncrease PVC size if StorageClass allows. Cannot shrink.
Dynamic provisioningPVC + StorageClass → provisioner auto-creates PV + disk

📝 Quiz: PersistentVolumes & PersistentVolumeClaims

Q1: A PVC requests 20Gi with RWO. Two PVs exist: PV-A (50Gi, RWO) and PV-B (10Gi, RWO). Which does it bind to?

PV-A (50Gi). The PV must have capacity ≥ the PVC request. PV-B (10Gi) is too small. PV-A (50Gi) satisfies the 20Gi request. The PVC gets bound to 50Gi even though it only asked for 20Gi — PV binding uses the smallest PV that satisfies all requirements.

Q2: What's the difference between ReadWriteOnce and ReadWriteOncePod?

RWO: The volume can be mounted read-write by one node. Multiple Pods on that same node CAN mount it simultaneously.
RWOP: The volume can be mounted read-write by exactly one Pod. If another Pod (even on the same node) tries to mount it, it's denied. RWOP provides true single-writer semantics.

Q3: A PVC is deleted. The PV had persistentVolumeReclaimPolicy: Retain. What's the state of the PV and underlying data?

The PV moves to Released state. The underlying storage (EBS volume, disk) and its data are preserved. However, the PV cannot be automatically bound to a new PVC — it still references the old PVC in its claimRef. To reuse: patch out the claimRef or manually delete and recreate the PV.

Q4: You want to expand a PVC from 50Gi to 100Gi. What must be true for this to work?

The StorageClass must have allowVolumeExpansion: true. Also: (1) The underlying CSI driver must support expansion. (2) You can only grow, never shrink. (3) The filesystem may need to be expanded (some drivers do this online, others require a Pod restart).

Q5: A developer creates a PVC but it stays in "Pending" status forever. The cluster has no StorageClass. What's wrong?

No matching PV exists (static provisioning) AND no StorageClass exists (no dynamic provisioning). The PVC has nothing to bind to. Fix: either (1) create a PV that matches the PVC's requirements, or (2) install a StorageClass with a provisioner so PVs are created automatically when PVCs are created.

Q6: In a StatefulSet with volumeClaimTemplates, what happens to PVCs when you scale down from 5 to 3 replicas?

The PVCs for Pods 3 and 4 are NOT deleted (by default). They remain Bound to their PVs with data intact. When you scale back up to 5, the original PVCs are re-attached to the new Pods — data is preserved across scale operations. This is a key StatefulSet guarantee for stateful workloads.