
Kubernetes Namespace 卡在 Terminating 删不掉:finalizer 原理与安全清理全流程kubectl delete namespace dev敲下去,命令没报错,但过了十分钟这个 namespace 还在,状态死死钉在Terminating。再删一次也没用,kubectl delete直接 hang 住。这是运维绕不开的一道坎,而网上一搜全是「强删 finalizer」的一行命令,复制粘贴上去 namespace 是没了,但很可能留下一堆孤儿资源和后患。这篇把卡住的真正原因讲透,再给一套安全的清理流程。先看现象$ kubectl get ns NAME STATUS AGE dev Terminating 32m想看它到底卡在哪,先别急着强删,describe一下往往就有答案:$ kubectl get ns dev-ojsonpath{.status.conditions}|jq正常情况下能看到类似这样的 condition:[{type:NamespaceDeletionContentFailure,status:True,reason:ContentDeletionFailed,message:Failed to delete all resource types, 1 remaining: unable to retrieve the complete list of server APIs: metrics.k8s.io/v1beta1: the server is currently unable to handle the request}]看到没,message 已经把凶手指出来了——有一个metrics.k8s.io/v1beta1的 API 拉不到。这是最典型的病根,后面会讲。原理:namespace 删除是「两阶段」的理解卡住的前提,是搞清楚 namespace 为什么不能秒删。K8s 里 namespace 的删除分两步,靠finalizer这个机制串起来:你发起 delete,API Server 不会立刻抹掉这个对象,而是给它打上deletionTimestamp,状态变Terminating。此时对象还在,因为它的spec.finalizers里还挂着一个kubernetes的 finalizer。namespace controller 看到这个 timestamp,开始逐个删除该 namespace 下的所有资源(pod、svc、cm、pvc……)。全删干净后,它才会把kubernetes这个 finalizer 从spec.finalizers里摘掉。一旦 finalizer 列表空了,API Server 才真正把 namespace 对象从 etcd 里删除。所以「卡在 Terminating」第 2 步没干完,finalizer 摘不掉。finalizer 就像一把锁,它的设计初衷是「资源被删前必须先做完清理」,防止你留下孤儿资源。卡住不是 bug,恰恰是这把锁在尽职地拦着你。三类常见病根病根一:某个 API 拉不到(最常见)namespace controller 删资源前,要先向 API Server 问「这个 namespace 下所有类型的资源都有哪些」。它会遍历所有注册的 API Group。只要有一个APIService 处于不可用状态(比如你装过 metrics-server 或某个自定义 apiserver,后来 Pod 挂了但 APIService 注册还在),这个「列举所有资源类型」的动作就整体失败,controller 不敢贸然删,只能卡住。查有没有坏掉的 APIService:$ kubectl get apiservices|grep-vTrue NAME SERVICE AVAILABLE AGE v1beta1.metrics.k8s.io kube-system/metrics-server False(MissingEndpoints)40dAVAILABLE是False的就是它。正确修法是修好或删掉这个坏掉的 APIService,而不是去动 namespace 的 finalizer:# 如果 metrics-server 已经不用了,直接删掉这个注册kubectl delete apiservice v1beta1.metrics.k8s.io删完之后,被卡的 namespace 往往几秒内自己就消失了——因为 controller 终于能列全资源了。病根二:namespace 下有带自定义 finalizer 的资源不只是 namespace 本身有 finalizer,它里面的资源也可能有。比如某个 CRD 对象挂了个第三方 controller 的 finalizer,而那个 controller 已经被卸载了,没人来摘这个 finalizer,于是这个资源删不掉,连累整个 namespace 卡住。把 namespace 下所有还没删掉的资源捞出来看看:# 列出该 namespace 下所有类型里还残留的对象kubectl api-resources--verbslist--namespaced-oname\|xargs-n1kubectl get --show-kind --ignore-not-found-ndev如果发现某个残留对象,查它的 finalizer:kubectl getresource/name-ndev-ojsonpath{.metadata.finalizers}确认那个 finalizer 对应的 controller 确实已经不存在、清理动作也不需要了,才可以手动摘掉它:kubectl patchresource/name-ndev\--typemerge-p{metadata:{finalizers:null}}单个资源的 finalizer 摘掉后它就能删了,namespace 也就跟着能删完。病根三:apiserver 和 etcd 之间有残留少数情况是资源已经删了,但 controller 状态没刷新。这种一般重启一下 namespace controller(它在 kube-controller-manager 里)或等一会儿会自愈,不用动手。万不得已:强摘 namespace 的 finalizer(理解代价再用)如果确认孤儿资源不重要、也接受可能留下少量残留,才走这一步。注意:直接kubectl edit改spec.finalizers是不生效的,API Server 会忽略普通更新路径对 finalizers 的修改,必须走finalize子资源接口。安全做法是用kubectl replace --raw打 finalize 端点:# 1. 把当前 namespace 导出并去掉 finalizerskubectl get ns dev-ojson\|jqdel(.spec.finalizers)dev-ns.json# 2. 起一个本地代理,避免直接怼线上 apiserverkubectl proxy--port8901# 3. 通过 finalize 子资源接口提交(注意 URL 末尾是 /finalize)curl-k-HContent-Type: application/json-XPUT\--data-binary dev-ns.json\http://127.0.0.1:8901/api/v1/namespaces/dev/finalize提交后 namespace 会立刻消失。但要清醒:这只是把「锁」硬撬开了,它本该保护的清理动作并没有真正完成。如果病根是「病根一」那种 API 不可用,强删之后那个 namespace 里可能还有没被回收的资源在别处留着引用,或者 PV 没解绑。所以强删是最后手段,不是首选。小结namespace 删除是两阶段的:先删光内部资源,再摘kubernetesfinalizer,最后才真正删除对象。卡在Terminating 内部资源没删干净,finalizer 摘不掉,这是 finalizer 这把「保护锁」在起作用,不是 bug。排查第一步永远是看 conditions/describe,而不是直接强删。kubectl get ns name -o jsonpath{.status.conditions}会直接告诉你卡在哪。三类病根对应三种正解:坏掉的 APIService → 删掉那个 apiservice(最常见);残留资源的自定义 finalizer → 确认 controller 已弃用后 patch 摘掉;偶发状态不同步 → 等待或重启 controller。强撬 finalizer 必须走/finalize子资源接口(kubectl edit改不动),而且是万不得已的最后手段,因为它跳过了本该做的清理。一句话记忆点:Terminating 卡住先看 conditions 找病根,别一上来就强删 finalizer——那是撬锁,不是修锁。