A pod sits in Pending. You run kubectl describe pod and the events end with a line from the scheduler:
Warning FailedScheduling 0/6 nodes are available: pod has unbound immediate PersistentVolumeClaims.It reads like a capacity problem, so the first instinct is usually to look at nodes. That is almost always the wrong place to look. The scheduler is not telling you it ran out of room. It is telling you it refuses to even start evaluating nodes, because a volume the pod depends on does not exist yet, and the scheduler will not place a pod whose storage might never arrive.
This post walks the five things that actually cause it, in the order that isolates the problem fastest, then covers the part most guides skip: the standard fix trades a scheduling error for a mobility ceiling, and whether that trade matters depends entirely on what your storage layer looks like underneath.
What the error message actually means
Two separate mechanisms have to line up before a pod with a PersistentVolumeClaim can run: the claim has to bind to a volume, and the scheduler has to pick a node.
The word immediate in the message is not a description of urgency. It is the name of a StorageClass setting: volumeBindingMode: Immediate. With that mode, binding happens the moment the PVC is created, with no reference to any pod. The provisioner picks where the volume lives, the claim goes to Bound, and only later does a pod come along asking to use it.
The scheduler’s volume-binding plugin checks, for every pod it considers, whether all Immediate-mode claims are already bound. If any is still Pending, the plugin rejects the pod for every node at once and emits exactly the message above. The node count in the message (0/6 nodes are available) is misleading: no node was ever actually assessed.
So the error is a symptom with one meaning, and it always points at the claim, never at the nodes:
# Confirm which claim is the problemkubectl get pvc -n <namespace>
# The events on the claim carry the real reasonkubectl describe pvc <claim-name> -n <namespace>If the claim shows Pending, the describe output’s event list is where the actual cause lives. Everything below is a way of reading that output.
Five causes, in the order worth checking
These are ordered by how often they turn out to be the answer and how cheap they are to rule out, not by severity.
| # | Cause | Fastest check | What you see |
|---|---|---|---|
| 1 | No StorageClass resolves | kubectl get sc | No class marked (default), and the PVC has no storageClassName |
| 2 | Provisioner is not running or is failing | kubectl describe pvc events | ProvisioningFailed, or no provisioner event at all |
| 3 | Topology conflict under Immediate | kubectl get pv -o wide | Volume bound in one zone, pod constrained to another |
| 4 | Access mode not supported | kubectl describe pvc | Claim requests ReadWriteMany, driver offers ReadWriteOnce |
| 5 | Static PV mismatch or quota | kubectl get pv, kubectl describe quota | No PV satisfies size, class, or selector; or storage quota exhausted |
Table 1: Root causes of an unbound immediate PVC, ordered by how quickly each can be ruled out.
1. No StorageClass resolves. A PVC created without storageClassName relies on a cluster default. If no class carries the storageclass.kubernetes.io/is-default-class: "true" annotation, nothing ever picks the claim up, and it will sit Pending indefinitely with no error event, which is what makes this one confusing. Two defaults is equally broken: Kubernetes refuses to choose. Set exactly one, or name the class explicitly in the claim.
2. The provisioner is not running or is failing. The CSI driver controller has to be up, authorized against the backend, and able to satisfy the request. A crash-looping controller pod, expired backend credentials, or a full storage pool all surface here as a ProvisioningFailed event on the claim. Read the controller logs, not the pod’s:
kubectl -n <csi-namespace> logs deploy/<csi-controller> -c csi-provisioner --tail=1003. A topology conflict under Immediate binding. This is the interesting one, and the reason the error exists at all. Under Immediate, the volume gets placed before anyone knows where the pod will run. If the provisioner put it in availability zone A, and the pod carries a nodeSelector, an affinity rule, or a second volume that constrains it to zone B, no node can satisfy both. The claim is bound, the pod is unschedulable, and the two facts never reconcile on their own. Deleting and recreating the claim is a coin flip, not a fix.
4. The access mode is not supported. A claim asking for ReadWriteMany against a driver that only offers ReadWriteOnce has no candidate volume, so it stays Pending. This bites hardest on workloads moved from a shared-filesystem world, where multi-writer access was simply assumed.
5. A static PV mismatch, or a quota ceiling. When you pre-provision volumes by hand, binding requires the PV to match the claim on capacity, access modes, storageClassName, and any label selector, with a PV that is genuinely Available rather than Released from an earlier claim. Separately, a ResourceQuota capping requests.storage or the persistentvolumeclaims count will block provisioning with a quota event on the claim.
Chasing unbound PVCs across zones every time a node goes down? If binding mode and topology have become a recurring scheduling constraint rather than a one-off misconfiguration, the storage layer underneath is worth a conversation. Talk to a storage architect
Immediate vs WaitForFirstConsumer, and what the workaround costs
The advice you will find everywhere is to switch the StorageClass to volumeBindingMode: WaitForFirstConsumer. That advice is correct, and it does fix cause 3 properly. It is worth understanding why, because the mechanism explains the catch.
With WaitForFirstConsumer, the claim deliberately stays unbound until a pod references it. The scheduler picks the node first, using all the pod’s real constraints, and then passes that placement decision to the provisioner as a topology hint. The volume is created where the pod already is. The ordering that caused the conflict is reversed, so the conflict cannot occur.
What that does not change is where the volume physically lives. If the storage layer is node-local, meaning the volume is carved out of disks attached to one specific machine, then the resulting PersistentVolume is stamped with a nodeAffinity naming that machine. From then on the pod can only ever be scheduled there. That is fine on day one. It is not fine when the node needs draining for maintenance, or fails outright: the pod becomes unschedulable again, and this time the fix is a data rebuild rather than a config change.
So WaitForFirstConsumer on node-local storage does not remove the coupling between volumes and nodes. It moves the moment you feel it, from first deploy to first node loss. This is the same class of problem as treating ephemeral storage as if it were durable: the architecture looks correct until the cluster changes shape.
| Storage layer | Immediate binding | WaitForFirstConsumer | After a node is lost |
|---|---|---|---|
| Node-local disks | Topology conflicts are likely | Conflicts resolved, pod pinned to one node | Pod unschedulable, data rebuild required |
| Per-zone cloud block storage | Conflicts when zones disagree | Conflicts resolved, pod pinned to one zone | Reschedulable inside the zone only |
| Shared pool over a storage fabric | Safe, binding implies no placement | Also safe, no pinning introduced | Reattach on any node in the pool |
Table 2: How binding mode interacts with the storage layer underneath, and what each combination means when a node disappears.
Why the problem disappears with a shared storage pool
The root of every topology variant of this error is the same assumption: that a volume belongs to a place. Break that assumption and the whole class of failure stops applying.
Simplyblock presents volumes from a single pool over NVMe/TCP or NVMe/RoCE, which means any node on the fabric can attach any volume. Three consequences follow directly, and each one maps to a cause in the table above.
Binding a claim stops implying a placement. Under Immediate mode against a shared pool, the claim binds straight away, as designed, but the resulting volume carries no nodeAffinity restricting where it can be used. The scheduler evaluates the pod against every node on its real merits. Cause 3 has nothing to act on.
Capacity comes from the pool, not from one machine’s free space. A provisioning failure caused by the specific node the scheduler happened to choose having no room left is not a scenario that arises, because provisioning is not scoped to a node in the first place. Compute and storage scale independently: adding storage capacity does not mean adding compute nodes, and vice versa.
One class serves both access modes. Because the volume is reachable from multiple nodes over the fabric, ReadWriteMany block is available from the same StorageClass that serves ReadWriteOnce, which removes cause 4 as a design constraint rather than as a per-claim workaround. This is what makes live migration work for VM workloads on KubeVirt and Red Hat® OpenShift® Virtualization, where the destination node needs a write-capable attachment before the source releases one.
The operational payoff shows up on the day a node dies. Rescheduling a stateful pod becomes a volume reattachment measured in seconds, not a replica rebuild measured in the time it takes to stream the dataset across the network. Licensing follows the same logic: simplyblock is priced like the platform it runs on, by the CPU capacity of your worker nodes counted the way the subscription counts them, and data volume does not change the price.
What to fix now, and what to fix once
Separate the two timescales, because they call for different actions.
Right now, to get the pod running: read the events on the claim, not on the pod. Confirm exactly one default StorageClass exists, or name one in the claim. Check the CSI controller is healthy and its credentials are valid. If the claim is Bound but the pod still will not schedule, compare the volume’s zone against the pod’s constraints, since that combination is cause 3 and nothing but a rebind or a constraint change will clear it. If a static PV is involved, diff its capacity, access modes, class, and selector against the claim field by field.
Once, so it stops recurring: set WaitForFirstConsumer as the default on any StorageClass backed by node-local or zonal storage, since it is strictly better than Immediate there. Then decide, deliberately, whether your stateful workloads should be pinned to nodes at all. If they should not, the binding-mode question becomes a detail rather than a recurring operational tax, because the volume is no longer tied to a location the scheduler has to reason about.
Questions and Answers
What does “pod has unbound immediate PersistentVolumeClaims” mean?
It means the pod references at least one PersistentVolumeClaim that is still Pending, on a StorageClass using volumeBindingMode: Immediate. The scheduler’s volume-binding plugin rejects the pod before evaluating any node, so the node count in the message is misleading. The cause is always on the claim, and kubectl describe pvc <name> is where the real event is.
How do I fix an unbound immediate PersistentVolumeClaim?
Work through the claim, not the nodes. Confirm exactly one default StorageClass exists or that the claim names one; check the CSI controller is running and authorized; compare the bound volume’s zone against the pod’s node constraints; verify the requested access mode is one the driver supports; and if the PV was created by hand, check capacity, access modes, class, and selector all match. For a lasting fix on node-local or zonal storage, switch the StorageClass to WaitForFirstConsumer.
Should I always use WaitForFirstConsumer instead of Immediate?
On node-local or per-zone storage, yes: it lets the scheduler choose the node first and provisions the volume where the pod already is, so topology conflicts cannot happen. But it does not make the volume portable. On node-local storage the resulting PV is pinned to one machine by nodeAffinity, so you trade a first-deploy error for an unschedulable pod after node loss. Against a shared pool reachable from every node, neither mode introduces pinning, so the choice stops mattering.
Does simplyblock avoid this error?
Simplyblock removes the topology variant of it, which is the one that recurs. Volumes are served from one pool over NVMe/TCP or NVMe/RoCE and carry no node affinity, so binding a claim never commits the pod to a node or a zone, capacity is drawn from the pool rather than one machine’s free space, and ReadWriteMany block comes from the same StorageClass as ReadWriteOnce. The configuration causes remain possible, since a missing default StorageClass or a down CSI controller will stall any driver, but they are one-time setup faults rather than an ongoing constraint on scheduling.
Why is the pod still Pending after the PVC shows Bound?
That combination is the topology conflict, cause 3 above. The claim bound successfully, but to a volume in a zone or on a node the pod is not allowed to run on, because Immediate mode placed the volume before the scheduler saw the pod’s constraints. Nothing resolves this on its own. Either relax the pod’s constraints, or delete the claim and recreate it under a WaitForFirstConsumer class so placement drives provisioning rather than the reverse.