What Are Finalizers?
Finalizers are keys in a resourceβs metadata.finalizers array that tell Kubernetes: βDo not delete this object until I have cleaned up.β They act as pre-delete hooks that guarantee external dependencies are resolved before garbage collection.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
finalizers:
- kubernetes.io/pvc-protectionWhen you run kubectl delete, Kubernetes sets metadata.deletionTimestamp but does NOT remove the object. The controller responsible for the finalizer must:
- Perform its cleanup logic
- Remove its finalizer from the array
- Only then does Kubernetes actually delete the object
Common Finalizers You Will Encounter
| Finalizer | Purpose | Controller |
|---|---|---|
kubernetes.io/pvc-protection | Prevents PVC deletion while mounted | PVC Protection Controller |
kubernetes.io/pv-protection | Prevents PV deletion while bound | PV Protection Controller |
foregroundDeletion | Waits for dependents to delete first | Garbage Collector |
orphan | Leaves dependents running | Garbage Collector |
external-dns | Removes DNS records before ingress deletion | ExternalDNS |
helm.sh/hook-delete-policy | Helm release cleanup | Helm/Tiller |
| Custom finalizers | Operator-specific cleanup | Custom operators |
Why Resources Get Stuck in Terminating
A resource stays in Terminating state when:
- Controller is down β the controller that should process the finalizer is not running
- Controller is broken β the cleanup logic is failing (e.g., cannot reach external API)
- Namespace deletion β namespace is terminating but contains resources with unprocessable finalizers
- Orphaned finalizer β a CRD was deleted before its controller could clean up
Check a stuck resource:
kubectl get pod stuck-pod -o jsonpath='{.metadata.finalizers}'
# ["my-operator.io/cleanup"]
kubectl get pod stuck-pod -o jsonpath='{.metadata.deletionTimestamp}'
# 2026-06-01T10:30:00ZHow to Unstick Resources Safely
Step 1: Identify What Is Stuck
# Find all resources stuck in Terminating
kubectl get all --all-namespaces | grep Terminating
# For namespaces specifically
kubectl get namespaces | grep Terminating
# Check finalizers on a specific resource
kubectl get namespace stuck-ns -o json | jq '.spec.finalizers'
kubectl get namespace stuck-ns -o json | jq '.metadata.finalizers'Step 2: Understand Why
Before removing finalizers, understand what cleanup they represent:
# Check events for the resource
kubectl describe namespace stuck-ns
# Check if the responsible controller is running
kubectl get pods -n kube-system | grep controller
# Check operator logs
kubectl logs -n operator-system deploy/my-operatorStep 3: Remove Finalizers (When Safe)
For regular resources:
kubectl patch pod stuck-pod -p '{"metadata":{"finalizers":null}}' --type=mergeFor namespaces (requires API call):
# Export namespace spec
kubectl get namespace stuck-ns -o json > ns.json
# Remove finalizers from spec
jq '.spec.finalizers = []' ns.json > ns-clean.json
# Replace via API
kubectl replace --raw "/api/v1/namespaces/stuck-ns/finalize" -f ns-clean.jsonFor CRDs:
kubectl patch crd my-crd.example.com \
-p '{"metadata":{"finalizers":[]}}' --type=mergeStep 4: Bulk Cleanup
When multiple resources are stuck (common during CRD/operator uninstall):
# Remove finalizers from all resources of a type in a namespace
kubectl get configmaps -n stuck-ns -o name | while read cm; do
kubectl patch "$cm" -n stuck-ns \
-p '{"metadata":{"finalizers":null}}' --type=merge
done
# Nuclear option: all resources in a namespace
for resource in $(kubectl api-resources --namespaced -o name); do
kubectl get "$resource" -n stuck-ns -o name 2>/dev/null | while read obj; do
kubectl patch "$obj" -n stuck-ns \
-p '{"metadata":{"finalizers":null}}' --type=merge 2>/dev/null
done
doneWriting Your Own Finalizer
If you are building an operator, here is the pattern:
const myFinalizer = "mycompany.io/cleanup"
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
obj := &v1alpha1.MyResource{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Resource is being deleted
if !obj.DeletionTimestamp.IsZero() {
if containsString(obj.Finalizers, myFinalizer) {
// Perform cleanup
if err := r.cleanupExternalResources(obj); err != nil {
return ctrl.Result{}, err
}
// Remove finalizer
obj.Finalizers = removeString(obj.Finalizers, myFinalizer)
if err := r.Update(ctx, obj); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// Add finalizer if not present
if !containsString(obj.Finalizers, myFinalizer) {
obj.Finalizers = append(obj.Finalizers, myFinalizer)
if err := r.Update(ctx, obj); err != nil {
return ctrl.Result{}, err
}
}
// Normal reconciliation logic
return ctrl.Result{}, nil
}Prevention: Best Practices
- Always test operator uninstall β simulate removing your operator and verify cleanup completes
- Add timeouts to cleanup logic β do not let finalizers hang forever
- Log finalizer actions β make it obvious what cleanup is happening
- Document your finalizers β other team members need to know what they do
- Use owner references β prefer garbage collection over finalizers when possible
# Owner references auto-delete dependents (no finalizer needed)
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
ownerReferences:
- apiVersion: apps/v1
kind: Deployment
name: my-app
uid: d9607e19-f88f-11e6-a518-42010a800195Debugging Checklist
# 1. What finalizers are present?
kubectl get <resource> <name> -o jsonpath='{.metadata.finalizers}'
# 2. When was deletion requested?
kubectl get <resource> <name> -o jsonpath='{.metadata.deletionTimestamp}'
# 3. Is the controller running?
kubectl get pods -A | grep -i <operator-name>
# 4. What do the controller logs say?
kubectl logs -n <operator-ns> deploy/<operator> --tail=50
# 5. Are there events?
kubectl describe <resource> <name> | tail -20Deepen Your Kubernetes Skills
If you found this article useful, check out my books for hands-on Kubernetes mastery:
- Kubernetes Recipes β A practical guide for container orchestration and deployment with real-world patterns
- Ansible for Kubernetes by Example β Automate Kubernetes cluster operations with Ansible playbooks
Both books follow the same practical, example-driven approach you see in my articles.