TT Lab
开始
学习 学习路径 课程

GitOps 与 Argo CD

这个字段是谁的

在 TT Lab 中继续学习

一句话总结

服务端应用是为对象的每个字段记录所有者的装置,只有会读这份记录(managedFields), 才能解释“为什么我的值总是被还原”,以及 Argo CD 的 managedFieldsManagers 忽略规则。

为什么需要这份记录

以前的 kubectl apply 在客户端计算。它把“我上次发送的清单”整个放进 kubectl.kubernetes.io/last-applied-configuration 注解里,再与新清单比较, 来决定要删除什么。这种方式没有所有者的概念。所以当两个工具处理同一个对象时,会悄悄删掉对方的字段—— Argo CD 刚应用完,自动扩缩器就修改了 replicas,下一次应用时这个值又消失了, 自动扩缩器再次写入,就这样没完没了地来回折腾。甚至无法知道是谁错了。

服务端应用把这项计算移到 API 服务器,并记录每个字段由哪个管理者所有。 kubectl apply --server-side --field-manager=gitops 中的 gitops 就是这个名称。

工作原理

记录放在 metadata.managedFields 中。这里先有一个陷阱—— kubectl get ... -o json 默认会隐藏这个字段。 如果不加 --show-managed-fields, 它总是 null,jq 就会崩溃。

kubectl -n demo get deploy web -o json --show-managed-fields   | jq -r '.metadata.managedFields[] | .manager + " | " + .operation'
gitops | Apply
kubectl | Update

如果试图向不属于自己的字段写值,服务器会拒绝,并以列表形式告知哪些字段属于谁。 加上 --force-conflicts 就能通过,但必须准确了解此时发生了什么——冲突并没有消失, 而是所有权转移了。 原来的所有者从该字段中退出,下次原来的所有者再应用自己的值时, 这次被拦住的就是对方。如果两个自动化工具把强制当作习惯,就会变成一场没有尽头的争斗。

反方向也与直觉不同。从清单中去掉字段,该管理者就会放弃所有权。但值不会回到原来 所有者的值。 没有人拥有的字段会变成 API 默认值。把 replicas 抢走设为 5 的管理者 删掉那一行之后,结果不是 2,而是 1。

子资源也会单独记录。kubectl scale 写入的不是整个对象,而是 scale 子资源,所以所有权记录中 会单独出现管理者为 kubectl、子资源为 scale 的条目,冲突消息也会告知这一点。 自动扩缩器造成的冲突,正是这种样子。

与 Argo CD 如何衔接

Argo CD 可以通过同步选项 ServerSideApply=true 使用这种方式。而忽略规则中的 managedFieldsManagers 是按这份所有权记录,而不是按字段路径来选择比较对象。

resource.customizations.ignoreDifferences.apps_Deployment: |
  managedFieldsManagers:
  - kubectl
  jsonPointers:
  - /spec/replicas

优点是,“自动扩缩器写入的字段不参与比较”可以不必逐一写出路径就能表达。 代价是有局限——预览命令只渲染路径规则。管理者名称规则实际会过滤掉什么, 必须由人去读所有权记录来判断。

在现场相遇的样子

最常见的事故是“HPA 和 Argo CD 打架”。仓库里写了 replicas,HPA 也会写这个值。 看一下所有权记录,答案立刻就有了——两个管理者握着同一个字段。解决办法不是强制,而是 把 replicas 那一行从仓库中去掉。不过正如上面看到的,去掉的瞬间值会掉回 API 默认值, 所以在 HPA 重新计算之前,Pod 会短暂减少。不了解这一点,在业务时间操作就会变成事故。

第二种是迁移过程中的混乱。把以客户端方式管理的对象迁移到服务端方式时,last-applied-configuration 注解仍然保留,这个注解所指的字段与新的所有权记录不一致,可能发生意想不到的删除。 所以迁移要以对象为单位,并一边确认所有权记录一边进行。

本实验环境的局限

实验 Pod 中没有 Argo CD 控制器。 因此看不到 ServerSideApply=true 同步的真实运转。 不过,kwok 启动的是真正的 kube-apiserver,所以所有权记录、冲突、强制、释放所有权 都与生产环境完全一样地发生,而 Argo CD 这一侧的衔接,则通过忽略规则渲染来确认。

下一项实验要做什么

以管理者 gitops 应用并读取所有权记录。再用第二个管理者 hotfix 写同一个字段来制造冲突, 强制抢夺之后再释放,看看值会去哪里。比较客户端应用留下的注解, 并触发 kubectl scale 造成的子资源冲突。最后对包含所有权记录的真实对象 套用 managedFieldsManagers 规则并渲染。