LabHub
学习 学习路径 课程

GitOps 与 Argo CD

调谐循环 — 撤回的力量与抹掉的力量

在 LabHub 中继续学习

一句话总结

selfHeal 是把集群状态恢复到仓库状态的力量,prune 则是删除仓库中不存在内容的力量。二者方向不同,风险等级也不同。

循环图: 恢复到 · 删除 · 由谁执行这个 apply? · 循环执行

为什么需要了解这些

gitops-manifest 实验中,你亲手制造了漂移:通过 kubectl scale 把 replicas 增加到 5,kubectl diff 用退出码 1 报告差异,再次执行 kubectl apply 后恢复为 3。只要追问一个问题,GitOps 的最后一块拼图就会出现:由谁执行这个 apply

如果由人执行,那就不是 GitOps,只是整理得很好的部署脚本。人会忘记、会休假,也会被其他故障占用。第四项原则“自我修复”要求的是:让循环执行这个 apply,用固定周期代替人的决心。

工作原理

ArgoCD 的 Application Controller 通过 informer 观察资源变化,并为每个 Application 执行以下循环。

반복:
  1. repo-server 에 원하는 상태(렌더된 매니페스트)를 요청
  2. 대상 클러스터에서 실제 상태를 조회
  3. 정규화한 뒤 둘을 비교 (3-way diff)
  4. Sync 상태 갱신   → Synced / OutOfSync
  5. Health 상태 갱신 → Healthy / Progressing / Degraded / Missing
  6. 자동 동기화가 켜져 있으면 동기화 실행
  7. 다음 주기까지 대기 (기본 180초)

这里必须区分两个维度。Sync 状态表示“是否与仓库相同”Health 状态表示“是否运行正常”。系统可能处于 SyncedDegraded:清单已经按仓库要求部署,镜像却拉取失败,Pod 陷入 CrashLoopBackOff。反过来,也可能 OutOfSyncHealthy:有人手动增加的 Pod 正常运行。如果混淆两个维度,就会错误判断故障原因。

第 3 步的比较中包含规范化。Kubernetes 自动填充的值(resourceVersionuidgenerationcreationTimestampmanagedFields、大多数 status)会先从两侧移除再比较。没有这个过程,即使什么也没改变,每次都会被报告存在差异。

selfHeal 的作用

selfHeal: true 是当第 4 步出现 OutOfSync 时进入第 6 步的开关。因此,通过 kubectl scalekubectl edit 进行的修改会在下一次协调时消失。重要的是,它不会立即消失——系统有默认周期,所以人们会感觉“修好后一度正常,却突然恢复了原样”。这种时间差经常让人去错误的方向排查原因。

关闭 selfHeal 后,ArgoCD 就只是一块在界面展示差异的仪表板。生产环境是否启用 selfHeal,本质上是组织对**“是否允许紧急手动干预”**的选择,而不是技术偏好。启用后,紧急处理会被悄悄撤销;关闭后,漂移会逐渐累积。大多数团队选择启用,同时建立纪律:紧急处理必须立即升级为正式提交。

prune 的作用——以及它为何可怕

prune: true 的方向相反,判断过程如下。

1. 저장소를 렌더해 "있어야 할 오브젝트" 목록을 만든다
2. 클러스터에서 이 앱이 소유한 오브젝트를 찾는다
   (argocd.argoproj.io/tracking-id 어노테이션 = 앱:그룹/종류:네임스페이스/이름)
3. 2에 있는데 1에 없는 것 = 삭제 대상

真正危险的是第 3 步。只需一次误把 source.path 改为空目录的提交,第 1 步的列表就会变成 0 个,该应用管理的所有对象都会成为删除目标。 评审者漏掉一个字符的拼写错误就足以造成事故。因此,需要以下防护措施。

措施 作用
allowEmpty: false 渲染结果为空时拒绝同步
PruneLast=true 对齐其他资源后最后执行删除
Prune=false(资源注解) 仅将该对象排除在删除目标之外
orphanedResources.warn 只警告管理范围外的对象

selfHeal 最坏的结果是“手工修改消失”,prune 最坏的结果却是“数据消失”。二者风险等级不同,因此,PVC 等有状态资源添加 Prune=false 更安全。

不应恢复的字段

协调循环非常忠实,甚至会恢复其他控制器正当拥有的字段。HPA 把 spec.replicas 增加到 8,ArgoCD 降回 3,HPA 又增加到 8。解决这种无限循环的方法不是关闭开关,而是明确所有权

spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas

ServerSideApply=true 从另一个层面处理相同问题。它让 API 服务器跟踪字段所有权,把“由别人管理我没有声明的字段”视为正常情况。

生产现场中的常见情况

第一,本实验环境没有 ArgoCD 控制器。 Pod 在无特权模式下运行,内部的 kwok 集群只有 etcd、apiserver、controller-manager、scheduler 真正运行。因此,可以注册 CRD 并创建 Application,但它不会自行变成 Synced。本课程不会掩盖这一点,所以后续实验不会模拟控制器,而是让你亲手把控制器的判断写成代码。你已经完成过一半相同工作——gitops-manifest 第 8 步创建的 sync.sh,正是单次执行第 3~6 步的脚本。控制器只是一个不会忘记并持续重复该过程的存在。

第二,“为什么恢复原样了”的真相。 这是运维中最常见的询问:明明修改了配置,几分钟后却恢复了原状。答案总是相同——启用了 selfHeal,而且该变更不在仓库中。此时不应责怪工具,而应检查流程。因为团队已经约定:不经过仓库的变更,应被视为不存在。

第三,按环境划分自动同步范围。 常见折中方案是在 dev 同时启用 pruneselfHeal,生产环境只启用 selfHeal,或完全采用手动同步。这是为了防止误合并的删除立即应用到生产环境。无论选择哪种方式,把选择写入 Application YAML 并提交到仓库这一行为本身就是 GitOps。

下一次实验要做什么

紧接着的实验会用八个步骤完整执行一轮该循环。在 Application 中声明开关与忽略字段后,分别编写用于规范化预处理、漂移判断、删除候选计算、空结果防护的脚本。通过服务端应用亲自确认:已声明字段会恢复,交给其他控制器的字段会保留。最后制造一种仓库状态一致、Pod 却无法启动的情况,观察为何两个状态维度必须分开。