LabHub
学习 学习路径 课程

GitOps 与 Argo CD

手工把调谐循环转一圈

在 LabHub 中继续学习

目标

亲手构建一次 reconcile loop 中发生的事情:带所有权标记应用期望状态,去除易变字段,排除约定忽略的字段后比较,筛选删除候选,并拒绝空结果。

为什么重要

gitops-manifest 实验中,你手工制造漂移并手工恢复。只需再问一个问题,就能得到 GitOps 的最后一块拼图:**谁来执行应用。**如果由人执行,那只是整理得很好的部署脚本,而不是 GitOps。

要让控制器代为执行,判断标准必须写成代码:哪些是易变值,哪些字段留给其他控制器所有,如何标记本应用所有的对象,以及结果为空时如何处理。selfHeal 最坏会让手工变更消失,而 prune 最坏会让数据消失,两者风险等级不同。把判断写成脚本后,开关名称背后的实际行为就会清晰可见。

环境

此 Pod 没有 ArgoCD 控制器。可以创建 Application 对象,但它不会自行变为 Synced。因此本实验采用先写声明,再亲自构建按声明行动的工具。字段所有权、服务端应用和调度判断由 kwok 启动的真实 apiserver 处理,因此这些部分是真实的。工作目录为 /root/gitops-sh,使用其下的 k8s/desired/bin/out/

步骤

  1. Application shop 中声明开关和忽略字段。
  2. 带所有权标记以服务端方式应用期望状态。
  3. bin/normalize.sh 去除易变字段。
  4. bin/drift.sh 排除忽略字段后比较。
  5. 制造漂移,只恢复其中一类,并记录到 out/selfheal.txt
  6. bin/prune.sh 筛选删除候选。
  7. bin/sync.sh 拒绝空结果。
  8. 制造已 Synced 但不健康的状态,并整理到 out/status.txt

参考

声明循环如何运行

/root/gitops-sh/k8s/application.yaml 中写入并应用 argocd 命名空间的 Application shop。在 syncPolicy.automated 中设置 prune: trueselfHeal: trueallowEmpty: false;在 syncOptions 中加入 PruneLast=trueServerSideApply=true;通过 ignoreDifferences 忽略 apps/Deployment/spec/replicas。目标命名空间为 sh-lab

此 Pod 没有 ArgoCD 控制器,该对象不会自行变为 Synced。这里用它声明循环应如何运行,后续脚本按声明行动。allowEmpty 默认虽为 false,本实验仍显式写出意图。ignoreDifferences 防止回滚 HPA 等其他控制器正当所有的字段。

带所有权标记应用期望状态

/root/gitops-sh/desired/ 创建 deployment.yaml(名称 orders)和 configmap.yaml(名称 orders-config)。二者命名空间均为 sh-lab,且 argocd.argoproj.io/tracking-id 注解以 shop: 开头。Deployment 不要写 spec.replicas。然后用 --server-side --field-manager=argocd-controller 一次应用整个目录。

规范做法是完全不声明约定忽略的字段,否则会取得该字段所有权,之后每次与 HPA 或人工修改冲突。tracking-id 是 ArgoCD 标记自身所有权的注解,形式为 <앱>:<그룹>/<종류>:<네임스페이스>/<이름>。没有所有权标记,第 6 步就看不到该对象。应用后用 kubectl get deploy orders -o yaml --show-managed-fields 查看字段所有者。

创建去除易变字段的预处理

创建 /root/gitops-sh/bin/normalize.sh <매니페스트>。从输出 YAML 中删除 metadataresourceVersionuidgenerationcreationTimestampmanagedFields 和顶层 status。人工声明的内容(kind、名称、命名空间、注解、spec)必须保留。

若直接比较 Kubernetes 自动填充且持续变化的值,即使没有变更也会每次报告差异,reconcile loop 将永不停止。反之,删除过多又会看不到真实差异,因此保留什么与删除什么同样重要。可用 python3yaml 模块读取,再用 yaml.safe_dump_all 输出。评分器会用 fixture 实际运行脚本。

排除忽略字段后比较

创建 /root/gitops-sh/bin/drift.sh <선언파일> <실제파일>。先规范化,再去掉 apps/Deploymentspec.replicas 进行比较;相同则输出 SYNCED 并以 0 结束,不同则输出 OUTOFSYNC 并以 1 结束。

比较方向是检查声明内容是否原样存在于实际状态。实际对象含有大量 Kubernetes 自动字段,若都算作差异就无法通过。可逐层遍历声明侧的键,检查实际侧是否有相同值;列表的长度和顺序也要比较。评分器会用三组配对实际运行脚本。

制造漂移并只恢复其中一类

kubectl scaleorders 的 replicas 提高到 5,再用 kubectl set image 把镜像标签改成其他值。随后用 --server-side --field-manager=argocd-controller --force-conflicts 重新应用声明目录,并在 /root/gitops-sh/out/selfheal.txt 中写五行:DRIFT_FIELDIGNOREDLIVE_REPLICASIMAGE_RESTOREDCONFLICT_RESOLUTION

这里同时展示两件事:未声明的 spec.replicas 属于其他所有者,因此保持不变;已声明的镜像会恢复。若去掉 --force-conflicts,镜像也不会恢复,因为 kubectl set image 已取得该字段所有权。真实 ArgoCD 使用 ServerSideApply=true 同步时也以这种方式收回所有权。LIVE_REPLICAS 必须从集群读取。

按所有权标记筛选删除候选

不创建声明文件,直接在 sh-lab 中创建一个 ConfigMap,并添加 argocd.argoproj.io/tracking-id。然后创建 /root/gitops-sh/bin/prune.sh,对标记为本应用所有、但声明目录中不存在的每个对象输出一行 PRUNE=<종류>/<이름>。不要真的删除。

判断有三步:读取声明目录构建应存在列表;在集群中查找本应用所有对象;筛选后者有而前者没有的对象。无所有权标记的对象无论何时创建都不属于候选,不要误抓 kube-root-ca.crt 等集群自动创建对象。评分器会独立计算并核对输出。

渲染结果为空时停止

创建 /root/gitops-sh/bin/sync.sh。读取环境变量 DESIRED_DIR(默认值为声明目录);若没有任何清单,输出包含 REFUSED 的消息并以非 0 结束。若存在,则用 --server-side --field-manager=argocd-controller --force-conflicts 应用,输出 APPLIED=<개수> 并以 0 结束。

只需一个把 source.path 误改为空目录的提交,应存在列表就会变为 0,该应用管理的所有对象都会成为删除候选。审查者漏看一个字符就足够造成事故,因此直接拒绝空结果是最直接的防御。拒绝时必须不删除任何内容。评分器会分别用空目录和正常目录运行,并确认对象未消失。

制造已 Synced 但不健康的状态

/root/gitops-sh/desired/broken.yaml 中声明带所有权标记、具有无法调度 nodeSelector 的 Deployment broken,并用 sync.sh 应用。然后在 /root/gitops-sh/out/status.txt 中写五行:SYNCHEALTHREASONPRUNEALLOW_EMPTY

Sync 表示是否与仓库相同,Health 表示是否正常运行,它们是不同轴。因此完全按仓库部署、Pod 却无法启动的组合是合理状态。实际工作中镜像标签错误正会产生这种状态;混淆两轴会在错误方向排查。给 nodeSelector 写集群中不存在的标签即可复现。PRUNEALLOW_EMPTY 原样写入第 1 步声明的值。