10.2 GitOps:持续协调、发布与回滚
传统流水线常在最后执行 kubectl apply:CI 拿到集群凭据,向生产环境推送一次变更。GitOps 把方向反过来:受信任的协调器读取被批准的期望状态,持续比较 live state,并把差异收敛回去。
CI:源代码 → 测试 → 构建不可变制品 → 签名/扫描
CD:期望状态仓库或 OCI artifact → 协调器 → 集群GitOps 不是“所有 YAML 都放进 Git”,关键是声明式状态、版本化来源、自动拉取和持续协调构成控制回路。
期望状态需要可追溯来源
一条可追溯发布链应固定不可变身份:
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 已创建,项目规则与仓库凭据允许访问对应来源和目标。仓库地址及提交占位符需要替换:
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=trueselfHeal 修复受管理对象的手工漂移;prune 删除来源中已移除的受管理对象。错误提交的影响会随授权范围扩散,所以要限制项目可写目标,配置同步窗口和健康检查。PruneLast=true 只把删除放到其他同步波次成功且资源健康之后,不是删除确认;关键对象可另设 Prune=confirm。
固定 targetRevision 到提交后,协调器不会追随分支上的后续提交。晋级需要通过管理该 Application 的流程更新引用;只提交新的配置,应用仍会停在旧提交。
Argo CD 开启自动同步时不允许直接执行历史版本 rollback。通常应 revert 配置或恢复已知良好的 digest,生成新的期望状态提交。若像示例这样固定提交,还要把 targetRevision 更新到该提交;若跟踪受保护分支,新提交才会随分支更新被发现。
仓库边界映射权限与发布节奏
一种常见布局:
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 各构建一次:
构建一次 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 或父应用管理,否则手工关掉的自动同步也可能被改回来。流程应预先定义:
- 谁能暂停哪个应用,凭据多长时间过期;
- 何时记录工单、命令和影响范围;
- 服务恢复后,如何把有效修复提交回期望状态;
- 如何恢复协调并验证没有二次回滚。
没有回归路径的 kubectl edit 会成为下一次事故的种子;完全禁止紧急操作又会拖慢恢复。GitOps 的目标是让例外可控、短暂、可追溯。
上线前的控制面检查
- 来源是否固定到可追溯 revision 或 digest?
- 协调器是否只能写入授权 namespace 和资源类型?
prune、selfHeal、删除确认和同步窗口是否匹配风险?- CRD 与其实例、数据库迁移与应用版本是否有顺序约束?
- 控制器自身故障时,当前工作负载是否继续运行?
- 能否从 Git revision 定位到运行中的 image ID?
- 紧急暂停、回滚和恢复协调是否演练过?
IaC 与 GitOps 都使用声明式配置,但所有权不同:前者通常管理云资源和集群底座,后者持续管理集群内期望状态。让两个控制器同时改同一字段,最终得到的不是“双保险”,而是永不停止的争夺。
参考资料
OpenGitOps, GitOps Principles
Argo CD, Automated Sync Policy
Argo CD, Sync Options
Flux, Core Concepts
Argo CD, Tracking strategies
GitHub, About code owners
Flux, SOPS integration
Secrets Store CSI Driver, Optional Kubernetes Secret synchronization