🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Kubernetes Namespace Stuck in Terminating State — Fix

Your namespace has been Terminating for hours. kubectl delete won't help. Here's exactly why this happens and how to force-remove a stuck namespace safely.

DevOpsBoysJun 3, 20264 min read
Share:Tweet

You deleted a namespace. It's been "Terminating" for 30 minutes. kubectl delete namespace hangs. Recreating it gives an error. This is one of the most common Kubernetes stuck states.

Here's the fix.


Why Namespaces Get Stuck

When you delete a namespace, Kubernetes needs to delete all resources inside it. It gets stuck when:

  1. Finalizers — resources have finalizers that haven't been removed
  2. CRD resources — custom resources whose controller is already deleted
  3. API aggregation issues — API service is unavailable, can't clean up

The namespace enters Terminating but can't complete because something blocks it.


Diagnose

bash
# Check what's blocking
kubectl get namespace my-namespace -o json | jq '.spec.finalizers'
# ["kubernetes"]  ← This is normal, means cleanup in progress
 
# Check for stuck resources
kubectl api-resources --verbs=list --namespaced -o name | \
  xargs -I{} kubectl get {} -n my-namespace --ignore-not-found 2>/dev/null
 
# Check for resources with finalizers
kubectl get all -n my-namespace -o json | \
  jq '.items[] | select(.metadata.finalizers != null) | {kind: .kind, name: .metadata.name, finalizers: .metadata.finalizers}'

Fix 1: Remove Resource Finalizers (Most Common)

bash
# Find resources with finalizers in the stuck namespace
for resource in $(kubectl api-resources --verbs=list --namespaced -o name); do
  kubectl get $resource -n my-namespace -o json 2>/dev/null | \
    jq -r '.items[] | select(.metadata.finalizers != null) | "\(.kind)/\(.metadata.name)"'
done
 
# Remove finalizer from a specific resource
kubectl patch <resource-type>/<resource-name> -n my-namespace \
  -p '{"metadata":{"finalizers":null}}' \
  --type=merge

Example — removing finalizer from a stuck PVC:

bash
kubectl patch pvc my-pvc -n my-namespace \
  -p '{"metadata":{"finalizers":null}}' \
  --type=merge

Fix 2: Force-Delete the Namespace (Nuclear Option)

If fixing individual resources is too tedious:

bash
# Export current namespace state
kubectl get namespace my-namespace -o json > /tmp/ns.json
 
# Remove the finalizer from the spec
cat /tmp/ns.json | jq 'del(.spec.finalizers)' > /tmp/ns-clean.json
 
# Use kubectl proxy to bypass API
kubectl proxy &
PROXY_PID=$!
 
# Finalize the namespace via API
curl -s -o /dev/null -w "%{http_code}" \
  -H "Content-Type: application/json" \
  -X PUT \
  --data-binary @/tmp/ns-clean.json \
  http://127.0.0.1:8001/api/v1/namespaces/my-namespace/finalize
 
kill $PROXY_PID

This tells Kubernetes "cleanup is done" even if it's not — the namespace deletes immediately.


Fix 3: CRD Resources Without Controllers

If you deleted a CRD (Custom Resource Definition) before deleting the custom resources in namespaces, those resources are orphaned with no controller to handle their finalizers.

bash
# Find stuck CRD resources
kubectl get <crd-resource-name> -n my-namespace
 
# Remove finalizers
kubectl patch <crd-resource-name>/<resource-name> -n my-namespace \
  --type json \
  -p '[{"op":"remove","path":"/metadata/finalizers"}]'

Or patch all instances:

bash
for resource in $(kubectl get <crd-resource-name> -n my-namespace -o name); do
  kubectl patch $resource -n my-namespace \
    -p '{"metadata":{"finalizers":null}}' --type=merge
done

Fix 4: APIService Issue

Sometimes an APIService (extended API) is unavailable, blocking namespace cleanup:

bash
# Check APIService health
kubectl get apiservice | grep False
# v1beta1.metrics.k8s.io   False (ServiceNotFound)  ← This can block namespace deletion
 
# Delete broken APIService if it's no longer needed
kubectl delete apiservice v1beta1.metrics.k8s.io

Automate with a Script

bash
#!/bin/bash
# force-delete-namespace.sh
 
NAMESPACE=$1
 
if [ -z "$NAMESPACE" ]; then
  echo "Usage: $0 <namespace>"
  exit 1
fi
 
echo "Force-deleting namespace: $NAMESPACE"
 
# Remove finalizers from all resources in namespace
echo "Removing finalizers from resources..."
for resource_type in $(kubectl api-resources --verbs=list --namespaced -o name 2>/dev/null); do
  for resource in $(kubectl get "$resource_type" -n "$NAMESPACE" -o name 2>/dev/null); do
    kubectl patch "$resource" -n "$NAMESPACE" \
      -p '{"metadata":{"finalizers":null}}' \
      --type=merge 2>/dev/null
  done
done
 
# Force finalize namespace
echo "Finalizing namespace..."
kubectl get namespace "$NAMESPACE" -o json | \
  jq 'del(.spec.finalizers)' | \
  kubectl replace --raw "/api/v1/namespaces/$NAMESPACE/finalize" -f -
 
echo "Done. Check: kubectl get namespace $NAMESPACE"
bash
chmod +x force-delete-namespace.sh
./force-delete-namespace.sh my-stuck-namespace

Prevention

Always delete CRD resources before deleting CRDs:

bash
# Delete resources first
kubectl delete <custom-resource-name> --all -n my-namespace
 
# Then delete the CRD
kubectl delete crd <crd-name>

When using Helm with CRDs: Helm doesn't delete CRD resources automatically. Clean up manually before helm uninstall.


The nuclear option (patching namespace finalizers via API) works every time but bypasses Kubernetes' cleanup mechanisms. Use it only when the namespace truly needs to go and you've verified there's no real cleanup blocking it.

Master Kubernetes troubleshooting with real cluster labs at KodeKloud.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments