跳到内容

第 7 卷 / 第 10 章 / 第 2 课

10.2 GitOps:持续协调、发布与回滚 ​

传统流水线常在最后执行 kubectl apply:CI 拿到集群凭据,向生产环境推送一次变更。GitOps 把方向反过来:受信任的协调器读取被批准的期望状态,持续比较 live state,并把差异收敛回去。

text
CI:源代码 → 测试 → 构建不可变制品 → 签名/扫描
CD:期望状态仓库或 OCI artifact → 协调器 → 集群

GitOps 不是“所有 YAML 都放进 Git”,关键是声明式状态、版本化来源、自动拉取和持续协调构成控制回路。

期望状态需要可追溯来源 ​

一条可追溯发布链应固定不可变身份:

text
source commit
  → image: registry.example/atlas@sha256:...
  → deployment manifest commit / signed OCI artifact
  → reconciler observed revision
  → running Pod imageID

环境配置中应使用 image digest,或由自动化把经过验证的 digest 更新进仓库。只写 :latest 会让同一个 Git commit 在不同时间解析成不同二进制,回滚和审计都会失真。

图中的 digest 是占位写法,部署时要替换为完整值。多架构镜像的索引 digest 与节点实际运行的平台镜像 digest 可能不同,审计链要保留解析关系,不能只比较两个字符串。配置来源也可以是控制器支持的 OCI artifact 或 Helm repository;固定版本时还要确认仓库是否允许覆盖同版本内容。

协调循环不是一次部署 ​

以 Argo CD 为例,Application 描述来源、目标和同步策略。以下示例要求 Argo CD 已安装、production AppProject 和 atlas-prod namespace 已创建,项目规则与仓库凭据允许访问对应来源和目标。仓库地址及提交占位符需要替换:

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: atlas-api
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://git.example.com/platform/env-config.git
    targetRevision: "<approved-full-commit-sha>"
    path: clusters/prod/atlas-api
  destination:
    server: https://kubernetes.default.svc
    namespace: atlas-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - PruneLast=true

selfHeal 修复受管理对象的手工漂移;prune 删除来源中已移除的受管理对象。错误提交的影响会随授权范围扩散,所以要限制项目可写目标,配置同步窗口和健康检查。PruneLast=true 只把删除放到其他同步波次成功且资源健康之后,不是删除确认;关键对象可另设 Prune=confirm。

固定 targetRevision 到提交后,协调器不会追随分支上的后续提交。晋级需要通过管理该 Application 的流程更新引用;只提交新的配置,应用仍会停在旧提交。

Argo CD 开启自动同步时不允许直接执行历史版本 rollback。通常应 revert 配置或恢复已知良好的 digest,生成新的期望状态提交。若像示例这样固定提交,还要把 targetRevision 更新到该提交;若跟踪受保护分支,新提交才会随分支更新被发现。

仓库边界映射权限与发布节奏 ​

一种常见布局:

text
env-config/
├── apps/
│   └── atlas-api/base/
└── clusters/
    ├── staging/atlas-api/
    └── prod/atlas-api/

应用团队维护 base 和应用参数,平台团队维护集群能力与策略,生产环境变更要求相应代码所有者审批。仅放置 CODEOWNERS 文件不会强制审批,还需启用对应分支保护或仓库规则。不要复制整套 YAML 形成三个逐渐分叉的环境;用 Kustomize overlays、Helm values 或其他明确的组合方式,只表达必要差异。

仓库也不应大到任何一个提交都触发所有集群重算。按权限、故障半径和变化频率划分,比追求形式上的 mono-repo 或 multi-repo 更重要。

Promotion 是提升同一个制品 ​

正确的环境晋级不是在 staging 和 prod 各构建一次:

text
构建一次 image digest D
  → staging 引用 D
  → 集成、性能与策略验证
  → PR 将 prod 引用更新为同一个 D
  → 分批发布与业务验证

这样生产问题可以追溯到同一个已测试制品。环境专属配置应与制品身份分离,数据库迁移还要明确向前兼容、执行顺序和失败恢复,不能假设回滚镜像就能回滚数据。

Secret 不能以明文进入期望状态 ​

Git 可以保存“需要哪个秘密”的声明,但不应保存秘密明文。常见方案包括:

  • External Secrets 一类控制器从云 Secret Manager 或 Vault 拉取;
  • SOPS 加密清单,密钥放在 KMS,并配置协调器支持的解密集成;
  • Secrets Store CSI Driver 从外部存储挂载秘密;未启用可选同步时,可以不创建 Kubernetes Secret 对象。

Base64 只是编码。即使使用加密文件,也要限制解密身份、轮换密钥、避免秘密出现在 diff、日志和渲染后的 artifact 中。

Break-glass 必须设计回归路径 ​

事故中可能需要暂停协调或直接修补 live state。先确认 Application 是否又受 ApplicationSet 或父应用管理,否则手工关掉的自动同步也可能被改回来。流程应预先定义:

  1. 谁能暂停哪个应用,凭据多长时间过期;
  2. 何时记录工单、命令和影响范围;
  3. 服务恢复后,如何把有效修复提交回期望状态;
  4. 如何恢复协调并验证没有二次回滚。

没有回归路径的 kubectl edit 会成为下一次事故的种子;完全禁止紧急操作又会拖慢恢复。GitOps 的目标是让例外可控、短暂、可追溯。

上线前的控制面检查 ​

  • 来源是否固定到可追溯 revision 或 digest?
  • 协调器是否只能写入授权 namespace 和资源类型?
  • prune、selfHeal、删除确认和同步窗口是否匹配风险?
  • CRD 与其实例、数据库迁移与应用版本是否有顺序约束?
  • 控制器自身故障时,当前工作负载是否继续运行?
  • 能否从 Git revision 定位到运行中的 image ID?
  • 紧急暂停、回滚和恢复协调是否演练过?

IaC 与 GitOps 都使用声明式配置,但所有权不同:前者通常管理云资源和集群底座,后者持续管理集群内期望状态。让两个控制器同时改同一字段,最终得到的不是“双保险”,而是永不停止的争夺。

参考资料 ​

Built with VitePress | Software Systems Atlas