PVC Protection Finalizers

You run kubectl delete pvc and the claim sits in Terminating, so what is holding it? The kubernetes.io/pvc-protection finalizer, added at creation: while a Pod using the claim still exists the deletionTimestamp is set but the delete never completes, because pulling storage from a running Pod would break it. Terminating is only a display: the phase stays Bound, and only the last consuming Pod leaving frees it. The fix is finding that Pod, even a finished one.

Play the animation 7 steps, animated, best on a wide screen

PVC Protection Finalizers diagram: Why a deleted PersistentVolumeClaim sits in Terminating

PVC Protection Finalizers, step by step

  1. The claim is a handle, and the volume behind it is what stores the bytes. Kubelet resolved data-claim to data-vol and mounted it at slash data, so the app writes through the claim into the disk. The finalizer guards the claim for as long as this Pod object exists.

    Play from step 1
  2. You run kubectl delete pvc data-claim. The API accepts it and writes a deletionTimestamp onto the object. That is all a delete does when finalizers are present: it is a request, recorded on the object, and nothing has been removed yet. Your terminal prints deleted anyway and then hangs, because kubectl waits for finalizers by default.

    Play from step 2
  3. Now watch what does not happen. The finalizers list still holds kubernetes.io/pvc-protection, so the API server does not complete the delete and the object stays exactly where it was. The Pod never noticed: the volume is still mounted and the app is still writing to it, straight through a claim you already deleted.

    Play from step 3
  4. The protection is deliberate. Taking the claim away under a running Pod would pull the mount out from beneath it and could lose writes that are still in flight. Deletion also closes the claim to new users: a new Pod that names it is left unschedulable and stays Pending, without ever holding the claim.

    Play from step 4
  5. So remove the reason. The Pod is deleted, or it finishes and its Pod object is removed, and as it goes Kubelet unmounts the volume and the claim loses its last consumer. This is the event the protection controller has been waiting for the whole time.

    Play from step 5
  6. The pvc-protection controller checks whether any Pod still uses the claim, finds none, and does the job it was waiting for: it removes the finalizer from the object. The finalizers list is now empty, the last thing the outstanding delete was waiting for, so the kubectl command hanging since step 2 is about to return.

    Play from step 6
  7. The moment the finalizers list is empty, the API server completes the delete it accepted five steps ago and the record leaves ETCD. The disk itself is a separate question, settled by the reclaim policy on the volume. So for a stuck Terminating claim, find the Pod object that still names it, finished or not: kubectl describe pvc lists it under Used By.

    Play from step 7

Sources