bigywater.tech
Kubernetes · 18

Finalizer:对象为什么删不掉

对象删不掉?十有八九是 metadata.finalizers 里还有人没点头。这篇在 ConfigMap 上模拟一个“控制器”,把带 finalizer 对象的删除过程拆成两步看清楚:先打标记、再摘 finalizer;并用 PVC 保护这个内置例子对照真实场景。

39 ms
带 finalizer 的对象 kubectl delete --wait=false 的返回时间,而对象仍然存在
Forbidden
给正在删除的对象新增 finalizer 的结果
4.1 秒
kubectl delete 默认等到“控制器”摘掉 finalizer 所花的时间

删除其实是两步

实测 · 本机执行输出
  kubectl delete cm a --wait=false:退出码 0;configmap "a" deleted from k12 namespace;耗时 39 ms
  之后 kubectl get cm a 仍然能读到;deletionTimestamp=2026-10-10T08:57:42Z deletionGracePeriodSeconds=0 finalizers=['example.com/block']
  data 还在:{"v":"1"}

对象带着 finalizers 时,kubectl delete 几十毫秒就返回 deleted,但对象还在:apiserver 只做了一件事,把 metadata.deletionTimestamp 设上。这时 kubectl get 照常能读到,data 原样保留。真正的删除要等 finalizers 变空,由写下这个 finalizer 的控制器负责:先做完清理工作,再把自己那一项摘掉。

正在删除的对象还能做什么

实测 · 本机执行输出
  改 data:退出码 0;configmap/a patched;data={"v":"2"}
  再加一个 finalizer:退出码 1;metadata.finalizers: Forbidden: no new finalizers can be added if the object is being deleted, found new finalizers []string{"example.com/another"}
  再删一次:deletionTimestamp 2026-10-10T08:57:42Z → 2026-10-10T08:57:42Z(没有变化:True)
  想撤销删除(删掉 deletionTimestamp):退出码 0;configmap/a patched (no change)
  同名重建:error: failed to create configmap: object is being deleted: configmaps "a" already exists

改 data 仍然允许;新增 finalizer 被拒绝(no new finalizers can be added if the object is being deleted);再删一次不会改变 deletionTimestamp;想用 patch 把 deletionTimestamp 删掉得到 (no change),撤销不了;同名重建报 object is being deleted: configmaps "a" already exists。规则很简单:finalizer 只能减不能加。

摘掉 finalizer

实测 · 本机执行输出
  kubectl delete cm b(默认 --wait=true),4 秒后由“控制器”摘掉 finalizer:退出码 0;configmap "b" deleted from k12 namespace;总共等了 4.1 秒
  kubectl get cm b:Error from server (NotFound): configmaps "b" not found
  有两个 finalizer 时只摘掉一个:对象还在吗 True;剩下 ['example.com/second']
  再摘掉最后一个:对象还在吗 False

kubectl delete 默认等到对象真的消失才返回:“控制器”4 秒后把 finalizers 置空,命令就等了 4.1 秒。有多个 finalizer 时必须全部摘光,摘掉一个对象仍在。所谓“强制删除卡住的对象”,就是替那个不工作的控制器把 finalizer 摘掉——对象会立刻消失,但它本该做的清理没有发生。

内置例子:PVC 保护

实测 · 本机执行输出
  Pod user 已经 Running、PVC data 已经被它使用:phase=Bound finalizers=['kubernetes.io/pvc-protection']
  kubectl delete pvc data 之后:仍然存在,phase=Bound deletionTimestamp=有 finalizers=['kubernetes.io/pvc-protection']
  删除用它的 Pod 之后:PVC 消失了(Pod 删除完成后又过了约 0.1 秒)

PVC 创建时就带着 kubernetes.io/pvc-protection。Pod user 正在使用它时 kubectl delete pvc data,PVC 仍是 Bound,只是多了 deletionTimestamp;把用它的 Pod 删掉后,PVC 在约 0.1 秒内消失。这个 finalizer 防止的是“卷还在被挂载时底层存储被回收”。

分步演示

两步删除
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);对象是 k12 命名空间里的 ConfigMap,finalizer 是自己起的名字 example.com/block(没有真正的控制器,由脚本在需要时 patch 摘掉);PVC 用 kind 默认的 local-path StorageClass,Pod 用 busybox:1.36 并等到 Running 后再删 PVC;实验末尾摘掉了故意留下的 finalizer,否则命名空间会卡在 Terminating;每种情形各一次
    • 没有验证:foregroundDeletion 与 orphan 这两个由垃圾回收器管理的 finalizer(下一篇);自定义资源上 finalizer 的差别;finalizer 名字的格式校验(这里只用了合法名字);PV 的 pv-protection;apiserver 重启时 deletionTimestamp 的持久化(它本来就存在 etcd 里);强制删除(--force --grace-period=0)对带 finalizer 对象的行为
    京ICP备2026067502号-1京公网安备11010502063487号