一、引言:部署成功之后,集群为什么还是"不可复制"
前面的文章已经解决了"怎么把应用部署进 Kubernetes":手写 YAML 能理解对象关系,Helm 能模板化单个服务,Helmfile 能组织多服务栈。但生产团队很快会遇到另一类问题:
- 测试环境改过的参数,生产环境是不是也改了?
- 某次故障时有人临时 kubectl edit,一周后谁还记得?
- Git 里写着 3 个副本,集群里为什么实际是 8 个?
- 新集群要恢复同一套系统,究竟该以文档、终端历史,还是某个同事的本地目录为准?
- 一次发布涉及多个对象,如何审计谁改了什么、如何可靠回退?
这些问题的根源,不是缺少部署工具,而是缺少持续一致的目标状态管理机制。GitOps 给出的答案是:把系统期望状态声明在 Git 中,由集群内的控制器持续比较并收敛实际状态。
本文以 Argo CD 为例,完成一条可复用的持续交付链路。
开篇核心认知:GitOps 不是"把 kubectl apply 搬到 CI 里执行",而是拉取式、声明式、持续 reconcile。CI 负责构建与验证,Git 保存目标状态,Argo CD 负责让集群持续接近这个状态。
二、原理解析:Argo CD 如何把 Git 变成集群事实源
2.1 Push 与 Pull:交付模型的根本变化
传统 CI/CD 常采用 Push 模式:
代码提交 → CI 构建 → CI 持有集群凭据 → kubectl/Helm 修改生产集群
GitOps 通常采用 Pull 模式:
代码提交 → CI 构建/测试/推送镜像 → 更新 Git 中的镜像版本
│
▼
Argo CD 拉取声明
│
▼
比较并收敛 Kubernetes 状态
二者最关键的差别是: - CI 不必长期持有生产集群的高权限凭据;
- 集群侧控制器持续运行,而不是只在流水线执行时工作;
- 人工绕过 Git 的修改可以被识别为漂移;
- 所有变更天然具备提交记录、评审记录和回滚入口。
边界必须说清:Argo CD 是持续交付与状态协调工具,不负责编译代码、运行单元测试或构建镜像,这些仍属于 CI。
2.2 GitOps 的四个基本约束
- 声明式:描述 Deployment、Service、 replicas 等目标状态,而不是记录一串操作步骤。
- 版本化且不可变:目标状态进入 Git,有明确提交、评审与历史。
- 自动拉取:集群中的代理主动获取已批准的目标状态。
- 持续协调:不断比较目标与实际,发现偏差后提示或修复。
因此,Git 中保存的是"想要集群成为什么样",而不是"今天早上执行过哪些命令"。
2.3 Argo CD 的核心对象与组件
名称
作用
运维关注点
Application
一个应用及其 Git 来源、目标集群、同步策略
最基本的交付单元
AppProject
限制可用仓库、目标集群/命名空间、资源类型和角色
多租户边界,不要所有应用都用 default
ApplicationSet
按规则批量生成 Application
多环境、多集群、目录型应用
Repository
Git/Helm/OCI 等来源及凭据
私有仓库凭据与最小权限
application-controller
持续比较、同步、健康评估
核心 reconcile 控制器
repo-server
拉取仓库并渲染 Helm/Kustomize 等清单
仓库访问与模板渲染问题先看它
API Server / UI / CLI
提供操作入口与可视化
登录、RBAC、审计和日常操作
一个 Application 的主线可以概括为:
Git 仓库 + 路径 + 修订版本
│
▼
渲染 Kubernetes 清单
│
▼
Desired State(期望) ←→ Live State(实际)
│
▼
Sync 状态 + Health 状态 + 必要时执行同步
2.4 Sync 与 Health:两个维度不能混看
- Sync 状态:Synced 或 OutOfSync,描述 Git 与集群声明是否一致。
- Health 状态:Healthy、Progressing、Degraded 等,描述工作负载是否健康运行。
一个应用完全可能 Synced + Degraded:YAML 已正确同步,但镜像拉取失败或探针不通过。也可能 OutOfSync + Healthy:集群被人改了副本数,但应用仍可正常服务。
排障时先问"是不一致,还是不健康",再走不同路径,不要看到红叉就只点 Sync。
2.5 自动同步的三个关键开关
配置
含义
主要风险
automated
发现 Git 变更后自动同步
未充分评审的提交会快速进入集群
selfHeal: true
集群声明偏离 Git 时自动改回
会覆盖合法的临时运维调整
prune: true
Git 删除资源后,同步时删除集群对应资源
配错来源或路径可能造成真实资源删除
生产环境建议按风险分级:开发环境可全自动,生产环境可采用自动同步但人工审批关键变更,或先关闭 prune,待流程成熟后再逐步开启。
2.6 同步顺序、Hook 与回滚边界
复杂应用通常不只是 Deployment: - CRD 往往要先于自定义资源创建;
- 数据库迁移可能要在新版本启动前执行;
- ServiceAccount、RBAC、ConfigMap、工作负载存在先后依赖;
- 某些资源同步后还需要健康检查或验证。
Argo CD 提供 Sync Wave、PreSync/Sync/PostSync Hook 等机制控制顺序,但不要把 GitOps 回滚理解成数据库时间机器: - git revert 能恢复 Kubernetes 声明;
- 它不能自动撤销不兼容的数据库 schema;
- 它不会把 PV 里的数据恢复到旧版本;
- 有状态变更仍需独立迁移、备份与恢复方案。
2.7 配置与 Secret 的交付边界
GitOps 不等于"所有内容都明文放 Git"。密码、令牌和私钥不应直接提交。常见方案包括: - External Secrets:从 Vault、云 Secret Manager 等同步;
- Sealed Secrets:将只能由目标集群解密的密文放 Git;
- SOPS + KMS:加密文件进入 Git,集群侧解密;
- CI 渲染敏感值:可用,但要严格防止日志泄露与权限扩大。
另外,Argo CD 的仓库凭据、SSO/RBAC、项目边界和审计,与"应用如何同步"同样重要。
三、实战演示:从 Git 部署电商前端并持续收敛
3.1 场景与目标
部署一个 shop-web 前端,开发环境声明保存在 Git 的 overlays/dev:
- Argo CD 从 Git 渲染并创建应用;
- 修改 Git 副本数后自动发布;
- 手工扩容造成漂移,由 Argo CD 自动修复;
- 从 Git 删除资源后验证 prune;
- 用 git revert 回滚应用声明。
前置:可用 Kubernetes 集群、Git 仓库、已配置推送权限。以下 repoURL 必须替换成你的真实仓库地址。实验可使用 kind/minikube,生产请独立规划高可用、权限和备份。
3.2 步骤一:安装 Argo CD 并登录
kubectl create namespace argocd
kubectl apply -n argocd
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl wait --for=condition=Available deployment/argocd-server
-n argocd --timeout=300s
kubectl get pods -n argocd
将界面转发到本地:
kubectl port-forward svc/argocd-server -n argocd 8080:443
另开终端获取初始管理员密码:
kubectl get secret argocd-initial-admin-secret -n argocd
-o jsonpath='{.data.password}' | base64 -d
echo
浏览器访问 https://localhost:8080,用户名为 admin。生产环境应立即接入正式身份系统、修改初始凭据,并限制端口转发与网络暴露。
3.3 步骤二:准备 Git 仓库与 Kustomize 配置
仓库结构:
shop-gitops/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
└── overlays/
└── dev/
└── kustomization.yaml
base/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-web
spec:
replicas: 1
selector:
matchLabels:
app: shop-web
template:
metadata:
labels:
app: shop-web
spec:
containers:
- name: web
image: nginx:1.27-alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /
port: 80
livenessProbe:
httpGet:
path: /
port: 80
base/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: shop-web
spec:
selector:
app: shop-web
ports:
- name: http
port: 80
targetPort: 80
base/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
overlays/dev/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: shop-dev
resources: - .../.../base
replicas: - name: shop-web
count: 1
推送前先本地渲染,避免把明显错误交给控制器:
kubectl kustomize overlays/dev
git add .
git commit -m "feat: add shop web gitops manifests"
git push origin main
3.4 步骤三:创建 Application
shop-dev-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/platform/shop-gitops.git # 替换
targetRevision: main
path: overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: shop-dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
kubectl apply -f shop-dev-application.yaml
kubectl get application shop-dev -n argocd -w
等待状态变为 Synced / Healthy 后验证:
kubectl get pods,svc -n shop-dev
kubectl port-forward -n shop-dev svc/shop-web 8081:80
这里的 automated.prune: true 是为了完整演示。生产环境请先理解其删除语义,不要无审查地复制。
3.5 步骤四:通过 Git 发布副本变更
修改 overlays/dev/kustomization.yaml:
replicas:
- name: shop-web
count: 2
提交并推送:
git add overlays/dev/kustomization.yaml
git commit -m "chore: scale dev shop web to two replicas"
git push origin main
Argo CD 默认会周期检查仓库,也可在界面点击 Refresh 加速发现。验证:
kubectl get application shop-dev -n argocd
kubectl get deployment shop-web -n shop-dev
kubectl get pods -n shop-dev
发布依据是一次 Git 提交,而不是某人的终端操作。
3.6 步骤五:制造漂移,验证 Self-Heal
绕过 Git,直接扩容到 5:
kubectl scale deployment shop-web -n shop-dev --replicas=5
kubectl get deployment shop-web -n shop-dev
由于 selfHeal: true,Argo CD 会检测到实际副本数偏离 Git,并将其恢复为 2。再次查看:
kubectl get application shop-dev -n argocd
kubectl get deployment shop-web -n shop-dev
这说明 GitOps 管的不是"一次部署",而是"持续一致"。如果确需临时扩容,应通过 Git 修改,或由明确排除在 GitOps 管理外的 HPA 控制对应字段。
3.7 步骤六:理解 Prune 的删除语义
假设 Git 中不再需要 Service,可从 base/kustomization.yaml 的 resources 删除 - service.yaml,并将仓库中该文件删除后推送:
git rm base/service.yaml
同时编辑 base/kustomization.yaml,删除 service.yaml 引用
git add base/kustomization.yaml
git commit -m "chore: remove shop service"
git push origin main
因为启用了 prune: true,Argo CD 同步后会删除集群中的对应 Service。
危险提醒:误改 source.path、仓库分支或资源跟踪标签,可能让 Argo CD 认为大量资源"已从 Git 消失"。生产开启 prune 前,必须做好项目权限、目录保护、评审和恢复预案。
3.8 步骤七:用 Git 回滚,而不是 rollout undo
若刚才副本变更引入问题,应回退对应提交:
git revert
git push origin main
Argo CD 获取新提交后,将集群同步到恢复后的声明。这样 Git 历史同时保留"错误变更"和"回退变更",便于审计。
再次强调:这恢复的是声明,不会自动回滚数据库数据或已产生的外部副作用。
3.9 进阶:生产环境的最小项目边界
不要在多团队场景下让所有 Application 共用无限权限的 default 项目。可用 AppProject 限制来源与目标:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: shop-team
namespace: argocd
spec:
description: Shop team delivery boundary
sourceRepos:
destinations:
- server: https://kubernetes.default.svc
namespace: shop-*
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: ""
kind: " "
实际生产还应进一步收敛可创建的命名空间资源,配合 Argo CD RBAC、SSO、Kubernetes RBAC 与准入策略,避免"GitOps 平台变成新的 cluster-admin 分发器"。
四、排错实战:按仓库、渲染、同步、健康四层定位
4.1 高频问题对照表
现象
优先根因
定位动作
处理方向
仓库无法拉取
URL/分支错误、凭据无效、网络或证书问题
查看 Application 条件与 repo-server 日志
修正来源与最小权限凭据
ComparisonError
Git 路径不存在、模板渲染失败
本地 kubectl kustomize / helm template
先让本地渲染通过
OutOfSync 长期不变
未开启自动同步、字段持续漂移
看 diff、同步策略和事件
手工 Sync 或修复漂移来源
Synced + Degraded
声明一致但工作负载异常
查 Pod Events、镜像、探针、日志
走常规应用排障,不是反复 Sync
SyncFailed
RBAC、准入拒绝、资源冲突、Hook 失败
看 Application 操作状态与 Events
按报错修复权限或资源
CRD 资源创建失败
CRD 与 CR 顺序不对
看同步波次和 CRD 建立状态
分 Application 或配置 Wave
副本数反复变化
HPA 与 Git 同时管理 replicas
看 HPA 与 Argo CD diff
明确字段唯一管理者
资源被意外删除
prune 或来源路径变更
看同步审计、Git 提交和 Application 来源
停止同步、恢复声明、收紧 prune
Namespace 卡在 Terminating
Finalizer/资源清理阻塞
查 namespace 与残留资源
先定位阻塞资源,不盲目强删
回滚后数据仍异常
把声明回滚误当数据回滚
查迁移与数据备份
执行独立数据恢复流程
4.2 常用诊断命令
应用整体状态
kubectl get application -n argocd
kubectl describe application shop-dev -n argocd
控制器与仓库渲染日志
kubectl logs deployment/argocd-application-controller -n argocd --tail=100
kubectl logs deployment/argocd-repo-server -n argocd --tail=100
本地复现模板渲染
kubectl kustomize overlays/dev
检查实际工作负载
kubectl get events -n shop-dev --sort-by=.lastTimestamp
kubectl describe deployment shop-web -n shop-dev
kubectl logs deployment/shop-web -n shop-dev --tail=100
4.3 一张排错决策树
text
Application 异常
│
├─ 仓库拉不到? ───────→ 查 URL、分支、凭据、网络、repo-server
│
├─ 渲染失败? ─────────→ 本地 Kustomize/Helm 渲染复现
│
├─ SyncFailed? ───────→ 查 RBAC、准入、Hook、CRD 顺序、资源冲突
│
├─ OutOfSync? ────────→ 查 diff、自动同步、selfHeal、外部控制器争用
│
└─ Synced 但 Degraded? → 回到 Pod/镜像/探针/存储/网络常规排障
五、总结:GitOps 的核心不是工具,而是变更纪律
Argo CD 把"应用目标状态"从分散的本地目录和终端命令,收敛到可评审、可审计、可持续协调的 Git 工作流中。本篇实战覆盖了安装、Kustomize 应用接入、自动发布、漂移修复、Prune 和 Git 回滚,也划清了三条重要边界:
1. GitOps 不是 CI:构建、测试、镜像扫描仍由流水线承担;
2. Git 是声明事实源,不是运行时全部事实:健康状态、监控指标和数据内容仍在集群与外部系统;
3. 声明可回滚,不代表数据可回滚:有状态变更必须配套迁移与备份策略。