PersistentVolumes and PersistentVolumeClaims
For data that must outlive any Pod, Kubernetes separates two things: the storage itself (a PersistentVolume, PV) and an app's request for storage (a PersistentVolumeClaim, PVC). The app asks for "10Gi, one writer", and Kubernetes finds a matching disk. This chapter covers static provisioning: an admin creates the disks in advance.
The idea: a parking garage
PersistentVolume = a parking space
A real piece of storage: a cloud disk, an NFS share, a local SSD. Created by the cluster admin. Has a size and rules. Exists on its own, not inside any Pod or namespace.
PersistentVolumeClaim = a parking ticket
Written by the developer: "I need a space for 10Gi". Kubernetes matches the ticket to a free space and they are bound, one to one. Lives in your namespace.
Why split them? Your app YAML stays the same on every cluster. Only the PVs change: NFS on-premise, EBS on AWS, a folder on minikube.
Pod → PVC → PV → disk
The three YAMLs
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-medium
spec:
capacity:
storage: 10Gi
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
nfs: # where the data really is
server: 10.0.0.5
path: /exports/blogNo namespace: PVs belong to the whole cluster. The last block says what kind of storage it is: nfs, csi (cloud disks), local, or hostPath for experiments.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: posts-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: manual
resources:
requests:
storage: 8Gi"At least 8Gi, one node writing, class manual." No mention of NFS or servers: the app does not care where the bytes live.
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: posts-data
containers:
- name: posts
image: micro-posts:1.0
volumeMounts:
- name: data
mountPath: /app/dataThe Pod only names the claim. Delete and recreate the Pod, on any node, and it gets the same data back.
Access modes
| Mode | Short | Means | Typical storage |
|---|---|---|---|
| ReadWriteOnce | RWO | Read-write, mounted by one node at a time (Pods on that node may share it) | Cloud block disks (EBS, Persistent Disk) |
| ReadOnlyMany | ROX | Read-only, many nodes | Shared reference data |
| ReadWriteMany | RWX | Read-write, many nodes at once | NFS, EFS, Azure Files, CephFS |
| ReadWriteOncePod | RWOP | Read-write, exactly one Pod | CSI disks, when you need a guarantee |
Most disks are RWO. Needing RWX usually means you need a shared file system, which is slower and more expensive.
Try binding
The admin created four PVs. Create claims of different sizes and modes. Kubernetes binds each claim to the smallest available PV that fits. Then delete a claim and see what happens to its PV.
- Ask 8Gi, get the 10Gi PV: the whole PV is yours; the extra 2Gi is wasted.
- No fit → the PVC stays Pending, and so does any Pod using it.
- Binding is one to one: a bound PV is never shared with another claim.
A PV's lifecycle
Available ──(a PVC matches)──▶ Bound ──(PVC deleted)──▶ Released ──▶ ?
│
reclaimPolicy: Retain ───┤ data kept, admin cleans up by hand
reclaimPolicy: Delete ───┘ PV and disk deleted (next chapter)
A Released PV still has the old data and cannot be bound again automatically, so one team's data never leaks to the next claim.
With kubectl
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
pv-medium 10Gi RWO Retain Bound default/posts-data manual
pv-small 5Gi RWO Retain Available manual
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
posts-data Bound pv-medium 10Gi RWO manual
$ kubectl describe pvc big-claim # when it is Pending
Events:
Normal FailedBinding no persistent volumes available for this claim and no storage class is set
Deployments, replicas and RWO
A Deployment with replicas: 3 and one RWO claim: all three Pods share the same claim. If they land on different nodes, only the first can mount it; the others hang in ContainerCreating with "Multi-Attach error". Two ways out:
Shared data
All replicas really need the same files → use RWX storage (NFS/EFS). Or better: put the data in a database or object storage (S3), not on a disk.
Own data per replica
Each replica needs its own disk (databases) → a StatefulSet with volumeClaimTemplates. It creates data-db-0, data-db-1… and db-1 always gets db-1's disk back (chapter 26).
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10GiRecap
- PV = actual storage (cluster-wide). PVC = a request for storage (in your namespace). Pods mount PVCs.
- Kubernetes binds a PVC to the smallest available PV with the right class, access mode and size.
- Access modes: RWO (one node), RWX (many nodes), ROX, RWOP (one Pod).
- Deleting a PVC releases its PV; what happens next is the reclaim policy.
- Static provisioning means an admin creates PVs in advance. Next: letting Kubernetes create them on demand.
PersistentVolumes and PersistentVolumeClaims: common questions
What is the difference between a PV and a PVC?
A PersistentVolume is a piece of real storage in the cluster. A PersistentVolumeClaim is an app request for storage. Kubernetes binds a claim to a matching volume, one to one.