Full lesson
Explore the full explanation, examples, and visuals at your own pace.
Terminal: deleted, but still there
You deleted orders-0, kubectl said deleted, and minutes later the Pod still shows Terminating. The command even hangs, waiting. That's because the output is slightly misleading. In Kubernetes, deleting a Pod isn't an instant removal. It's a handshake, and something on the other side hasn't answered yet.
$ kubectl delete pod orders-0
pod "orders-0" deleted
# ...command keeps waiting
$ kubectl get pod orders-0
NAME READY STATUS RESTARTS AGE
orders-0 1/1 Terminating 0 3dNormal deletion is a handshake
When kubectl sends the delete, the API server doesn't remove the Pod. It sets a deletionTimestamp, and that's what you see as Terminating. The kubelet then sends SIGTERM to the container, waits for it to exit, and confirms it stopped. Only then, if no finalizers remain, does the API server remove the object. Deletion is a handshake.
Three common blockers
Orders-0 can get stuck in three common ways. A finalizer nobody removes, a node that can't report back, or a shutdown waiting out its grace period. Each one blocks a different sign-off in the handshake, so each needs a different check.
- A finalizer nobody removes
- A node that cannot report back
- A shutdown waiting out grace
Story 1: finalizers, the healthy path
A finalizer like example.com/cleanup is a lock owned by a controller. When you delete orders-0, the API server sets a deletionTimestamp, then checks the finalizer list. It keeps the object until the controller finishes cleanup and clears its entry. Only then is the object removed. When it works, you barely notice.
Controller gone: deletion waits forever
Now suppose the controller that owns example.com/cleanup is gone. Maybe it was uninstalled, it's crashing, or it lacks RBAC permission to patch the Pod. The deletionTimestamp is set, but nobody clears the finalizer. There's no timeout here, so orders-0 can stay Terminating indefinitely, even though its containers are healthy.
Story 2: node-b is unreachable
Now node-b goes NotReady, maybe it crashed or lost its network. kubectl sends the delete, and the API server records it. But the message to the kubelet on node-b never lands, so nobody confirms the containers stopped. Pod orders-0 stays Terminating. And the containers might still be running, we just can't see it.
Story 3: SIGTERM ignored, grace is 600s
In this last case, nothing is broken. The kubelet sends SIGTERM, the app ignores it, and terminationGracePeriodSeconds is 600 instead of the default 30. A preStop hook spends from that same budget. Only after ten minutes does SIGKILL arrive, and then the kubelet confirms the container stopped. It looks stuck, but it ends on its own.
Telling the three apart
Three commands, three suspects. First, look at the Pod's YAML: a non-empty finalizers list means a controller owes cleanup. Second, check the node: if node-b shows NotReady, the kubelet can't report. Third, read the events for Killing, then compare the grace period against how long the Pod has been deleting.
# 1. Finalizers present?
kubectl get pod orders-0 -o yaml | grep -A3 finalizers
# 2. Which node, and is it Ready?
kubectl get pod orders-0 -o wide
kubectl get node node-b
# 3. Events and configured grace period
kubectl describe pod orders-0
kubectl get pod orders-0 -o jsonpath='{.spec.terminationGracePeriodSeconds}'Match the evidence to the cause
Start from the stuck Pod and read the evidence. Finalizers set means a controller still owes cleanup. Node NotReady means the kubelet can't report that containers stopped. Within grace means the Pod simply needs more time. Each finding points to a different missing sign-off, and more than one can be true at once.
Force delete: read the warning
Force delete works, but read what it actually does. With grace period zero, kubectl removes the orders-0 object from the API without waiting for the kubelet to confirm anything. The warning says it plainly: the resource may continue to run on the cluster indefinitely. The record is gone. The process may not be.
$ kubectl delete pod orders-0 --force --grace-period=0
Warning: Immediate deletion does not wait for confirmation
that the running resource has been terminated. The resource
may continue to run on the cluster indefinitely.
pod "orders-0" force deletedTwo orders-0 Pods at once
Once the object is deleted, the StatefulSet sees orders-0 is missing and creates a new one. But on the partitioned node-b, the old orders-0 may still be running and writing. Now two copies share the same identity and the same data. Depending on the storage, that can corrupt it.
Fix the missing sign-off instead
Fix the missing sign-off instead. Bring the finalizer's controller back, and strip a finalizer by hand only once you understand its cleanup. Recover the node, or confirm it's down. For slow shutdowns, handle SIGTERM and size the grace period. Force delete last.
- Finalizer: restore or repair its controller
- Node: recover it, or confirm it's down
- Slow: handle SIGTERM, right-size grace
Find who hasn't signed off
Terminating means deletion requested, but someone hasn't signed off yet. The sequence shows the handshake: the API server marks the Pod deleting, the kubelet stops the container and confirms, and the object goes away only if no finalizers remain. So check finalizers, the node, and the grace period before forcing, because force delete skips confirmation without stopping anything.

