🎤Speaking at KubeCon EU 2026Lessons Learned Orchestrating Multi-Tenant GPUs on OpenShift AIView Session
Troubleshooting intermediate ⏱ 15 minutes K8s 1.28+

How to Manage Kubernetes Finalizers and Stuck Resources

Understand and manage finalizers for controlled resource deletion. Handle stuck resources and implement custom cleanup logic.

By Luca Berton

How to Manage Kubernetes Finalizers and Stuck Resources

Finalizers prevent resources from being deleted until cleanup tasks complete. They’re used by controllers to ensure proper cleanup of dependent resources, but can cause resources to get stuck.

Understanding Finalizers

# Finalizers are metadata strings that block deletion
apiVersion: v1
kind: Namespace
metadata:
  name: my-namespace
  finalizers:
    - kubernetes  # Blocks deletion until removed
# How deletion works with finalizers:
# 1. kubectl delete issued
# 2. API server sets deletionTimestamp
# 3. Resource enters "Terminating" state
# 4. Controllers see deletionTimestamp, perform cleanup
# 5. Controllers remove their finalizer
# 6. When all finalizers removed, resource is deleted

View Finalizers

# Check finalizers on a resource
kubectl get namespace my-namespace -o jsonpath='{.metadata.finalizers}'

# View in YAML
kubectl get pv my-pv -o yaml | grep -A 5 finalizers

# Find resources with specific finalizer
kubectl get namespaces -o json | jq '.items[] | select(.metadata.finalizers != null) | .metadata.name'

# Check deletionTimestamp (indicates deletion in progress)
kubectl get namespace my-namespace -o jsonpath='{.metadata.deletionTimestamp}'

# Find all Terminating namespaces
kubectl get namespaces --field-selector status.phase=Terminating

Common Finalizers

# Namespace finalizer
kubernetes  # Ensures all namespaced resources are deleted first

# PersistentVolume finalizers
kubernetes.io/pv-protection  # Prevents deletion while bound to PVC
external-attacher/csi-driver  # CSI driver cleanup

# PersistentVolumeClaim finalizers
kubernetes.io/pvc-protection  # Prevents deletion while in use by pod

# Custom Resource finalizers
foregroundDeletion  # Garbage collection
orphan              # Leave dependents

Diagnose Stuck Namespace

# Namespace stuck in Terminating
kubectl get namespace stuck-ns

# Check what's blocking
kubectl get namespace stuck-ns -o yaml

# Look for:
# - finalizers that haven't been removed
# - deletionTimestamp set (deletion started)
# - status.conditions for errors

# Find remaining resources in namespace
kubectl api-resources --verbs=list --namespaced -o name | \
  xargs -I {} kubectl get {} -n stuck-ns --ignore-not-found

# Check for stuck API resources
kubectl get apiservices | grep -v Available

Remove Stuck Finalizers

# WARNING: Only do this after understanding why finalizer exists
# Removing finalizers bypasses cleanup - may leave orphaned resources

# Remove finalizer from namespace (API method)
kubectl get namespace stuck-ns -o json | \
  jq '.spec.finalizers = []' | \
  kubectl replace --raw "/api/v1/namespaces/stuck-ns/finalize" -f -

# Remove finalizer from other resources
kubectl patch pv my-pv -p '{"metadata":{"finalizers":null}}' --type=merge

# Using kubectl edit
kubectl edit namespace stuck-ns
# Remove the finalizers array and save

# Patch to remove specific finalizer
kubectl patch configmap my-cm -p '{"metadata":{"finalizers":null}}' --type=merge

Fix Stuck PV/PVC

# PVC stuck in Terminating (still in use)
kubectl get pods --all-namespaces -o json | \
  jq '.items[] | select(.spec.volumes[]?.persistentVolumeClaim.claimName == "my-pvc") | .metadata.name'

# Remove the pod using the PVC first
kubectl delete pod <pod-using-pvc>

# If still stuck, remove finalizer (after confirming no usage)
kubectl patch pvc my-pvc -p '{"metadata":{"finalizers":null}}' --type=merge

# PV stuck after PVC deleted
kubectl patch pv my-pv -p '{"metadata":{"finalizers":null}}' --type=merge

Fix Stuck CRDs

# CRD stuck in deletion
kubectl get crd stuck-crd.example.com -o yaml

# Delete all instances of the CRD first
kubectl delete <crd-kind> --all -A

# If controller is gone, remove finalizer
kubectl patch crd stuck-crd.example.com -p '{"metadata":{"finalizers":null}}' --type=merge

Foreground vs Background Deletion

# Background deletion (default)
# - Owner deleted immediately
# - Dependents deleted asynchronously
kubectl delete deployment my-deploy

# Foreground deletion
# - Owner waits for dependents to delete
# - Uses foregroundDeletion finalizer
kubectl delete deployment my-deploy --cascade=foreground

# Orphan dependents
# - Owner deleted, dependents kept
kubectl delete deployment my-deploy --cascade=orphan

Debug Finalizer Issues

# Check controller logs for finalizer
kubectl logs -n kube-system deployment/kube-controller-manager | grep -i finalizer

# For custom controllers
kubectl logs deployment/my-controller | grep -i cleanup

# Check events
kubectl get events --field-selector involvedObject.name=stuck-resource

# Check if API services are healthy (can block namespace deletion)
kubectl get apiservices | grep False

Automated Cleanup Script

#!/bin/bash
# force-delete-namespace.sh
NS=$1

if [ -z "$NS" ]; then
  echo "Usage: $0 <namespace>"
  exit 1
fi

echo "Checking namespace $NS..."

# Check if namespace exists and is terminating
STATUS=$(kubectl get namespace $NS -o jsonpath='{.status.phase}' 2>/dev/null)
if [ "$STATUS" != "Terminating" ]; then
  echo "Namespace is not in Terminating state (status: $STATUS)"
  exit 1
fi

# Remove finalizers
echo "Removing finalizers from namespace $NS..."
kubectl get namespace $NS -o json | \
  jq 'del(.spec.finalizers)' | \
  kubectl replace --raw "/api/v1/namespaces/$NS/finalize" -f -

echo "Done. Check namespace status:"
kubectl get namespace $NS

Prevent Stuck Resources

# Set appropriate timeouts in controllers
# Implement robust cleanup logic
# Handle errors gracefully

# Example: Timeout for cleanup operations
spec:
  terminationGracePeriodSeconds: 30
# Monitor for stuck resources
kubectl get namespaces --field-selector status.phase=Terminating

# Prometheus alert for long-terminating resources
# time() - kube_namespace_status_phase{phase="Terminating"} > 3600

Best Practices

1. Understand before removing
   - Finalizers exist for a reason
   - Removing may leave orphaned resources
   - Check what controller owns the finalizer

2. Fix root cause first
   - Delete dependent resources properly
   - Ensure controllers are running
   - Check for unhealthy API services

3. Custom controllers
   - Always handle cleanup errors gracefully
   - Implement timeouts
   - Log cleanup progress

4. Monitor stuck resources
   - Alert on Terminating state > threshold
   - Regular audit of orphaned resources

Summary

Finalizers ensure proper cleanup before resource deletion by blocking removal until controllers complete their work. When resources get stuck in Terminating state, diagnose by checking remaining finalizers, dependent resources, and API service health. Remove finalizers only after understanding the implications - this bypasses cleanup and may leave orphaned resources. For stuck namespaces, use the finalize API endpoint. Always try to fix the root cause (delete dependents, restart controllers) before force-removing finalizers.


📘 Go Further with Kubernetes Recipes

Love this recipe? There’s so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.

Inside the book, you’ll master:

  • ✅ Production-ready deployment strategies
  • ✅ Advanced networking and security patterns
  • ✅ Observability, monitoring, and troubleshooting
  • ✅ Real-world best practices from industry experts

“The practical, recipe-based approach made complex Kubernetes concepts finally click for me.”

👉 Get Your Copy Now — Start building production-grade Kubernetes skills today!

#finalizers #deletion #cleanup #stuck-resources #terminating

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.