为什么要拿三个对象来比
一句话总结
Argo CD 的 Application 控制器默认每 180 秒唤醒一次,比较 Desired(Git)、Live(集群)和 Last-Applied(注解)三种状态。为什么不是两个而是三个,以及为何在比较前不进行规范化就会永远停留在 OutOfSync,这就是本课的全部内容。
为什么需要它
不能直接比较 Git 与集群吗?不能,因为 Kubernetes 保存对象时会填入大量默认值。提交未指定 clusterIP 的 Service 时,API 服务器会分配一个;镜像标签为 latest 时,会将 imagePullPolicy 设为 Always;所有对象都会获得 resourceVersion、uid、generation、creationTimestamp、managedFields,并填充 status。Git 清单中没有这些内容。因此比较结果永远是“不同”,启用自动同步后,控制器会每 3 分钟无意义地重复 apply。
需要第三个比较对象则是另一类问题。只知道 Git 和集群时,无法判断“Git 中没有、集群中存在的字段”究竟是自己过去声明后又删除的,还是其他控制器(HPA、Sidecar 注入器、默认值设置器)添加的。Last-Applied 记住“我上一次声明了什么”。过去由自己声明、如今从 Git 消失的字段应当删除;自己从未声明过的字段属于他人,不应碰触。3-way diff 正是用来作出这一判断。Argo CD 在计算时使用与 Kubernetes Server-Side Apply 相同的 Structured Merge Diff 库。
工作原理
规范化阶段通常会移除 metadata.resourceVersion、metadata.uid、metadata.generation、metadata.creationTimestamp、metadata.managedFields,以及大多数资源的 status。仍然存在的差异则必须通过 ignoreDifferences 明确排除。最常见的例子是 HPA。Git 中写着 replicas: 2,而 HPA 将它提高到 8 时,应用会永远处于 OutOfSync;如果还启用了 selfHeal,Argo CD 会降回 2,HPA 又升到 8,双方不断争夺。答案是从 diff 中排除 /spec/replicas。
同步分为三个阶段:PreSync → Sync → PostSync;如果 Sync 失败,还会单独执行 SyncFail 阶段。各阶段运行的是钩子,钩子资源默认删除策略为 BeforeHookCreation。这意味着下次同步时先删除之前的钩子对象,再创建新对象,因此失败的迁移 Job 痕迹会一直保留到下一次部署。
同一阶段内的顺序由 wave 决定。从较小编号开始,等待一个 wave 中的所有资源都变为 Healthy,再进入下一个 wave。任何 wave 失败都会中止整个同步。这里的“等待”很重要。wave 并非只是改变 apply 顺序,而是等待资源准备就绪的机制。
重试采用指数退避。若设置 limit 5、duration 5s、factor 2、maxDuration 3m,就会以 5 秒、10 秒、20 秒、40 秒、80 秒的间隔尝试五次。Argo CD 通过跟踪注解识别自己管理的资源,其格式为 APP_NAME:GROUP/KIND:NAMESPACE/NAME。核心组的组名为空,因此像 my-app:/Service:default/nginx-svc 一样,冒号后紧接斜杠。亲手写一次该格式,就会理解为什么推荐注解方式而不是标签方式:标签有 63 字符限制,而且不包含命名空间。
生产现场中的表现
作者家庭实验室中的 ArgoCD 使用 MetalLB 地址池分配的 10.0.0.201,同一网段的 10.0.0.200 上运行 Gitea。注册集群时有一个必须注意的陷阱:argocd cluster add 会在目标集群中创建 argocd-manager ServiceAccount,并默认将其绑定到 cluster-admin。在家庭实验室里或许可以接受,但生产环境若保持这种设置,一个 GitOps 控制器就会成为所有集群的 root。正确做法是单独创建只包含所需 apiGroup 和动词的 ClusterRole,并替换绑定。
此外,该集群运行着 Cilium Gateway API。最初将 Gateway API CRD 保持为 v1.2 时,控制器因 tlsroutes 和 referencegrants 不是 v1 而拒绝启动,升级到 v1.6.1 后才成功。使用 Argo CD 部署 CRD 时经常发生类似问题:如果 CRD 与使用该 CRD 的自定义资源位于同一次同步中,在 CRD 尚未注册时便 apply CR,导致失败。标准解决方案是拆分 wave,先部署 CRD。这也是最能说明 wave 存在理由的案例。
下一项实验要做什么
在 /root/capa-app/ 中逐字段构建 Application 清单,依次填写 source、destination、syncPolicy、retry、ignoreDifferences。最后把该应用要部署的命名空间和 Deployment 真正提交到集群,并亲手添加跟踪注解。