Skip to main content
PersistentVolumeClaim Stuck Pending — How to Fix It

PersistentVolumeClaim Stuck Pending — How to Fix It

KU
• 6 min read•42 views

A PVC stuck in Pending means nothing on the cluster can hand it a volume. Here's how we read the events and get it bound fast.

A PVC sits in Pending for exactly one reason: nothing on the cluster has handed it a volume yet. Either no existing PersistentVolume matches the claim, or the dynamic provisioner that was supposed to create one hasn't. The pod that needs the storage will sit in Pending right alongside it, and you'll get paged because a deploy never came up.

The good news: the PVC tells you why, if you ask it. The bad news is the message is buried in the events, and half the teams I work with never run the one command that surfaces it.

What Pending actually means

When you create a PVC, the control plane tries to satisfy it. With static provisioning it looks for a pre-created PV whose capacity, access mode, and storage class match. With dynamic provisioning it calls the CSI driver named by the StorageClass and asks it to cut a fresh volume. Pending means neither path finished. It is not a transient "wait a bit" state most of the time. It's a mismatch or a missing piece, and it will stay Pending forever until you change something.

Start with describe, always

Skip kubectl get. It only tells you it's Pending, which you already know. Go straight to the events:

code
kubectl describe pvc data-postgres-0 -n prod

The bottom of that output is where the answer lives:

code
Name:          data-postgres-0
Namespace:     prod
StorageClass:  fast-ssd
Status:        Pending
Volume:
Access Modes:
Events:
  Type     Reason              Age                From                         Message
  ----     ------              ----               ----                         -------
  Warning  ProvisioningFailed  12s (x8 over 2m)   persistentvolume-controller  storageclass.storage.k8s.io "fast-ssd" not found

There it is. The claim asked for a StorageClass called fast-ssd and no such class exists on this cluster. Every root cause below shows up as a distinct event line, so read them before you touch anything.

Then list what you actually have:

code
kubectl get storageclass
code
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      DEFAULT
gp3 (default)        ebs.csi.aws.com         Delete          WaitForFirstConsumer  true
standard             ebs.csi.aws.com         Delete          Immediate             false

The causes, and how each one reads

No matching StorageClass, or no default. This is the "fast-ssd" not found case above. Either the class name in the PVC is wrong, or you left storageClassName blank expecting a default and no class is marked default. Check the DEFAULT column. If nothing says (default), a class-less PVC has nowhere to go. Fix it by pointing the PVC at a real class, or annotate one class as default:

code
kubectl annotate storageclass gp3 \
  storageclass.kubernetes.io/is-default-class=true

The CSI driver isn't installed or is failing. The class exists but provisioning still fails with something like failed to provision volume with StorageClass "gp3": rpc error ... driver name ebs.csi.aws.com not found. That means the controller can't reach the CSI driver. Check the driver pods:

code
kubectl get pods -n kube-system -l app=ebs-csi-controller
kubectl get csidrivers

If the controller is crash-looping, the usual culprit is IAM: the driver's service account can't create volumes in the cloud. Read the controller logs, fix the role, and the queued PVCs provision on their own.

Requested size or access mode isn't available. With static PVs, no persistent volumes available for this claim and no storage class is set means you're relying on hand-made PVs and none is big enough or free. The other classic is access mode. A block store like EBS or a plain GCP PD is ReadWriteOnce only. Ask for ReadWriteMany on it and no PV will ever match:

code
Warning  ProvisioningFailed  ...  invalid AccessModes [ReadWriteMany]: only ReadWriteOnce supported

RWX needs a file backend: EFS, Filestore, or an NFS-backed class. Don't try to bend a block volume into shared access. Pick the right backend.

WaitForFirstConsumer, no consumer. Look back at that StorageClass table. gp3 uses WaitForFirstConsumer. That binding mode is deliberate: the volume isn't provisioned until a pod that mounts the PVC gets scheduled, so the volume lands in the same zone as the pod. If you create a bare PVC and stare at it, it will stay Pending with:

code
Normal  WaitForFirstConsumer  ...  waiting for first consumer to be created before binding

This is not a bug. It's Pending-by-design. Create the pod that uses it. If the pod itself won't schedule (no capacity, taints, unsatisfiable node selector), the PVC waits on the pod, so go debug the pod's scheduling first.

Zone mismatch. With multi-zone clusters and Immediate binding, a volume can get provisioned in zone us-east-1a while the only node that can run the pod is in 1b. EBS volumes don't cross zones, so the pod stays Pending with volume node affinity conflict. This is the exact problem WaitForFirstConsumer was built to prevent, which is why I default every block-storage class to it.

Quota. A ResourceQuota on the namespace capping requests.storage or persistentvolumeclaims will block new claims with a quota-exceeded event. Run kubectl describe resourcequota -n prod and either raise the cap or clean up abandoned PVCs.

A worked example

Here's a PVC and a class that bind cleanly on AWS:

yaml.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-postgres-0
  namespace: prod
spec:
  storageClassName: gp3
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 50Gi

The data-postgres-0 failure at the top was just storageClassName: fast-ssd where the cluster only had gp3. Changing that one line and re-applying cleared it. The PVC bound the moment the StatefulSet pod scheduled.

If you want a broader map of cluster failure modes, our Kubernetes troubleshooting guide covers the neighboring ones.

The call we'd make

Run describe first, every time, and read the events before you change a single YAML line. Nine times out of ten the message names the exact problem, and the tenth is a CSI driver or IAM issue that the driver's own logs will hand you. Default your block-storage classes to WaitForFirstConsumer so zone mismatches stop happening, mark exactly one StorageClass as default, and stop reaching for ReadWriteMany on volumes that were never built to share.

Explore topics:KubernetesDevOps
React
Share this post
KU

Kiril Urbonas

AI Engineer

553 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.