为什么需要 GitOps
有句话说,运维的终极形态是让开发者忘记运维的存在。
传统的 CI/CD 流程里,Jenkins 或者 GitLab CI 跑完构建,直接调用 kubectl apply 把资源推到集群。这种方式有个致命问题:集群的实际状态和 Git 里的声明可能早就分叉了。
有人手动改了 ConfigMap,有人用 kubectl edit 调了副本数,这些变更不会回流到 Git。时间一长,没人说得清线上到底跑的是什么配置。出了问题排查的时候,大家对着屏幕发呆,谁也不知道最后一次正确的配置长什么样。
GitOps 的核心思想很简单:Git 仓库是唯一的真相来源。
集群状态必须和 Git 里的声明保持一致。任何变更都要通过 Git 提交触发,而不是直接操作集群。这样做的好处显而易见:
- 所有变更都有审计记录,谁在什么时间改了什么,一目了然
- 回滚就是 git revert,不需要翻 CI 日志找上一次成功的构建
- 环境可以完整复现,新机器拉下来跑一遍就是完整状态
- 权限控制收敛到 Git 层面,不用再给一堆人发 kubeconfig
这套思路听起来不复杂,但真正落地的团队并不多。很多人卡在"怎么做"这一步。
ArgoCD 和 Kustomize 各自解决什么问题
ArgoCD 是 GitOps 的执行引擎。它持续监听 Git 仓库的变化,自动把声明式配置同步到 Kubernetes 集群。
更关键的是,ArgoCD 会不断对比 Git 里的期望状态和集群的实际状态。一旦发现漂移,可以选择自动修复或者告警。这种持续 reconcile 的机制,才是 GitOps 区别于传统 CI/CD 的本质。
传统的 CI/CD 是推模式:流水线跑完就把产物推到目标环境,推完就不管了。ArgoCD 是拉模式:集群里的 controller 主动去拉 Git 里的最新状态,持续保证一致性。
拉模式的好处是集群不需要暴露 API 给外部系统。CI 服务器不需要持有集群凭证,安全边界收窄了很多。
Kustomize 解决的是多环境配置管理的问题。
你有 dev、staging、prod 三套环境,它们的 Kubernetes 资源定义大部分相同,但有些字段不一样。比如镜像 tag、副本数、资源限额、Ingress 域名。这些差异点如果靠人工复制粘贴 YAML 来维护,迟早会出事。
Helm 用模板和 values 文件来处理这种差异,但模板多了会变成另一种编程语言。调试 Helm chart 的痛苦,用过的人都懂。Kustomize 走的是另一条路:基础配置 + 补丁叠加。
你定义一个 base 目录放通用配置,每个环境一个 overlay 目录,里面只写差异部分。Kustomize 会把它们合并成最终的 manifest。没有模板语法,没有渲染过程,YAML 就是 YAML。
这种分层结构天然适合 GitOps 的场景。ArgoCD 原生支持 Kustomize,你只需要告诉它去监听哪个 overlay 目录,它就知道怎么渲染和同步。
Git 仓库的结构设计
仓库怎么组织,直接决定了后续维护的成本。这是很多团队容易忽略的一步。
一种常见的做法是把应用代码和部署配置放在同一个仓库的不同目录。这对于小团队、单体应用来说够用。但对于多服务、多团队的场景,独立的部署仓库(deploy repo)往往是更好的选择。
独立的 deploy-repo 有几个明显的好处。部署变更和应用代码变更解耦,CI 流水线不需要每次都触发部署。权限可以独立管理,开发者可以改应用代码但不一定能改 prod 配置。多个服务可以共享同一个 deploy-repo,方便做全局性的变更。
|
|
base 目录放所有环境共享的基础资源定义。overlays 下每个环境一个子目录,只包含这个环境特有的补丁。
apps 目录里放的是 ArgoCD 的 Application 资源定义,每个环境一个文件。这些文件告诉 ArgoCD 去监听哪个路径、同步到哪个集群。把 Application 定义也放在 Git 里,这就是所谓的"管理 Application 的 Application",ArgoCD 社区称之为 App of Apps 模式。
这种结构的好处是职责清晰。开发者改应用配置在 overlays 里提 PR,运维改基础设施在 base 里操作。权限可以按目录划分,prod overlay 的变更可以设置更严格的审批流程。
Kustomize 的分层策略详解
来看一个具体的例子,把分层策略讲透。
base/deployment.yaml 定义了通用的 Deployment:
|
|
这是所有环境共享的"骨架"。健康检查、端口、标签选择器,这些东西不应该随环境变化。
dev 环境可能只需要调整副本数:
|
|
|
|
dev 环境加了 namePrefix 和 commonLabels,这样 dev 环境的资源会自动带上 dev- 前缀和 env: dev 标签,避免和其他环境的资源冲突。
prod 环境需要更多定制:
|
|
images 字段是 Kustomize 的内置功能,可以在不修改原始 YAML 的情况下替换镜像 tag。这在 CI 流水线里特别有用,构建完新镜像后直接更新这个字段就行。
resource-patch.yaml 里调整资源配额:
|
|
把三个环境的差异点整理成表格,一目了然:
| 配置项 | dev | staging | prod |
|---|---|---|---|
| 副本数 | 1 | 2 | 3~5 |
| CPU 请求 | 100m | 200m | 500m |
| 内存请求 | 128Mi | 256Mi | 512Mi |
| CPU 限额 | 200m | 500m | 1000m |
| 内存限额 | 256Mi | 512Mi | 1Gi |
| 镜像 tag | latest | rc 版本 | 稳定版本 |
| HPA | 无 | 无 | 有 |
| PDB | 无 | 无 | 有 |
表格里的每一个差异点,就是 overlays 里需要覆盖的内容。base 里保持最小公共集,overlay 里只写差异。这就是 Kustomize 的精髓。
ArgoCD 的 Application 配置
ArgoCD 通过 Application 资源来管理部署。每个环境对应一个 Application:
|
|
syncPolicy.automated 这个字段很关键。开启后 ArgoCD 会自动同步 Git 的变更到集群。prune 表示删除 Git 里移除的资源,selfHeal 表示自动修复集群里的漂移。
retry 配置了同步失败后的重试策略。网络抖动、API Server 短暂不可用,这些情况都会触发重试。maxDuration 限制了重试的总时长,避免无限重试占用资源。
ApplyOutOfSyncOnly 是一个性能优化选项。默认情况下 ArgoCD 每次 sync 都会 apply 所有资源,即使它们没有变化。开启这个选项后,只 apply 有差异的资源,对于大应用能显著减少 sync 时间。
targetRevision 可以指定分支、tag 或者 commit SHA。对于 prod 环境,建议用 tag 而不是分支名,这样能确保部署的是经过验证的版本。dev 环境可以用 main 分支,追求快速迭代。
多环境的渐进式交付设计
渐进式交付的核心思想是:把风险控制在最小范围。
不是一口气把新版本推给所有用户,而是从小范围开始验证,逐步扩大覆盖面。这个过程中,任何阶段发现问题都可以快速回滚。
一个典型的完整流程是这样:
- 开发者在 feature 分支写完代码,提 PR 到 main
- CI 跑单元测试、集成测试、镜像扫描
- 测试通过,构建镜像,推送到镜像仓库,tag 为 commit SHA
- CI 更新 deploy-repo 的 dev overlay,把镜像 tag 改成新的 SHA,提交 PR
- ArgoCD 自动同步 dev 环境
- dev 环境跑 smoke test,验证基本功能
- 验证通过,CI 更新 staging overlay,提 PR
- staging 环境跑端到端测试和性能测试
- 验证通过,CI 更新 prod overlay,提 PR
- 人工 review PR,确认后 merge
- prod 环境开始金丝雀发布,逐步切流量
这里面有几个关键的设计决策。
dev 环境用 latest tag 或者 commit SHA,追求速度。staging 用 rc 版本,做完整验证。prod 用 semver 格式的稳定版本,确保可追溯。
prod 的 PR 不应该自动 merge。即使所有测试都通过了,也应该有人看一眼 diff,确认这次发布的内容是预期内的。这是人机协作的最后一道防线。
金丝雀发布的具体实现
ArgoCD 配合 Argo Rollouts 可以实现精细化的金丝雀发布。
|
|
这段配置的流程是:先把 10% 的流量切到新版本,观察 5 分钟。没问题的话切到 20%,跑一个 Analysis 检查成功率和延迟。通过的话切到 40%,再观察 10 分钟。全程无异常,切到 100%。
Analysis 模板可以对接 Prometheus 数据源,自动检查业务指标:
|
|
这个 Analysis 每分钟检查一次成功率,如果低于 95%,连续失败 3 次就会触发自动回滚。不需要人工介入,不需要半夜爬起来看监控。
Git 作为 Single Source of Truth 的工程实践
Git 作为唯一真相来源,听起来简单,落地的时候有不少坑要踩。
权限控制
不是所有人都应该有权限直接 push 到 main 分支。合理的做法是:
- 开发者只能提 PR,不能直接 merge
- 至少一个人 review 后才能 merge
- prod overlay 的变更需要额外的 approver,通常是 SRE 或者 Tech Lead
- 用 CODEOWNERS 文件按目录指定 reviewer
Git 平台(GitHub、GitLab、Gitea 等)都支持分支保护规则和 CODEOWNERS。把这些规则配好,权限控制就在 Git 层面闭环了,不需要额外建一套审批系统。
密钥管理
Git 仓库里绝对不能存明文密钥。这是底线,没有例外。常见的做法有几种:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| Sealed Secrets | 用集群公钥加密,只有集群内的 controller 能解密 | 中小规模,单集群 |
| External Secrets | 从 Vault、AWS SM 等外部系统拉取 | 多集群,已有 Vault |
| SOPS | 用 GPG 或云 KMS 加密 YAML 文件 | 需要 Git 内可见结构 |
SOPS 配合 Kustomize 使用很顺畅。把加密后的配置文件放在仓库里,ArgoCD 同步前用 config management plugin 解密。加密的文件在 Git 里看起来是一堆乱码,但文件名和目录结构还是可读的。
镜像 tag 的更新策略
CI 构建完新镜像后,怎么更新 deploy-repo 里的 tag?这个问题有几种解法。
最直接的做法是 CI 流水线里直接 git commit 更新 kustomization.yaml。这要求 CI 系统有 deploy-repo 的写权限,需要配一个 deploy key 或者 bot token。
另一种做法是用 ArgoCD Image Updater。它监听镜像仓库的新 tag,自动更新 Git 仓库或者直接更新 Application。这种方式把镜像更新的逻辑从 CI 里解耦出来,CI 只管构建和推送。
对于 dev 环境,可以配置自动追踪 latest tag 或者 main 分支的最新 commit。对于 prod 环境,只接受 semver 格式的 tag,并且需要人工确认。
|
|
环境漂移的检测
即使开启了 selfHeal,也应该定期跑 drift detection。ArgoCD 的 CLI 提供了 argocd app diff 命令,可以对比 Git 和集群的差异。
把这个命令放到 CronJob 里,发现漂移就发告警到 Slack 或者飞书。虽然 ArgoCD 会自动修复,但告警能帮你发现问题的根源——是不是有人绕过 Git 直接改了集群,是不是某个 operator 在自动修改资源。
漂移本身不可怕,可怕的是不知道漂移发生了。
落地过程中的几个决策点
单仓库还是多仓库?
小团队、服务少,应用代码和部署配置放同一个仓库没问题。服务多了、团队多了,独立的 deploy-repo 更容易管理权限和变更流程。
一个判断标准:如果你的 CI 流水线每次代码变更都要触发部署,放同一个仓库。如果部署变更和代码变更的频率差异很大,分开更好。
分支策略还是目录策略?
多环境可以用分支隔离(dev 分支对应 dev 环境),也可以用目录隔离(main 分支下不同 overlay)。
目录策略更直观,所有环境的配置都在一个分支里,方便对比差异。分支策略的好处是环境间完全隔离,但 merge 冲突处理起来麻烦,而且很难做跨环境的 diff。
大多数场景下目录策略够用。如果你的合规要求 prod 环境完全独立,可以考虑分支策略。
自动同步还是手动触发?
dev 和 staging 可以全自动,prod 建议半自动。ArgoCD 的 UI 提供了手动 sync 按钮,配合审批流程,能在便利性和安全性之间找到平衡。
也可以用 Sync Window 来限制自动同步的时间范围。比如 prod 环境只在工作日的 10:00-16:00 允许自动同步,其他时间需要手动触发。
Helm 还是 Kustomize?
如果你的团队已经重度使用 Helm,没必要强行切换到 Kustomize。ArgoCD 同时支持两者。
Kustomize 的优势是不需要学模板语法,YAML 就是 YAML,调试的时候所见即所得。Helm 的优势是生态丰富,很多现成的 chart 可以用,参数化能力更强。
两者也可以结合:用 Helm 生成基础 manifest,用 Kustomize 做环境级别的定制。ArgoCD 支持 Helm + Kustomize 的混合模式,在 Application 的 source 里配好就行。
监控和可观测性
GitOps 流水线搭好后,监控不能缺。不然出了问题你连 ArgoCD 本身是不是正常的都不知道。
ArgoCD 暴露了 Prometheus metrics,可以监控这些指标:
argocd_app_sync_total:同步次数和失败次数argocd_app_health_status:应用健康状态分布argocd_app_reconcile_count:reconcile 频率argocd_cluster_info:集群连接状态
把这些指标接到 Grafana,建一个 dashboard,能直观看到所有环境的部署状态。ArgoCD 社区有现成的 dashboard 模板,拿来改改就能用。
告警规则也要配好。同步失败超过 3 次、应用长时间处于 Progressing 状态、资源漂移频繁,这些都应该触发告警。告警要分级,prod 的告警要能打电话,dev 的告警发个消息就行。
日志方面,ArgoCD 的 application controller 和 repo server 的日志里能查到同步的详细信息。排查问题的时候,这些日志是主要线索。建议把日志接到统一的日志平台,方便检索和关联分析。
容易忽略的细节
资源删除的保护
开启 prune 后,从 Git 里删除一个资源会导致 ArgoCD 把它从集群里也删掉。对于 StatefulSet、PVC 这类有状态资源,这可能不是期望的行为。数据丢了就是丢了,Git revert 救不回来。
可以在资源上加注解 argocd.argoproj.io/sync-options: Prune=false,阻止自动删除。对于数据库、消息队列这类有状态服务,这个注解是必须加的。
大仓库的性能问题
当 deploy-repo 里有几百个 Application,ArgoCD 的 reconcile 可能会变慢。每次 sync 都要遍历所有资源,对比差异,这个开销不小。
这时候需要考虑分片部署,多个 ArgoCD 实例分担负载。或者把 Application 按团队拆分到不同的 project,每个 project 配独立的 sync 间隔。
多集群管理
如果 prod 环境分布在多个集群(多 region、多可用区),可以用 ArgoCD 的 ApplicationSet 来批量管理。定义一个模板,ArgoCD 会为每个集群生成一个 Application。
|
|
一段配置搞定所有 prod 集群的部署,新增集群只需要加标签,不需要改 ApplicationSet。
回滚策略
GitOps 的回滚就是 git revert。但要注意,revert 之后提 PR、review、merge 的流程不能省。紧急情况下可以临时绕过,但事后一定要补上审计记录。
ArgoCD UI 也提供了 rollback 按钮,本质上是把 targetRevision 切回之前的 commit。这种方式更快,但绕过了 Git 流程。建议只在紧急故障时用,事后把 rollback 操作也补成一个 Git commit,保持历史完整。
回滚的速度取决于 ArgoCD 的 sync 间隔。默认 3 分钟检测一次 Git 变更,紧急情况下可以手动触发 sync,把回滚时间压缩到秒级。
GitOps 不是银弹。它解决的是配置管理和部署流程的问题,但对于应用本身的架构设计、监控告警、故障排查,帮不上太多忙。
把 Git 作为 Single Source of Truth,本质上是在用版本控制的思维管理基础设施。这种思维转变比工具选型更重要。一旦团队接受了"所有变更都要经过 Git"这个约束,很多运维问题会自然消失。
ArgoCD 和 Kustomize 的组合,在易用性和灵活性之间找到了不错的平衡。Kustomize 的分层结构天然契合多环境场景,ArgoCD 的持续 reconcile 保证了声明和实际的一致性。两者配合 Image Updater 和 Argo Rollouts,能搭建出从构建到发布的完整自动化流水线。
渐进式交付不是可选项,而是生产环境的必备能力。从小流量验证开始,逐步扩大范围,配合自动化分析,能把发布风险降到可控范围。出了问题自动回滚,比半夜被 oncall 叫醒手动操作靠谱得多。
这套体系搭好后,开发者只需要关心代码和业务逻辑。提交 PR、等待 CI 通过、review 后 merge,剩下的事情 ArgoCD 会处理。运维的工作从手动操作变成了规则制定和异常处理。这才是 GitOps 真正的价值——把人从重复劳动里解放出来,去做更有意义的事。