很早以前,我就想过一件事:能不能把线上所有与 Kubernetes 有关的资源、配置和部署状态整理成文件,统一放进 Git?

那时我还没有认真了解 GitOps,更谈不上用一套成熟的方法论去设计交付平台。我只是越来越难接受一种现实:一个系统明明每天都在运行,但没有任何一个地方能完整说明它现在究竟是什么样子。

Deployment、Service、Ingress、ConfigMap 和 HPA 散落在不同 namespace;镜像版本要去 Pod、Jenkins 日志和发布记录里交叉确认;dev、test、stga、prod 的差异藏在几份相似却不完全相同的 YAML 里;生产又分为 prda、prdb 两套 A/B 环境。有人通过 Jenkins 的 sed 改过文件,有人直接执行过 kubectl apply,也有人为了让配置生效做过一次 kubectl rollout restart。Nacos 控制台里还有另一部分配置,而这些配置未必与本地文件一致。

系统当然可以继续运行,但它的知识被拆散了。一部分在集群里,一部分在 Jenkins Workspace 里,一部分在脚本中,还有一部分只存在于运维人员的记忆里。

我最初想解决的,就是这种混乱。

先把现实保存下来

当时我设想的路径其实很简单:

线上 Kubernetes 集群

导出并清洗 YAML

放入 Git

形成可追溯的配置档案

先弄清楚线上到底有哪些资源,每个服务部署在哪个 namespace,正在运行什么镜像,几个环境之间有什么差异。配置改过什么、是谁改的、为什么改,出现故障时能不能从历史版本里找到线索,甚至快速恢复到上一个可信状态。

这一步更接近基线化(Baseline)、基础设施即代码(Infrastructure as Code)和配置即代码(Configuration as Code):把线上现状文件化,把运维资产版本化,让系统知识不再依赖某个人是否记得。

它很重要,但严格来说还不等于完整的 GitOps。因为这时的 Git 更像一份档案:现实先在集群里发生,然后我们再把现实抄回 Git。它能回答“线上曾经是什么”,却还不能保证“线上应该是什么”。如果有人绕过仓库直接修改集群,Git 里的文件很快又会过期。

不过正是在整理这件事的过程中,我开始隐约觉得,只保存现实还不够。真正的问题不是 YAML 有没有备份,而是谁有资格定义系统的真实状态。

后来,我才知道这叫 GitOps

后来接触 Argo CD、Kustomize 和 GitOps,我才发现,自己原先那个模糊的想法已经被行业继续向前推了一步。

GitOps 不只是把 YAML 放进 Git,也不等于安装一个 Argo CD。它更核心的变化,是让 Git 从“现实的档案馆”逐渐成为“现实的定义者”,也就是单一事实来源(Single Source of Truth)。

Git 中的期望状态

Argo CD 持续比较

发现 Git 与集群差异

将 Kubernetes 调整为 Git 中声明的状态

Git 中保存的是期望状态(Desired State),Kubernetes 集群里运行的是实际状态(Live State)。Argo CD 持续比较两者,并负责协调(Reconciliation)。两边一致时,应用显示为 Synced;出现差异时,则会成为 OutOfSync。同步之后,集群重新回到 Git 所声明的状态。

这也意味着,人工直接修改集群不再只是一条临时命令,而是一次可被发现的配置漂移(Configuration Drift)。过去,“先在线上改了再说”很容易沉入历史;现在,它会明确地暴露为期望与现实之间的偏差。

这就是我认知里的那个转折:第一阶段是把现实保存到 Git,第二阶段则是让 Git 定义现实。前者让系统变得可见,后者让系统开始可控。

工具不是方法论本身

理解这一点后,几个经常被放在一起讨论的工具也更容易区分。

GitOps 是思想和方法论;Git 保存期望状态以及完整的变更历史;Kustomize 管理基础 YAML 与不同环境之间的差异;Helm 通过模板和 values 生成 Kubernetes YAML;Argo CD 将 Git 中声明的状态持续同步到 Kubernetes;Jenkins 则继续负责代码编译、测试、镜像构建和推送 ACR。

Helm 和 Kustomize 不是 Git 的替代品,它们产生或组织的内容都可以,也应该进入 Git。对于已经积累了大量现有 YAML 的团队,Kustomize 往往更适合起步:先保留可读的基础资源,再用 overlay 表达 dev、test、stga、prod 以及 prda、prdb 的差异。等服务结构足够统一、复用需求更明确时,再考虑用 Helm 做更高程度的模板化。

工具本身不会自动带来秩序。把一堆无人维护的 YAML 放进 Git,再让 Argo CD 自动同步,只会让混乱同步得更快。真正需要先建立的是资源边界、目录结构、变更审核、敏感信息处理和回滚规则。

GitOps 不需要推翻 A/B 发布

我们现有的生产发布带有明显的 A/B 特征:先确认 A/B 当前版本,把流量切到 A,确认 B 已经没有流量,再更新 B 的代码、镜像和 Nacos 配置;验证 B 后切流到 B,更新并验证 A,最后再让 A/B 合流。

这套流程解决的是生产发布中的风险隔离与业务验证。GitOps 没有必要推翻它。需要改变的,主要是中间“谁来驱动 Kubernetes 发生变化”。

过去可能是:

Jenkins
→ sed 修改 YAML
→ kubectl apply
→ 集群发生变化

未来可以逐步变成:

Jenkins 构建镜像
→ 推送阿里云 ACR
→ 修改 Git 中的镜像 tag
→ Merge Request 审核
→ Argo CD 同步
→ Kubernetes 更新

Jenkins 不再直接把某个临时 Workspace 里的文件推向集群,而是提交一份可审查、可追踪的状态变更。至于 A/B 切流、SQL 执行、Nacos 更新、业务监控、Redis 观察、Splunk 日志检查、服务预热和最终合流,仍然应该保留。GitOps 改造的是部署控制面,不是抹掉现有发布经验。

更现实的落地顺序,也不该从生产环境一键自动同步开始。我更愿意先建立仓库,导出并清洗线上 YAML,用 Kustomize 整理环境差异,再通过人工 apply 验证这些文件确实能够描述现状。之后让 Argo CD 只接管 dev,稳定后扩展到 test、stga,最后才谨慎进入 prod。

这条路看起来慢,但每一步都在校验一件关键的事:Git 里的蓝图是否真的可信。

Nacos 也不该留在 Git 之外

如果 Kubernetes 已经进入 Git,而 Nacos 配置仍然只能在控制台里查找和修改,那么系统的蓝图依然是不完整的。Nacos 配置也可以,并且应该逐步纳入 Git 管理。

理想链路可以是:Git 中的 Nacos 配置经过 Merge Request 审核,由 Jenkins Pipeline 或专门的配置同步工具发布到指定 Nacos namespace,再校验配置内容或 MD5,最后由应用动态刷新,或者按需要触发滚动重启。

Argo CD 主要管理 Kubernetes 资源,Nacos 不一定非要由 Argo CD 发布。只要 Git 是被审查过的配置来源,发布过程可重复、结果可校验、变化可追溯,它仍然是在实践 GitOps 的思想。

当然,密码、AccessKey、Token 等敏感数据不能明文进入 Git。它们可以通过环境变量和占位符注入,也可以交给 Vault、SOPS、KMS 或 External Secrets 等方案管理。更重要的是,一旦 Git 被确立为配置来源,就不应长期允许 Git 和 Nacos 控制台各自独立演化。紧急情况下可以在控制台修改,但事后必须回写 Git,否则下一次发布就可能用“正确流程”覆盖掉那次紧急修复。

我想留下的是一张可以维护的蓝图

回头看,我最初并不是因为知道 GitOps 才想这样做,而是现实中的混乱让我自然地走向了 GitOps。

我想保存的从来不只是几份 YAML。我真正想解决的是:系统的真实状态不应该散落在集群、脚本、控制台和人的记忆里。它应该有一份可以被阅读、比较、审查、回滚,并且能够持续维护的蓝图。

专业术语的价值,有时并不是替我们产生想法,而是让我们发现,自己从具体问题中形成的直觉,原来已经有一整套成熟的方法论在支撑。Infrastructure as Code、Configuration as Code、Desired State、Single Source of Truth、Configuration Drift 和 Reconciliation,并不是为了让一件事显得更专业,而是在从不同角度回答同一个问题:怎样让复杂系统的变化留下依据,并最终回到一个可信的状态。

GitOps 并不是从安装 Argo CD 开始的,而是从一个更早的问题开始:我们究竟允许谁来定义系统的真实状态?