LabHub
学习 学习路径 课程

CGOA — GitOps 认证助理

仓库怎么拆 — 以及把晋级做成一行 PR

在 LabHub 中继续学习

一句话总结

GitOps 的首项设计决策不是选择工具,而是确定repository boundary。是否分离 app source 与 deployment config,是否按团队拆分 repository,是否用 branch 或 directory 区分 environment——这四个问题的答案会决定之后全部运维成本。

循环图: repository boundary · 无限循环 · app repository(code + Dockerfile + CI) · Branch 隔离

为什么需要它

最初,在 app repository 中建立 k8s/ directory 并一起管理 manifest 很方便,但很快会遇到以下问题。

提升 image tag 的 commit 进入 app repository 后,会再次触发 CI。CI 创建新 image,再 commit 新 tag,形成无限循环。为了绕过问题开始添加 [skip ci] 后,pipeline 会越来越混乱。

第二个问题是权限。app code review 权限与 production deployment config review 权限,并不属于同一组人员。放在同一 repository 时,只能通过 CODEOWNERS 勉强拆分,一次失误就可能突破边界。

第三个问题是变更周期。app code 一天变化十次,deployment config 可能一个月才变一次。历史混在一起后,为了查找“replicas 从什么时候开始是 3”,就要翻阅数百个 commit。

因此,实际工作的基础形态是分离app repository(code + Dockerfile + CI)config repository(manifest + kustomize/Helm values)。考试中称之为 separation of concerns。

工作原理

config repository 再拆成几个,就是 monorepo 与 polyrepo 的争论。

Monorepo(一个 config repo) Polyrepo(每团队/app 一个)
一致性 在一个位置强制使用共同 base 每个 repository 的标准可能不同
RBAC 只能按 directory 控制,精细权限较难 可通过 repository permission 清晰分离
原子变更 可在一个 PR 中同时修改多个 app 需要跨 repository 协调
agent 负载 多个 app 同时 polling 一个 repository 分散
适用规模 一个或多个团队、数十个 app 多个组织、数百个 app

environment 也有三种隔离方式。

让 promotion 只改一行

判断 repository structure 好坏只有一个标准:“把 stage 中运行良好的版本提升到 prod,PR diff 有几行?”

如果 base 与 overlay 分离正确,答案就是一行。

# apps/checkout/overlays/prod/kustomization.yaml
images:
  - name: ghcr.io/labhub/checkout
    newTag: 1.5.0        # ← 1.4.0 에서 이 줄만 바뀐다

这一行 diff 向 reviewer 提供了非常丰富的信息:可以直观看出“进入 prod 的变更只有 image tag,其余配置与 stage 相同”。反过来,如果 promotion PR 同时修改 replicas、resource 与 environment variable,那就不是 promotion,而是一次新 deployment。

此处 tag 必须不可变。如果使用 latest 或 branch name tag,Git commit 不变,实际运行 image 却可能变化。这样 desired state 就无法完全由 Git 决定,违反第二项原则。

app-of-apps

app 数量增加后,Argo CD Application resource 也会达到数十个。若手动创建,“部署 deployment tool 的流程”又会退回手工作业。app-of-apps 是结束这种递归的 pattern。

一个 root Application 指向 bootstrap/ directory,该目录包含 child Application manifest。同步 root 后会创建 child Application,再由 child 分别同步自己的 app。添加新 app 只需向 bootstrap/ commit 一个文件。

它与用途相近的 ApplicationSet 仍有区别。app-of-apps 通过文件明确列出 child,ApplicationSet 则通过 generator(directory scan、cluster list、PR list)计算 child。希望 app list 明确可见时选择 app-of-apps;列表频繁变化并可用规则表达时选择 ApplicationSet。

现场会遇到的情况

作者家庭实验室中,Argo CD 位于 10.0.0.201,Gitea 位于 10.0.0.200。Argo CD 从同一集群中的 Gitea pull config。该架构有一个有趣的 bootstrap 陷阱:Gitea 故障后,Argo CD 无法读取 desired state,而 Gitea 自身 manifest 也保存在该 Gitea 中。因此,GitOps 管理区域与手动 bootstrap 区域之间的边界,应画在哪里,会成为真实设计问题。

还有一点:该集群的 Gateway API 需要 CRD v1.6.1。使用 v1.2 时,tlsroutesreferencegrants 尚不是 v1,导致 Cilium gateway controller 拒绝启动。这类事故说明,CRD version 等集群 prerequisite 不应放在 app overlay,而应置于platform layer repository,并让其 sync wave 更早执行。

下一项实验要做什么

/root/cgoa-repo/ 下亲自创建 apps/checkout/baseoverlays/dev|stage|prod,再通过 kubectl kustomize 查看各环境渲染结果如何不同。随后,把 Application manifest、app-of-apps root 与 AppProject 都写入文件。接下来的实验会通过 kubectl apply -k 把同一结构部署到真实集群。