第 5 / 23 篇

第 4 章:所有权决定能改什么

主资源、GC 所有权与显式清理责任

同名不代表属于自己

一个命名空间中已经存在名为 demo 的 Service,不代表新创建的 demo 网关实例可以接管它。它可能是业务自己创建的入口,也可能属于已经删除的旧实例。控制器必须用 owner reference 的 UID 核对身份,不能只比较名称和标签。

随书实现创建 Deployment 和 Service 时写入主对象的 APIVersion、Kind、Name、UID,并声明 controller 标志。后续读取同名对象,只有确认 controller owner 指向当前 UID 才修改托管字段。真实集群测试故意先创建一个无 owner 的 Service,协调必须返回明确错误,并保证原端口和所有权保持不变。

这类拒绝会使实例暂时无法收敛,但它把选择留给操作者。运维人员可以更名、删除冲突资源,或经过检查后执行显式迁移。程序自动“修复”一个归属不明的对象,通常比让实例显示冲突更危险。

垃圾回收理解关系,不理解业务

owner reference 让垃圾回收器知道主对象与依赖对象的关系。主对象真正删除后,Deployment 和 Service 可以被异步清理。它不保证所有依赖瞬时消失,因此测试必须等待最终 NotFound,而不能在删除请求成功后立刻断言所有子资源已经不存在。

本项目额外保存一个没有 owner reference 的 ConfigMap,模拟外部登记。这个对象不会随主对象自动回收,必须经过 finalizer 路径清理。两类资源放在一起,能够观察所有权自动删除与业务清理责任的不同。一个错误做法是给外部登记也加 owner,然后把垃圾回收成功当成 finalizer 已正确执行的证据。

BlockOwnerDeletion 也不是任意情况下阻止所有删除的万能开关。它与删除传播策略、权限和垃圾回收规则共同作用。教学代码使用管理员测试凭据建立关系;生产控制器需要明确相应权限,并在受限角色下验证实际操作。

字段所有权比对象所有权更细

拥有 Deployment 不等于应该每轮覆盖整个对象。API server 会填默认值,其他组件可能维护注解或状态,准入 webhook 可能加入容器字段。把新构造的 Deployment 直接替换现有对象,容易丢失这些合法变化。

本项目从最新对象读取并只调整声明托管的字段,保留无关元数据。比较容器时只比较确实拥有的字段,避免 API 默认的探针超时和终止消息路径造成永久差异。Service 更新保留 ClusterIP 等由服务器分配的属性,恢复 selector 和 ports。

这里仍有明确限制:教学实现只考虑一个主容器,没有为注入 sidecar、多个字段管理者或不同准入策略建立全面的合并协议。生产系统若支持这些机制,需要定义稳定的扩展点,并采用字段管理、补丁或专门配置接口,不能把“保留未知字段”当成已经解决所有协作冲突。

标签既是选择条件也是接口

Service 用实例标签寻找后端 Pod,Deployment 的 selector 也依赖同一组标签。标签变化如果只改一处,就可能得到运行正常却没有流量的 Pod。测试因此不只检查对象存在,还验证 Service 的选择器和 Pod 模板标签一致,并通过实际 HTTP 请求检验链路。

Deployment selector 是创建后不可随意修改的字段。设计标签时应选择稳定身份,而不是把 message、镜像版本或发布编号放进不可变 selector。需要区分发布批次时,额外标签更合适,不应改变实例归属关系。

防止名称复用跨越生命周期

外部登记名称使用主对象 UID。假设旧实例 demo 删除期间清理变慢,用户后来创建同名新实例;两次生命的 UID 不同,旧清理任务不能误删新登记。即使外部系统只允许人类友好的名称,也应将 UID 保存为幂等键或元数据,并在删除前核对。

练习是设计“用户提供已有 Service”模式。此时控制器不应自动把用户 Service 设为可垃圾回收的依赖,而应把引用、验证和删除策略写进 API 契约。托管创建与引用已有对象是两种不同产品能力,不能仅靠一个布尔开关掩盖它们的生命周期差异。

评审案例:HPA 与控制器争夺副本

平台后来允许用户为Deployment创建HPA。HPA改变副本数,下一秒我们的控制器又把它恢复为spec.replicas;两边都在正确执行自己的逻辑,系统却持续振荡。这不是资源版本重试不足,而是两个参与者都认为自己拥有同一字段。

支持自动伸缩需要改变托管契约:启用后副本数应由HPA决定,实例API提供最小值、最大值与指标引用,控制器不再持续覆盖Deployment副本。切回固定副本模式时,也要定义如何删除HPA并接管字段,避免先后顺序造成短暂争夺。

本项目没有HPA模式,因此明确由控制器决定副本。用户手工扩容会被恢复,这是预期行为而非缺陷。一个成熟接口应提前告诉用户哪些直接修改不会持久存在,并把确有价值的配置暴露在主资源上。用文档和状态解释所有权,可以减少操作者绕过托管入口后遇到的困惑。