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.

Claim
Storage class

The class is where volumeBindingMode, allowVolumeExpansion and reclaimPolicy live. None of them can be set on the claim.

Bind to a volume that already exists

Leave both blank for normal dynamic provisioning. These only apply when you are matching a pre-created PersistentVolume.

Check it against your storage

Not part of the output. Used to tell you whether this claim can bind at all, and what happens when you delete it.

Pod snippet

Emitted as comments under the claim, so the output stays one document you can apply as it is.

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.

    1. 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.

    2. 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.

    3. 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'

    More kubernetes tools

    Kubernetes YAML Generator Answer a few questions, get a manifest Kubernetes YAML Validator Is this YAML valid, and where is it wrong? Kubernetes Rollout & Probe Timing Restart loops, capacity dips and stuck drains Kubernetes Service Checker Will this Service have any endpoints? Kubernetes Network Policy Viewer What can reach what, and what just broke Kubernetes Manifest Diff What actually changed Kubernetes Resource Calculator What these manifests actually reserve Kubernetes RBAC Viewer Who can actually do what Kubernetes Deployment Generator See the capacity dip before you deploy Kubernetes Service Generator port, targetPort, and which one you meant Kubernetes StatefulSet Generator Stable names, and volumes that outlive you Kubernetes QoS Class Calculator And where you sit in the eviction order Kubernetes Node Capacity Planner Allocatable is not capacity Kubernetes Max Pods per Node Calculator The ENI limit, not the 110 default Kubernetes Label Selector Tester Which key failed, not just that one did Kubernetes Taint & Toleration Tester Tolerations permit, they do not attract Kubernetes Resource Name Validator RFC 1123 subdomain, label, or 1035 Kubernetes Label & Annotation Validator Why your image tag is not a legal label Kubernetes CrashLoopBackOff Guide The status is a timer, not a cause Kubernetes TLS Certificate Decoder When it expires, and what it covers Kubernetes ServiceAccount Token Decoder Bound or legacy, and when it dies Kubernetes imagePullSecret Generator The registry URL nobody gets right Kubernetes cert-manager Certificates Issuer scope, and the rate limit that costs a week Kubernetes Flux HelmRelease Generator With the source it cannot work without Kubernetes Helm Chart Generator Chart.yaml, values, and working templates Kubernetes Argo CD Application Generator The defaults that are off, turned on Kubernetes Ingress Generator pathType, and the TLS secret that fails quietly Kubernetes HPA Generator Why it says unknown and never scales Kubernetes SecurityContext Generator Pod level, container level, and which wins Kubernetes Probe Generator How long it gets to start before liveness kills it Kubernetes ResourceQuota Generator And the LimitRange that stops it breaking deploys Kubernetes CronJob Generator The fields that decide whether it runs Kubernetes NetworkPolicy Generator With the DNS rule already in it Kubernetes RBAC Generator And what it actually grants Kubernetes PodDisruptionBudget Generator Can this budget ever be satisfied? Kubernetes API Deprecation Checker What breaks on the next upgrade Kubernetes Kubeconfig Viewer Read it without handing it to anyone Kubernetes Quantity Parser What 512Mi, 100m and 1e3 actually mean Kubernetes Multi-Document YAML Splitter One file per object, in apply order Kubernetes .env to ConfigMap & Secret Split the credentials out on the way Kubernetes Secret Decoder See what is actually in there Kubernetes Ingress to Gateway API Which annotations silently stop working Kubernetes Manifest Visualizer What points at what, and what points at nothing Kubernetes Patch Tester Which --type, and what it deletes Kubernetes kubectl JSONPath Tester kubectl's dialect, not generic JSONPath Kubernetes Node Affinity Tester Required excludes, preferred only scores Kubernetes Helm Values Diff A missing key is a third value Kubernetes Pod and Service CIDR Fixed at cluster creation Kubernetes Secret Encoder base64 is not encryption Kubernetes API Version Checker Will this apply on that cluster Kubernetes Go Template Tester Go truthiness is not JavaScript's Kubernetes Cluster Cost Estimator At your rates, not a price table Kubernetes Kustomize Build Transformers run in kustomize's order Kubernetes Helm Chart Validator version is SemVer, appVersion is not

    Elsewhere on the site