Kubernetes PVC Generator
Build a PersistentVolumeClaim, and find out before you apply it whether the storage behind it can actually serve what you asked for. Tell it which backend the class uses and it says plainly when the claim would sit in Pending forever.
pvc.yaml
updates as you type Common mistakes
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
Requesting ReadWriteMany on block storage
EBS, GCE PD and Azure Disk are ReadWriteOnce. The claim stays Pending with an event most people do not look at.
Instead:Check what the storage class supports. ReadWriteMany needs a file protocol such as EFS or NFS.
Assuming a PVC can be shrunk
Expansion is supported where the storage class allows it. Shrinking is not supported at all.
Instead:Size with room, or migrate to a new claim.
Leaving the default reclaim policy on production data
Many dynamic provisioners default to Delete, so removing the PVC destroys the volume and its data.
Instead:Set Retain for anything that matters.
What a PersistentVolumeClaim actually controls, and what it does not
A PVC asks for storage of a given size and access mode. Almost everything that decides whether that request can be met sits somewhere else: on the StorageClass, and in what the backing disk or filesystem is physically able to do. That split is why the common failures here are silent. A claim that cannot be satisfied does not fail, it stays Pending, and the reason lives in an event that is garbage collected after an hour.
accessModes describe the storage, they do not ask it for anything
An access mode is matched against what a volume can do. It is not a capability the cluster can add. ReadWriteMany on a class backed by AWS EBS, Google Persistent Disk or an Azure managed disk can never be satisfied, because those attach to one machine at a time. The claim is accepted, nothing is provisioned, and kubectl get pvc shows Pending with no cause next to it.
accessModes:
- ReadWriteMany
on EBS / GCE PD / Azure Disk:
kubectl get pvc Pending, no reason shown
kubectl describe pvc ProvisioningFailed, until the
event is garbage collected
ReadWriteMany needs a shared filesystem:
EFS, FSx, Azure Files, CephFS, NFS ReadWriteOnce is one node. ReadWriteOncePod is one pod.
ReadWriteOnce constrains attachment, not usage: several pods scheduled onto the same node can all mount the volume and all write to it. The pod that fails is the one on a second node, and it fails as a Multi-Attach error with the pod stuck in ContainerCreating. That is why this surfaces during a Deployment rolling update, where the replacement pod starts before the old one is gone. ReadWriteOncePod is the mode that genuinely means one pod, and the API server enforces it.
ReadWriteOnce one NODE attaches it
3 pods on that node all write to it
ReadWriteOncePod one POD, enforced by the API server
1.22 alpha, 1.27 default on, 1.29 GA
needs a CSI driver, no in-tree plugin
ReadOnlyMany many nodes, read only
ReadWriteMany many nodes, all writing
Deployment + RWO + RollingUpdate:
new pod on node-b Multi-Attach error for volume
old pod on node-a still holding it
fix: strategy.type: Recreate, or a StatefulSet Immediate binding can put the disk where the pod cannot go
volumeBindingMode is on the StorageClass. Immediate provisions the volume the moment the claim exists, before anything knows where the pod will be scheduled. In a multi-zone cluster the volume can land in a zone with no room for the pod, and a zonal disk cannot be moved. The pod then stays Pending on a volume node affinity conflict while the claim reports Bound, which is why this gets investigated as a scheduling problem. WaitForFirstConsumer is the answer and should be the default choice.
volumeBindingMode: Immediate
1. PVC created disk provisioned in eu-west-1a
2. pod scheduled only eu-west-1b has capacity
3. pod Pending 1 node(s) had volume node
affinity conflict
PVC says Bound throughout.
volumeBindingMode: WaitForFirstConsumer
1. PVC created Pending, on purpose
2. pod scheduled eu-west-1b
3. disk provisioned eu-west-1b Omitted, empty and named storageClassName are three different things
Omitting the field lets the DefaultStorageClass admission plugin write the cluster default into the object as it is created, and that value is then fixed: changing the cluster default later does nothing to claims that already exist. Setting the field to the empty string means no class at all, so nothing is provisioned and the claim binds only to a PersistentVolume that already exists. The two are frequently treated as the same "no opinion" and they are opposites.
# omitted: the default class is written in at
# creation time, and no default class means Pending
spec:
resources:
requests:
storage: 10Gi
# empty string: no class, no provisioning. Binds only
# to a pre-created PV, or waits forever.
spec:
storageClassName: ""
# named: independent of whatever the default is today
spec:
storageClassName: gp3 Growing works if the class allows it, shrinking never does
Expansion requires allowVolumeExpansion: true on the StorageClass. With it, editing spec.resources.requests.storage upward grows the volume, and where the driver cannot resize a mounted filesystem the claim waits on a FileSystemResizePending condition until the pod restarts. Shrinking is rejected by the API server on every class. The requested size is also a minimum rather than an allocation: providers round up to their own increments and bill the rounded figure.
allowVolumeExpansion: true
10Gi -> 100Gi allowed, edit requests.storage
100Gi -> 10Gi rejected, always
reclaimPolicy is on the class, not the claim
Delete kubectl delete pvc data
-> PV and disk destroyed, no undo
Retain kubectl delete pvc data
-> PV goes Released and stays, still
billing, until spec.claimRef is
cleared by hand A claim stuck in Terminating is still referenced by a pod
Deleting a PVC that is in use returns success and then leaves the object in Terminating. The kubernetes.io/pvc-protection finalizer holds it there while any pod still lists the claim in spec.volumes, whether or not that pod is running. The fix is to find and delete the pod. Removing the finalizer by hand deletes the claim out from under a pod that still has the volume mounted, and on a Delete class that destroys the disk.
kubectl delete pvc data
persistentvolumeclaim "data" deleted
kubectl get pvc data
data Terminating
metadata:
finalizers:
- kubernetes.io/pvc-protection
# find what is holding it:
kubectl get pods -o json | jq -r '
.items[] | select(
.spec.volumes[]?.persistentVolumeClaim.claimName
== "data") | .metadata.name'