Challenge, Medium,  on  Kubernetes

Kubernetes - PersistentVolume Static Binding Patterns

In static provisioning an administrator creates PVs manually, before any PVC requests one. When a PVC is created, Kubernetes looks for an existing PV with enough capacity, a compatible access mode, and a matching storageClassName, and binds the two together. No provisioner runs and no PV gets created automatically.

This challenge covers three patterns that control how that match happens, plus the WaitForFirstConsumer binding mode for node-local storage.


Task 1 - Generic Match

Setting storageClassName: "" on both the PV and PVC bypasses the DefaultStorageClass admission controller and dynamic provisioning. Kubernetes binds the PVC to the first available PV that has sufficient capacity and a matching access mode.

Omitting storageClassName is not the same as setting it to "". A PVC without the key gets the default injected at admission time, and the control plane keeps it in sync if the default StorageClass changes later. Setting storageClassName: "" opts out of that behavior. The PVC keeps its empty class no matter what the cluster default is.

Steps:

  • Create a PV named pv-generic with storageClassName: "", type hostPath, path /mnt/static/generic, capacity 500Mi, access mode ReadWriteOnce
  • Create a PVC named pvc-generic with storageClassName: "", capacity 500Mi, access mode ReadWriteOnce
  • Observe pvc-generic bind to pv-generic

Task 2 - Label Selector Match

A PVC can narrow which PV it binds to using spec.selector.matchLabels. Kubernetes only considers PVs that carry all the listed labels. This lets you target a specific tier or quality of pre-provisioned storage without using separate StorageClass names.

Steps:

  • Create a PV named pv-selector with label disk-type: fast, storageClassName: "", type hostPath, path /mnt/static/selector, capacity 500Mi, access mode ReadWriteOnce
  • Create a PVC named pvc-selector with storageClassName: "", a label selector that matches PVs labeled disk-type: fast, capacity 500Mi, access mode ReadWriteOnce
  • Observe pvc-selector bind to pv-selector

Task 3 - Bidirectional Binding

When Kubernetes binds a PVC to a PV it writes claimRef on the PV to mark it as taken. You can set claimRef manually before the PVC exists to pre-reserve the PV - no other claim can take it while it waits. The other side of the lock is spec.volumeName on the PVC, which ensures the PVC only binds to that specific PV and not to any other eligible one.

Steps:

  • Create a PV named pv-bidir with a claimRef that points to a PVC named pvc-bidir in the default namespace (both name and namespace are required on claimRef, otherwise the PV cannot match the PVC), storageClassName: "", type hostPath, path /mnt/static/bidir, capacity 500Mi, access mode ReadWriteOnce
  • Create a PVC named pvc-bidir with volumeName set to pv-bidir, storageClassName: "", capacity 500Mi, access mode ReadWriteOnce
  • Observe both sides bound to each other

Task 4 - Local PV with WaitForFirstConsumer

hostPath PVs have no node affinity. The scheduler does not know which node the data is on and can place a pod anywhere, causing a mount failure if the pod lands on the wrong node.

The local volume type requires spec.nodeAffinity, which tells the scheduler exactly where the data lives. Pairing it with a StorageClass that uses volumeBindingMode: WaitForFirstConsumer delays PVC binding until a pod requests it. The scheduler picks a node for the pod first, checks that a PV exists there, then binds the PVC to that PV.

Steps:

  • Create a StorageClass sc-local-wfc with provisioner: kubernetes.io/no-provisioner and volumeBindingMode: WaitForFirstConsumer
  • Create a PV pv-local of type local, path /mnt/disks/local, storageClassName: sc-local-wfc, nodeAffinity pinned to cplane-01, capacity 1Gi, access mode ReadWriteOnce
  • Create a PVC pvc-local with storageClassName: sc-local-wfc, capacity 1Gi, access mode ReadWriteOnce
  • Confirm pvc-local stays Pending despite the matching PV existing
  • Create a pod local-pod (image busybox, command sleep 3600) consuming pvc-local at /data, no nodeSelector or nodeName
  • Confirm the pod lands on cplane-01 and pvc-local binds