Cascading Deletion and Finalizers

You run kubectl delete and the API answers right away, so why is the object still there and kubectl still waiting? By default the API drops the owner at once and the Garbage collector walks ownerReferences to clean up the dependents afterwards. Ask for foreground cascade instead and the object is only stamped for deletion until the dependents that carry blockOwnerDeletion are gone and the finalizers clear. A finalizer that never clears is how an object sticks.

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

Cascading Deletion and Finalizers diagram: How a cascading delete unwinds through finalizers

Cascading Deletion and Finalizers, step by step

  1. You run "kubectl delete deployment my-app --cascade=foreground". The client sends an HTTP DELETE to /apis/apps/v1/.../deployments/my-app on the API with propagationPolicy=Foreground in the request body.

    Play from step 1
  2. The API does not remove the object. It sets metadata.deletionTimestamp and adds the foregroundDeletion finalizer, then commits the change to ETCD via Raft at rv=843. The Deployment is now marked for deletion but still exists in cluster state.

    Play from step 2
  3. ETCD acks the committed write back to the API, and the API returns HTTP 200 OK to kubectl with the stamped object in the body. The call has returned, but kubectl waits by default until the object is gone, and the object lifecycle is only just beginning.

    Play from step 3
  4. The API broadcasts a MODIFIED event for the Deployment to its watchers. The Deployment controller sees the deletionTimestamp and stops issuing rollouts. The Garbage collector walks the ownerReferences and DELETEs ReplicaSet my-app-7d4 in foreground, then Pod my-app-7d4-x2k9p under it, each stamped with a deletionTimestamp rather than removed from ETCD yet.

    Play from step 4
  5. The Kubelet on Node-1 has a filtered watch for Pods bound to it. The API streams a MODIFIED event for my-app-7d4-x2k9p carrying its new deletionTimestamp down that watch to Node-1, and the Kubelet starts the termination procedure.

    Play from step 5
  6. The terminationGracePeriodSeconds budget (30s by default) started when the Pod was stamped. The container gets SIGQUIT, the STOPSIGNAL the nginx image sets, SIGTERM otherwise, then SIGKILL only if it outlives the timer. Once it stops, the Kubelet sets the terminal phase and sends a DELETE with grace 0. The Graceful Pod Shutdown card covers the budget.

    Play from step 6
  7. The grace 0 DELETE removes the Pod record from ETCD. With no Pod left under it, the Garbage collector clears the foregroundDeletion finalizer off the ReplicaSet, then off the Deployment, and as each list empties the API removes that record too, the Deployment last at rv=856. That completes the delete kubectl sent on step 1, so kubectl stops waiting.

    Play from step 7

Sources