PersistentVolumeClaim ā Extended Technical Detail
What is a PVC in Simple Terms?
Think of a PersistentVolume (PV) as a physical hard drive in the data centre. A PersistentVolumeClaim (PVC) is your ticket to reserve that hard drive for your pod. You say "I need 50GB of fast SSD storage" and Kubernetes finds a matching PV and binds them together.
PVC Lifecycle
+------------------------------------------+| Admin creates PersistentVolume | <- Static provisioning, or StorageClass| (or StorageClass auto-provisions one) | handles this automatically+------------------------------------------+ | v+------------------------------------------+| Developer creates PersistentVolumeClaim | <- Specifies size, access mode,| (requests storage size + type) | and StorageClass+------------------------------------------+ | v+------------------------------------------+| Kubernetes binds PVC to matching PV | <- Binding is exclusive ā one PVC| | to one PV only+------------------------------------------+ | v+------------------------------------------+| Pod mounts the PVC as a volume | <- Pod references PVC by name| | in its volumes spec+------------------------------------------+ | v+------------------------------------------+| Data persists even if pod restarts | <- Survives pod deletion, rescheduling,| or gets deleted | and node replacement+------------------------------------------+Access Modes Explained
+------------------------+ +------------------------+ +------------------------+| ReadWriteOnce (RWO) | | ReadOnlyMany (ROX) | | ReadWriteMany (RWX) || | | | | || One node can read | | Many nodes can read | | Many nodes can read || and write | | simultaneously | | AND write || | | | | || Use for: databases | | Use for: static | | Use for: shared file || (MySQL, Postgres) | | config, read caches | | storage (NFS, EFS) |+------------------------+ +------------------------+ +------------------------+Example PVC and Pod
1# pvc.yaml ā request 50GB SSD storage for a MySQL database2apiVersion: v13kind: PersistentVolumeClaim4metadata:5 name: mysql-data-pvc6 namespace: production7spec:8 accessModes:9 - ReadWriteOnce # Only one node can mount this for read-write10 storageClassName: gp3-encrypted11 resources:12 requests:13 storage: 50Gi1415# pod.yaml ā mount the PVC inside the MySQL container16spec:17 containers:18 - name: mysql19 image: mysql:8.020 volumeMounts:21 - name: mysql-storage22 mountPath: /var/lib/mysql # MySQL data directory inside the container23 volumes:24 - name: mysql-storage25 persistentVolumeClaim:26 claimName: mysql-data-pvc # Reference the PVC by nameStorageClass and Dynamic Provisioning
Most production clusters use dynamic provisioning ā no admin needs to pre-create PVs. The StorageClass defines the provisioner and disk type:
1# storageclass.yaml ā production-grade encrypted SSD StorageClass for AWS2apiVersion: storage.k8s.io/v13kind: StorageClass4metadata:5 name: gp3-encrypted6provisioner: ebs.csi.aws.com7parameters:8 type: gp39 encrypted: "true"10 iops: "3000"11 throughput: "125"12reclaimPolicy: Retain # CRITICAL: Retain disk after PVC deletion13allowVolumeExpansion: true # Allow resizing without pod restart14volumeBindingMode: WaitForFirstConsumer # Provision in the same AZ as the podš” Tip: Always use a StorageClass with reclaimPolicy: Retain for production databases. This prevents the underlying disk from being automatically deleted if the PVC is accidentally removed. You can always manually delete the PV after confirming the data is no longer needed.Checking PVC Status and Binding
1# List all PVCs across all namespaces2kubectl get pvc -A3 4# Check PVC binding status and which PV it's bound to5kubectl get pvc mysql-data-pvc -n production6# NAME STATUS VOLUME CAPACITY ACCESS MODES7# mysql-data-pvc Bound pvc-3a8f2c1d-4b5e-11ee-9a2f-0a1b2c3d4e5f 50Gi RWO8 9# Describe for full details including events10kubectl describe pvc mysql-data-pvc -n production11 12# Check if PV is retained after PVC deletion13kubectl get pv | grep Released14# A Released PV can be manually reclaimed and reboundResizing a PVC
1# Step 1: Edit the PVC to request more storage (StorageClass must allow expansion)2kubectl patch pvc mysql-data-pvc -n production \3 -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'4 5# Step 2: The CSI driver expands the underlying disk automatically6# Step 3: Verify the resize completed7kubectl get pvc mysql-data-pvc -n production8# Capacity should now show 100GiTroubleshooting Common PVC Problems
| Problem | Symptom | Fix |
|---|---|---|
| PVC stuck in Pending | STATUS: Pending indefinitely |
No matching PV or StorageClass ā check kubectl describe pvc events for the exact mismatch |
| Pod stuck in ContainerCreating | Pod never starts | PVC is not yet bound ā check kubectl get pvc and ensure STATUS is Bound |
| PVC deleted, data lost | Data unrecoverable | StorageClass had reclaimPolicy: Delete ā switch to Retain for all production StorageClasses |
| PVC resize fails | Capacity unchanged after patch | StorageClass does not have allowVolumeExpansion: true ā update the StorageClass and retry |
| Wrong AZ binding | Pod and PV in different AZs | Set volumeBindingMode: WaitForFirstConsumer on StorageClass to pin PV to pod's AZ |
š“ Common Mistake: Using accessModes: ReadWriteMany for databases. Most cloud block storage (AWS EBS, GCP Persistent Disk) does not support RWX mode ā the PVC will stay in Pending forever. Use RWX only for shared file storage like NFS or AWS EFS.ā ļø Security: Never store Kubernetes Secrets or TLS certificates inside a PVC. Use the Secret object or an external secrets manager (AWS Secrets Manager, Vault). A PVC with ReadWriteMany on a shared NFS mount means every pod in the cluster with the right claim can read every file on that volume.š Remember: A PVC is namespace-scoped, but a PV is cluster-scoped. A PVC in payments-prod can only bind to a cluster-level PV ā it cannot bind to a PV in another namespace. This is why StorageClass dynamic provisioning exists: it creates a fresh PV per PVC automatically without admin involvement.