课程目录(第 23 章 / 共 33 章)
课程/进阶:生产实践

GitOps:用 Git 管理集群状态

23 章 / 共 33·20 分钟·进阶GitOpsArgoCD交付自动纠偏

把「谁在什么时候改了集群」变成一条可审计的 Git 提交:用 ArgoCD 拉取式交付,让集群状态自动向仓库收敛。

学完这一章,你将能够

  • 说得出手工 kubectl apply 与 CI 直连部署的三种典型事故
  • 用 Helm 装好 ArgoCD,写出带自动同步与自愈的 Application 清单
  • 把 argocd 的 sync / diff / history / rollback 对应到熟悉的 kubectl 操作
  • 判断哪些场景不适合开 prune 与 selfHeal

手工 apply 与 CI 直连的三个隐患

前面每一章你都在用 kubectl apply -f。它简单、直接,也几乎一定会在生产环境里出这三类问题。

第一类是状态漂移。 凌晨两点线上出事,有人 kubectl edit deployment 把副本数调大顶住流量,然后在聊天群里说了一句「已处理」。第二天仓库里的 YAML 还是 3 副本,集群里是 10 副本,没人知道哪个才是真相。下次谁再 apply 一次,热修就被覆盖,事故复发。

第二类是没有审计。 谁在什么时候改了哪个字段、为什么改,这些信息只留在某个人的 shell 历史里。等你要复盘时,仓库的提交记录干干净净,集群却变了样。

第三类是把集群凭证交给流水线。 让 CI 拿着 kubeconfig 直接 kubectl apply,意味着流水线系统里存着集群的管理员权限;流水线被攻破,等于集群被交出。而且部署历史散落在每次构建的日志里,想找「上一版是什么」只能翻构建记录。

GitOps 的核心思路是:别让任何东西「推」进集群,让集群自己从 Git「拉」。

GitOps 的四个原则

GitOps 不是某个工具的名字,而是一套约定。业界把它概括为四条,理解了这四条,选哪个工具都不迷路。

  1. 声明式:系统状态全部用声明式描述(原生 YAML、Helm chart、Kustomize 都行),不写「执行步骤」。
  2. 版本化且不可变:期望状态存在 Git 里,每次变更都是一次提交,历史天然可追溯、可对比、可回滚。
  3. 自动拉取:集群里的代理主动从 Git 拉取并对比,而不是由 CI 推。集群凭证不出集群。
  4. 持续协调:控制器不断比较「Git 里的期望」和「集群里的现实」,不一致就纠正——这就是你已经熟悉的控制器模式,只不过这次的期望来源是 Git。
text
开发者 ──发起 PR──▶ Git 仓库(唯一真相)

                     │ ① 拉取 + 对比(pull 模型)

             ArgoCD / Flux 控制器

                     │ ② apply / delete

                目标集群

                     └── ③ 漂移检测:手工改的东西会被改回去

注意第 ③ 步既是 GitOps 最大的价值,也是最容易让人踩坑的地方:你手工做的「紧急修复」会被自动抹掉。后面会讲怎么处理。

ArgoCD 与 Flux 怎么选

两者都能实现上面四条原则,差别主要在架构和交互方式。

维度ArgoCDFlux
架构中心式:一个控制面可以管多个集群分布式:控制器装在每个目标集群里
配置方式Application / ApplicationSet 资源GitRepository + Kustomization 等资源
界面自带 Web UI 与可视化资源树以 CLI 与 CRD 为主,UI 依赖第三方
多集群注册集群后统一视图每集群 bootstrap 一套
Helm 支持原生渲染 chart由 helm-controller 负责
上手成本低,装完就能点点看中,配置全在 CRD 里
适合想要一个统一控制台、跨集群统一管理偏好纯声明式、把 Git 当唯一入口

选型建议很朴素:团队里有人需要「看一眼现在什么状态」,选 ArgoCD;团队全是 SRE、喜欢一切皆 CRD,Flux 更顺手。 这一章用 ArgoCD 演示,因为它的界面让「漂移」这件事看得见。

动手:用 Helm 装 ArgoCD 并拿到初始密码

动手练习:装起 ArgoCD 并登录

ArgoCD 用 Helm 装最省事,chart 名是 argo/argo-cd

bash
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm search repo argo/argo-cd --versions | head -5

helm install argocd argo/argo-cd \
  --namespace argocd --create-namespace
kubectl -n argocd rollout status deploy/argocd-server
kubectl -n argocd get pods

初始管理员密码存在一个 Secret 里,用 jsonpath 取出来:

bash
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d; echo

把 UI 端口转发到本地,浏览器打开 http://localhost:8080,用户名 admin,密码是上面那条命令的输出:

bash
kubectl -n argocd port-forward svc/argocd-server 8080:443

想用命令行,就装 argocd CLI 后登录(自签证书要加 --insecure):

bash
argocd login localhost:8080 --username admin --password '<上面取到的密码>' --insecure
argocd version --client

登录成功后立刻改密码argocd account update-password,然后删掉那个初始密码 Secret。

验证方式是:argocd 命名空间里的 Pod 全部 Runningargocd login 返回登录成功,UI 能进入 Applications 页面。

写第一份 Application 清单

Application 是 ArgoCD 的核心资源,它描述「从哪个仓库的哪个路径,把什么部署到哪个集群的哪个命名空间」。下面这份清单指向官方的示例仓库,可以直接跑起来:

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: guestbook
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

几个字段值得逐一说明:

字段作用注意
source.repoURL清单所在的 Git 仓库私有仓库需要额外配置仓库凭证
source.targetRevision分支、tag 或 commit生产上建议固定 tag 或 commit,不用 HEAD
source.path仓库里的目录目录里可以是原生 YAML、Helm chart 或 Kustomize
destination.server目标集群https://kubernetes.default.svc 表示本集群
destination.namespace目标命名空间配合 CreateNamespace=true 自动创建
syncPolicy.automated自动同步不加就是手动点同步
automated.prune删除 Git 里已不存在的资源最危险也最有用的开关
automated.selfHeal把漂移改回去会覆盖手工热修
bash
kubectl -n argocd apply -f guestbook-app.yaml
kubectl -n argocd get applications
argocd app get guestbook
argocd app sync guestbook
kubectl -n guestbook get deploy,svc,pod

argocd app get guestbook 里的 Sync Status 变成 SyncedHealth 变成 Healthy,并且 guestbook 命名空间里能看到 Deployment 和 Pod,就说明整条链路通了。

现在做一次漂移实验,这是理解 GitOps 最直观的方式:

bash
kubectl -n guestbook scale deploy guestbook-ui --replicas=5
kubectl -n guestbook get deploy guestbook-ui

先看到 5 个副本,等一会儿(ArgoCD 默认约 3 分钟做一次协调)再执行 kubectl -n guestbook get deploy guestbook-ui,副本数会自己回到 Git 里声明的值。这就是「集群向 Git 收敛」——你改的是现实,它改的是期望,最后现实被拉回去。

同步、对比、回滚:argocd 与 kubectl 的对照

如果你已经熟悉 kubectl,下面这张对照表能让你几分钟内上手 argocd CLI。

你想做的事kubectl 做法ArgoCD 做法
应用清单kubectl apply -fargocd app sync <app>
看将要发生的变化kubectl diff -fargocd app diff <app>
看部署历史没有内建能力argocd app history <app>
回滚到某个版本手工改回 YAML 再 applyargocd app rollback <app> <ID>
看应用状态kubectl get / describeargocd app get <app>,UI 有资源树
删除应用及其资源kubectl delete -fargocd app delete <app>
bash
argocd app diff guestbook
argocd app history guestbook
argocd app rollback guestbook 1
argocd app list -o wide
kubectl -n argocd get applications -o wide

argocd app diff 在自动化流水线里特别有用:它把「集群现在和 Git 期望差在哪」列出来,等于一份人类可读的漂移报告。

手动同步作为默认,自动同步作为目标

新接入的应用先关掉 automated,用 argocd app sync 手动同步几次,确认渲染结果符合预期,再打开自动同步。这样能避免一份写错的清单直接铺到集群里。

App of Apps 与 ApplicationSet

一个应用一份 Application 清单,很快会变成几十份。Kubernetes 的惯例解法是「用资源管理资源」。

  • App of Apps:再写一个 Application,它的 path 指向一个目录,目录里全是其他 Application 清单。同步这一个应用,等于同步了下面所有应用。
  • ApplicationSet:用一份模板加一个生成器(Git 目录列表、集群列表、Pull Request 列表)批量生成 Application。适合「同一个应用要部署到 5 个集群」这类场景。

两者都不改变 GitOps 的本质:唯一的输入仍然是 Git 仓库里的文件。

与前面章节的关系

如果你把这一章和前面的内容对照着看,会发现一件很有意思的事:清单本身没有变。

换句话说,GitOps 换掉的只有一件事:谁来执行 `apply`。从「人拿着 kubeconfig 敲命令」,变成「集群里的控制器从 Git 拉取」。

常见坑与速查表

GitOps 里最容易出事的四个点

  • 自愈覆盖手工热修:线上紧急 kubectl edit 之后忘了回写 Git,几分钟后配置被改回去,故障「复发」。正确做法是热修的同时开一个 PR,或者先临时关掉 selfHeal 并记下来。
  • prune 删掉未纳入 Git 的资源:开了 automated.prune 之后,Application 管理范围内、Git 里不存在的资源会被删除。第一次开启前,先 argocd app diff 看清它会删什么。
  • 仓库凭证与权限:用只读的 deploy key 而不是个人访问令牌;ArgoCD 自己的 ServiceAccount 权限也要收,它权限多大,等于谁能改 Git 谁就有多大权限。
  • 镜像 tag 变了但 Git 没变:CI 构建出 app:20240521-1 却没把新 tag 提交回 Git,集群会一直跑旧镜像。GitOps 只管 Git 里写了什么,CI 必须把变更写回仓库(或改成固定 digest 后提交)。
现象原因怎么确认
手工改的配置过一会儿自己变回去了selfHeal 生效,把漂移纠正了argocd app get <app>Sync Status 与最近一次协调时间
同步后某些资源被删了prune: true 且这些资源不在 Git 里argocd app diff <app> 会列出待删除项
应用一直 OutOfSync 但看不出差异有控制器在改同一字段,两边打架argocd app diffignoreDifferences 排除该字段
提示仓库无法访问仓库凭证缺失或过期kubectl -n argocd get secret -l argocd.argoproj.io/secret-type=repository
代码更新了但集群没变CI 没有把新镜像 tag 提交回 Gitargocd app history <app> 看最新 commit 是否包含镜像变更
界面能打开但应用一直 UnknownArgoCD 与目标集群连接失败kubectl -n argocd logs deploy/argocd-application-controller

自测题

自测:为什么 GitOps 强调「拉取」而不是「推送」?(点击展开答案)

推送模型下,CI 系统必须持有集群凭证,凭证泄露就等于集群失守;而且「谁改了集群」的答案散落在各个流水线的日志里,无法统一审计。

拉取模型把凭证留在集群内部:控制器只需要一个 Git 的只读凭证,集群凭证不离开集群。同时,因为期望状态来自 Git 这一份唯一来源,任何人都能通过 argocd app diff 看到「现实和期望差在哪」,漂移变成了一个可观测的指标而不是事故。

自测:开了 `selfHeal` 之后,线上紧急修复该怎么做?(点击展开答案)

先判断这是不是「期望需要永久改变」的修复。如果是,正确顺序是:改 Git → 走 PR → 合并 → 等 ArgoCD 同步;赶时间可以先合并再同步,但变更必须落到 Git。

如果是临时止血(比如临时扩容、临时改阈值),两个选择:一是同步把临时值写进 Git,事后再改回来;二是临时关闭该应用的 selfHeal,但要在故障群里说明并设置提醒。绝不能做的事是改完就走——那只是把问题推迟到下一次协调。

自测:`prune` 为什么既是最有用的开关又是最危险的开关?(点击展开答案)

有用是因为它让「Git 里删掉的资源在集群里也删掉」成为自动行为,这是 GitOps 能做到「Git 是唯一真相」的前提;否则 Git 里删了东西,集群里还留着,状态又不一致了。

危险是因为它的删除依据是「资源不在 Git 里」,而不是「这个资源该被删」。如果你把一个历史遗留的命名空间纳入了 Application 的管辖范围,而它的一部分资源本来就不在 Git 里,同步就会把它们删掉。所以开启前必须用 argocd app diff 确认待删除清单,并且把 Application 的管辖范围划清楚。

小结

  • 手工 kubectl apply 与 CI 直连部署会带来状态漂移、缺少审计、凭证外泄三类问题。
  • GitOps 的四条原则是:声明式、版本化且不可变、自动拉取、持续协调。
  • ArgoCD 是中心式加可视化,Flux 是分布式加纯 CRD,两者都能落地 GitOps。
  • Application 描述「从哪拉、部署到哪」,syncPolicy.automated 决定是否自动同步、是否删除与自愈。
  • argocd app sync/diff/history/rollback 分别对应 apply、diff、无内建能力、手工回滚。
  • GitOps 只改变「谁来 apply」,清单、控制器、RBAC 这些前面的知识全部继续有效。

练习

  1. guestbook 应用的 targetRevisionHEAD 改成某个具体 commit,说明为什么生产上更推荐这样做。
  2. argocd app diff 找出一次漂移,然后加上 ignoreDifferences 让某个字段不再触发 OutOfSync,观察状态变化。
  3. 想一个你手头的项目:它需要几个 Application?如果要做 App of Apps,目录结构会怎么组织?

集群状态交给了 Git,但 Git 里的清单恢复不了数据。下一章 备份与恢复 讲怎么把「删错了」变成一件小事。