# StorageClass Uses Immediate Binding

> Flags StorageClasses using Immediate binding, which can provision zonal volumes where the consuming pod cannot be scheduled.

Source: https://zop.dev/integrations/kubernetes/recommendations/storageclass-uses-immediate-binding

---

## Early provisioning ignores where the pod will run

The [storage classes documentation](https://kubernetes.io/docs/concepts/storage/storage-classes/)
describes `Immediate` as binding and provisioning the volume as soon as the
PersistentVolumeClaim is created. For topology-constrained storage that is not reachable from
every node, the volume is created without knowing the pod's scheduling requirements, which can
leave the pod unschedulable. On a multi-zone cluster backed by zonal storage, the disk can
appear in one zone while the only nodes that fit the pod are in another.

`WaitForFirstConsumer` avoids that. It delays binding and provisioning until a pod that uses the
claim is created, then provisions to match the pod's constraints: resource requests, node
selectors, affinity and anti-affinity, and taints and tolerations. When the field is left out of
a StorageClass, the API server fills in `Immediate`.

## Listing classes that bind immediately

```bash
kubectl get storageclasses -o json | jq -r '
  .items[]
  | select((.volumeBindingMode // "Immediate") == "Immediate")
  | "\(.metadata.name) provisioner=\(.provisioner)"'
```

`kubectl get storageclass` marks the cluster default with `(default)`, which is the class most
new claims will use.

## Reading one field on the class

ZopNight checks the binding mode recorded for each StorageClass and fires only when it reads
`Immediate`. A class set to `WaitForFirstConsumer` is left alone, as is a class where the mode was
not collected. Because the API server stores the default explicitly, classes that never set the
field still show `Immediate` and are flagged. The check does not look at the cluster's zone layout.

## Scope of the finding

The finding is about the class, not the claims already bound through it. Existing volumes stay
where they were provisioned. Claims stuck waiting for a volume are reported by
[Unbound PVC](https://zop.dev/integrations/kubernetes/recommendations/unbound-pvc), and classes that cannot grow
their volumes by
[StorageClass disallows volume expansion](https://zop.dev/integrations/kubernetes/recommendations/storageclass-disallows-volume-expansion).

## Pods that cannot start, not a bill

There is no saving attached. The risk is StatefulSet or Deployment pods stuck `Pending` because
their freshly provisioned volume sits in a zone with no suitable node.

## Switching to WaitForFirstConsumer

1. Export the class: `kubectl get storageclass <name> -o yaml`.
2. `volumeBindingMode` is immutable, so create a new class, or delete and recreate this one,
   with `volumeBindingMode: WaitForFirstConsumer` and the same provisioner and parameters.
3. If the old class was the default, move the
   `storageclass.kubernetes.io/is-default-class: "true"` annotation to the new one and set the
   old one to `"false"`.
4. Point new claims and StatefulSet volume claim templates at the new class.
5. Avoid `nodeName` in pods that use it. With `WaitForFirstConsumer`, `nodeName` bypasses the
   scheduler and the claim stays pending; use a `kubernetes.io/hostname` node selector instead.
