Finalizer 详细说一下是干啥的,是 Operator 中的机制吗

一、先给核心结论

  1. Finalizer 不是 Operator 独有的机制,是 Kubernetes APIServer 原生支持的通用机制。
     
    所有资源(Pod、Deployment、CR、ConfigMap……)都可以加 Finalizer,不只自定义 CR。
  2. Operator 只是大量使用 Finalizer 解决自定义资源删除前的清理难题。

二、Finalizer 是什么?

Finalizer 是资源对象 metadata.finalizers 里的字符串数组,作用:
阻止资源被真正删除,给控制器一段窗口期执行 “删除前清理逻辑”
工作规则(APIServer 内置逻辑):
  1. 用户执行删除操作(kubectl delete);
  2. APIServer 不会立刻清除数据,先把资源 deletionTimestamp 设置为当前时间,资源进入 Terminating 状态;
  3. 只要 finalizers 列表不为空,APIServer 拒绝彻底移除该资源;
  4. 控制器感知到资源进入 Terminating,执行自定义清理逻辑;
  5. 清理全部完成后,控制器必须把自己的 finalizer 从数组中移除,更新资源;
  6. finalizers 数组为空 → APIServer 才会真正删除这条资源。
一句话通俗理解:
 
Finalizer = 删除拦截器;有 Finalizer,资源删不掉,等控制器做完打扫工作,手动放行。

三、原生资源自带 Finalizer 例子(证明不是 Operator 专属)

  • PV/PVC 绑定场景:PV 有 kubernetes.io/pv-protection
  • Node 节点:kubernetes.io/node-protection
     
    目的:防止资源还在被使用时被直接删除,造成数据丢失。

四、为什么 Operator 格外依赖 Finalizer?

原生 Deployment/StatefulSet 依靠 OwnerReference 垃圾回收:父资源删除,APIServer 自动删子资源。
 
但 Operator 经常要清理集群外部资源:
 
对象存储 Bucket、云数据库、负载均衡、外部域名、备份任务……
 
👉 OwnerReference 只能管理集群内 API 资源,管不了集群外部资源!
场景举例:
 
你写 MySQL Operator,CR 被删除时:
  1. 需要先销毁云厂商 RDS 实例
  2. 需要清理外部备份存储
  3. 需要释放外网 SLB
     
    这些集群外资源 APIServer 完全不认识,垃圾回收机制无能为力。
流程:
  1. CR 创建时,Reconcile 检测不存在自定义 finalizer,就追加 finalizers: ["mysql.example.com/finalizer"]
  2. 用户删除 CR → CR 进入 Terminating
  3. Operator 收到事件,进入清理分支:销毁外部资源
  4. 外部资源清理成功 → 移除 finalizer
  5. APIServer 看到 finalizers 为空,彻底删除 CR
⚠️ 致命坑(面试高频)
 
清理逻辑执行失败、网络报错、代码异常,忘记移除 finalizer → CR 永远卡在 Terminating,删不掉!

五、Finalizer 完整标准代码流程(controller-runtime 标准范式)

  1. 正常调谐阶段(CR 新建 / 更新)
     
    判断:CR 不包含自定义 finalizer && CR 没有 deletionTimestamp
     
    → 添加 finalizer,更新 CR。
  2. 检测到 deletionTimestamp != nil(资源正在被删除)
    • 执行外部资源清理逻辑
    • 清理成功:移除 finalizer,更新 CR
    • 清理失败:返回报错,等待下一轮重试,不要移除 finalizer
  3. 一旦 finalizer 被移除,后续无法再次拦截删除。

六、Finalizer VS OwnerReference(面试常对比)

表格
 
机制作用范围执行方适用场景
OwnerReference 集群内 K8s 资源 APIServer 垃圾回收控制器 Pod、Service、ConfigMap 等集群内附属资源自动清理
Finalizer 集群内资源拦截,可联动外部资源 你的 Operator 控制器 需要清理外部资源、复杂有序清理流程
工程最佳实践:
 
集群内子资源使用 OwnerReference 自动回收;
 
外部资源、复杂业务清理逻辑使用 Finalizer。

七、面试高频追问汇总

追问 1:多个 Finalizer 执行顺序有保证吗?

没有!APIServer 不保证 finalizer 顺序,并发执行风险由开发者自己处理。
 
一般 Operator 只定义一个自定义 Finalizer,规避顺序问题。

追问 2:我手动删除 finalizer 会发生什么?

APIServer 直接删除 CR,Operator 来不及执行清理 → 外部资源残留,产生僵尸资源。生产严禁手动清 finalizer。

追问 3:Finalizer 会无限重试吗?

会。清理逻辑报错,不删除 finalizer,每次 Reconcile 都会尝试清理,直到成功或者人工干预。
不要手动在 CR 的 YAML 里写 ownerReferences!绝对不要人工写。
 
ownerReferences 是控制器在创建子资源(Deployment/Service/Pod)的时候,由代码主动打上的,不是写 CR yaml 加在自己身上。
我们分层讲明白:
 

OwnerReference是干什么的

1. OwnerReference 作用链路

CR = 父资源
 
Deployment、Service、ConfigMap = 子资源
Operator 在 Reconcile 创建 Deployment 时,代码里给 Deployment 设置:
go
 
运行
 
 
ownerReferences := []metav1.OwnerReference{
    *metav1.NewControllerRef(cr, schema.GroupVersionKind{...}),
}
deploy.OwnerReferences = ownerReferences
 
效果:
 
当 CR 被删除,K8s 垃圾回收控制器自动清理所有带这个属主引用的 Deployment、Service。
属主引用是打在子资源上,不是打在父 CR 身上!

2. 那 CR 自己能不能加 ownerReferences?

语法上允许,但几乎没人这么用。CR 一般是顶层资源,很少有父资源。

3. 结合你的场景:无状态 CR,管理 Deployment

场景:CR(MyApp)→ 托管 Deployment
  1. 在代码创建 Deployment 时,给 Deployment 设置 OwnerReference
  2. 用户删除 CR
  3. APIServer 发现父 CR 删除,自动回收 Deployment、关联 Pod
     
    ✅ 这种场景,不需要 Finalizer!
限制:
 
这套机制只生效于集群内 K8s 资源。
 
如果有外部资源(云 SLB、外部数据库),单纯依靠 OwnerReference 不够,依旧需要 Finalizer。

4. 区分两条路线(面试高频)

路线 A:只管理集群内部资源(Deployment/Service/ConfigMap)

  • 创建子资源时代码注入 OwnerReference
  • 不需要 Finalizer
  • 删除 CR:子资源自动回收

路线 B:存在外部资源需要清理

  • 子集群资源依旧用 OwnerReference
  • 额外增加 Finalizer,拦截 CR 删除,先清理外部资源
  • 外部清理完成后移除 finalizer,CR 才被真正删除

5. 澄清你刚才的疑问

“无状态 cr 在编写 yaml 文件时要加上 ownerReferences 字段?”
 
❌ 错误做法:手动编辑 CR yaml 添加 ownerReferences
 
✅ 正确做法:Operator 代码创建 Deployment 的时候,给 Deployment 打上 ownerReferences

6. 附带一个高频面试追问

问:我忘记给 Deployment 设置 OwnerReference,会发生什么?
 
答:删除 CR 之后,Deployment 和 Pod 保留在集群里,变成 “孤儿资源”,不会自动清理,需要手动删除。
 
 
 
总结就是
  • OwnerReference:建立父子关联 → APIServer 自动回收子资源,不需要业务代码写删除逻辑
  • Finalizer:拦截 CR 删除事件 → 留给控制器执行自定义清理逻辑(尤其是外部资源)

 

posted @ 2026-07-22 10:41  老油条666  阅读(7)  评论(0)    收藏  举报