课程目录(第 23 章 / 共 33 章)
GitOps:用 Git 管理集群状态
把「谁在什么时候改了集群」变成一条可审计的 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 不是某个工具的名字,而是一套约定。业界把它概括为四条,理解了这四条,选哪个工具都不迷路。
- 声明式:系统状态全部用声明式描述(原生 YAML、Helm chart、Kustomize 都行),不写「执行步骤」。
- 版本化且不可变:期望状态存在 Git 里,每次变更都是一次提交,历史天然可追溯、可对比、可回滚。
- 自动拉取:集群里的代理主动从 Git 拉取并对比,而不是由 CI 推。集群凭证不出集群。
- 持续协调:控制器不断比较「Git 里的期望」和「集群里的现实」,不一致就纠正——这就是你已经熟悉的控制器模式,只不过这次的期望来源是 Git。
开发者 ──发起 PR──▶ Git 仓库(唯一真相)
│
│ ① 拉取 + 对比(pull 模型)
▼
ArgoCD / Flux 控制器
│
│ ② apply / delete
▼
目标集群
▲
└── ③ 漂移检测:手工改的东西会被改回去注意第 ③ 步既是 GitOps 最大的价值,也是最容易让人踩坑的地方:你手工做的「紧急修复」会被自动抹掉。后面会讲怎么处理。
ArgoCD 与 Flux 怎么选
两者都能实现上面四条原则,差别主要在架构和交互方式。
| 维度 | ArgoCD | Flux |
|---|---|---|
| 架构 | 中心式:一个控制面可以管多个集群 | 分布式:控制器装在每个目标集群里 |
| 配置方式 | 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:
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 取出来:
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath='{.data.password}' | base64 -d; echo把 UI 端口转发到本地,浏览器打开 http://localhost:8080,用户名 admin,密码是上面那条命令的输出:
kubectl -n argocd port-forward svc/argocd-server 8080:443想用命令行,就装 argocd CLI 后登录(自签证书要加 --insecure):
argocd login localhost:8080 --username admin --password '<上面取到的密码>' --insecure
argocd version --client登录成功后立刻改密码:argocd account update-password,然后删掉那个初始密码 Secret。
验证方式是:argocd 命名空间里的 Pod 全部 Running,argocd login 返回登录成功,UI 能进入 Applications 页面。
写第一份 Application 清单
Application 是 ArgoCD 的核心资源,它描述「从哪个仓库的哪个路径,把什么部署到哪个集群的哪个命名空间」。下面这份清单指向官方的示例仓库,可以直接跑起来:
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 | 把漂移改回去 | 会覆盖手工热修 |
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,podargocd app get guestbook 里的 Sync Status 变成 Synced、Health 变成 Healthy,并且 guestbook 命名空间里能看到 Deployment 和 Pod,就说明整条链路通了。
现在做一次漂移实验,这是理解 GitOps 最直观的方式:
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 -f | argocd app sync <app> |
| 看将要发生的变化 | kubectl diff -f | argocd app diff <app> |
| 看部署历史 | 没有内建能力 | argocd app history <app> |
| 回滚到某个版本 | 手工改回 YAML 再 apply | argocd app rollback <app> <ID> |
| 看应用状态 | kubectl get / describe | argocd app get <app>,UI 有资源树 |
| 删除应用及其资源 | kubectl delete -f | argocd app delete <app> |
argocd app diff guestbook
argocd app history guestbook
argocd app rollback guestbook 1
argocd app list -o wide
kubectl -n argocd get applications -o wideargocd 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 仓库里的文件。
与前面章节的关系
如果你把这一章和前面的内容对照着看,会发现一件很有意思的事:清单本身没有变。
- 第 5 章 用 YAML 描述 Pod 里写的 Pod 清单,照样能交给 ArgoCD 部署。
- 第 6 章 Deployment 的滚动更新与回滚,仍然由 Deployment 控制器完成,ArgoCD 只负责把新版本的期望写进去。
- Helm 的 chart 可以直接作为
source.path,ArgoCD 会自己渲染。 - 第 12 章 命名空间与 RBAC 里的权限模型,正好用来限制 ArgoCD 能碰哪些资源。
换句话说,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 diff 加 ignoreDifferences 排除该字段 |
| 提示仓库无法访问 | 仓库凭证缺失或过期 | kubectl -n argocd get secret -l argocd.argoproj.io/secret-type=repository |
| 代码更新了但集群没变 | CI 没有把新镜像 tag 提交回 Git | argocd app history <app> 看最新 commit 是否包含镜像变更 |
界面能打开但应用一直 Unknown | ArgoCD 与目标集群连接失败 | 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 这些前面的知识全部继续有效。
练习
- 把
guestbook应用的targetRevision从HEAD改成某个具体 commit,说明为什么生产上更推荐这样做。 - 用
argocd app diff找出一次漂移,然后加上ignoreDifferences让某个字段不再触发OutOfSync,观察状态变化。 - 想一个你手头的项目:它需要几个
Application?如果要做 App of Apps,目录结构会怎么组织?
集群状态交给了 Git,但 Git 里的清单恢复不了数据。下一章 备份与恢复 讲怎么把「删错了」变成一件小事。