finalizers 是 kubernetes 中用于阻断资源立即删除的元数据字段,必须置于 metadata.finalizers([]string 类型),不可置于 annotations 或 spec 中;其作用是在 deletiontimestamp 设置后异步执行清理逻辑,待所有 finalizer 被显式移除后资源才被真正删除。

Finalizer 字段必须写在 metadata.finalizers 里,不能放错位置
Finalizer 不是 annotation 或 label,它是 metadata 下的一个独立字段,类型为 []string。写错位置(比如塞进 annotations 或漏掉 metadata 层级)会导致 Kubernetes 完全忽略它,对象仍会立即删除。
正确结构示例:
apiVersion: apps/v1 kind: Deployment metadata: name: my-app finalizers: ["mycompany.com/cleanup-job"] # ✅ 必须在这里 spec: # ...
常见错误:
- 把
finalizers写在spec里 → 无效 - 用
annotations模拟,如mycompany.com/finalizer: "true"→ 不触发阻断逻辑 - 字符串含非法字符(如大写字母、下划线、点号过多)→ API Server 拒绝创建,报错
Invalid value: "mycompany.com/CleanupJob": a valid label must be an empty string or consist of alphanumeric characters...
Go 代码中添加 Finalizer 要用 client.Client.Update 或 Patch,不能只改本地 struct
你用 client.Get 拿到对象后,在内存里往 obj.ObjectMeta.Finalizers append 一个字符串,这只是本地修改。不调用 Update 或 Patch,API Server 根本不知道变化,删除时照样跳过清理逻辑。
推荐用 Patch,避免读-改-写冲突:
patchData := []byte(`{"metadata":{"finalizers":["mycompany.com/cleanup-job"],"managedFields":null}}`)
err := r.Patch(ctx, obj, client.RawPatch(types.MergePatchType, patchData))
注意两点:
- 务必带上
"managedFields": null,否则 patch 可能因 server-side apply 冲突失败(尤其在 k8s ≥ 1.22+) - 不要用
Update直接覆盖整个对象 —— 它会抹掉其他 controller 写入的status或managedFields - 如果对象还没创建(比如 reconcile 第一次运行),
Patch会报NotFound,需先Create再Patch
Finalizer 清理必须在 Reconcile 中显式移除,且要检查 deletionTimestamp
加 Finalizer 是为了“拦住删除”,但拦住之后你得自己收尾:执行清理动作(如删外部资源、关连接、发通知),再把 Finalizer 从数组里删掉。Kubernetes 不会自动帮你删,也不会重试你的清理逻辑。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
关键判断条件是:obj.DeletionTimestamp != nil。只有这时才表示用户已执行 kubectl delete,对象进入 Terminating 状态,你的清理逻辑才该触发。
典型流程:
- 检查
if obj.DeletionTimestamp != nil - 执行清理(比如调第三方 API、删 PVC、等待 Job 完成)
- 清理成功后,用 Patch 移除 finalizer:
"finalizers": []或过滤掉目标项 - 若清理失败,return error 触发重试;不要 return nil,否则 Finalizer 永远卡着
错误写法:if len(obj.Finalizers) == 0 { return nil } —— 这会跳过所有清理,导致资源泄漏。
多个 Finalizer 共存时顺序无关,但每个都必须被显式清除
Kubernetes 对 finalizers 数组是“全有或全无”语义:只要数组非空,对象就卡在 Terminating;清空数组后才真正删除。它不关心顺序,也不支持“按序执行”。
所以如果你同时注册了两个 Finalizer:["mycompany.com/cleanup-job", "mycompany.com/notify-slack"],你得在 Reconcile 中确保两者都被移除,缺一不可。
容易踩的坑:
- 只清理第一个,忘了第二个 → 对象永远 Terminating
- 用
obj.Finalizers = obj.Finalizers[:0]一键清空 → 看似省事,但会把其他 controller 添加的 Finalizer(如kubernetes.io/pvc-protection)也干掉,引发意外交互 - 没做去重,重复 add 同一个 Finalizer → 数组膨胀,但不影响功能;不过后续 remove 时得循环过滤,别只删第一个匹配项
最稳妥做法:用 controllerutil.RemoveFinalizer(obj, "mycompany.com/cleanup-job")(来自 sigs.k8s.io/controller-runtime/pkg/controller/controllerutil),它自动去重且安全。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










