Argo CD 深度拆解:GitOps 控制器的同步、漂移修复与多租户边界
posts posts 2026-07-14T03:13:50+08:00Argo CD 是 Kubernetes 上的声明式 GitOps 持续交付工具,核心由 API Server、Repository Server、Application Controller 三大组件协同。本文拆解其同步机制、漂移修复、Helm/Kustomize/plain 三类任务流,以及多租户边界。技术笔记KubernetesArgo CD 深度拆解:GitOps 控制器的同步、漂移修复与多租户边界
核心判断
很多人把 Argo CD 当成"会 watch Git 的 kubectl"。它的核心价值不是把 kubectl apply 自动化,而是把"集群的实时状态"和"Git 上声明的目标状态"做成两份独立可对比的事实(live state vs target state),并以 Kubernetes 控制器的形式持续把前者收敛到后者。
仓库地址是 github.com/argoproj/argo-cd,Apache-2.0 协议,累计 Star 数 2.37w、Forks 约 7.6k(README 与 GitHub API 实时数据)。CNCF 毕业项目,OpenSSF Scorecard 和 CII Best Practices 都打了卡;最新的 v3.5.x 发布版里,容器镜像全部用 cosign 签名,并生成满足 SLSA Level 3 的 provenance。从工程量级看,它就是安装到集群里的"GitOps 控制器 + 一组 CRD + 一个 UI + 一组 CLI"。
归纳出三条核心判断:
- 判断 1:Argo CD 是 Kubernetes 的控制器,不是 CI 流水线。它跑在集群里,以工作负载(Deployment/StatefulSet)的形式受 Kubernetes 调度与生命周期管理;它和外部世界唯一的协议是 Git(pull 模型),而不是 webhook(push 模型)。这条直接决定了它的部署模型、灾备模型和权限模型。
- 判断 2:Application 不是一组 manifest,而是一个 CRD。Application 是 Argo CD 自己定义的 Kubernetes Custom Resource,里面写的是"我想要的最终态"。Argo CD 只关心这份期望的最终态,并把它和集群里真实的对象逐个对比。manifest 只是 Application 的一部分字段。
- 判断 3:sync 是一次幂等操作,Reconcile 才是核心循环。Sync 是用户或控制器主动触发的一次"对齐"动作;Reconcile 是控制器对每个 Application 周期性跑的那段 reconcile loop(对照 live vs target、修正 sync status、清理孤儿资源)。理解这两件事的区别,drift detection、self-heal、prune 这些功能才好分清顺序。
系统地图
Argo CD 在集群内部署成 3 个核心组件 + 1 组 CRD + N 个集群凭证。
三组件:API Server / Repository Server / Application Controller
| 组件 | 角色 | 关键职责 | 通信协议 |
|---|---|---|---|
API Server (argocd-server) | 控制平面入口 | 暴露 gRPC/REST API;终端用户、CI 系统、UI 都通过它操作 Application;认证鉴权;Git Webhook 事件的转发器 | gRPC / REST,对外暴露 |
Repository Server (argocd-repo-server) | manifest 渲染器 | 拉取 Git 仓库、缓存、调用 Helm/Kustomize/Jsonnet/Plain 等工具把 Application 引用的 source 渲染成最终 Kubernetes 对象 | 内部 gRPC,对 controller 提供 |
Application Controller (argocd-application-controller) | reconcile 引擎 | 周期性地把"期望态"(来自 repo-server)和"实时态"(来自 kube-apiserver)做 diff,标 Sync/Health 状态,并按策略触发 sync | kube-informer + 内部 gRPC |
三组件都跑在 Argo CD 自己的 namespace 里(默认 argocd),由 Deployment/ReplicaSet 拉起,各自有 ConfigMap 和 Secret。Application Controller 是 reconcile 的核心,但它自己并不直接 git clone,所有 manifest 渲染都委托给 Repository Server,自己保留"对比 → 标记 → 触发 sync"这条主线。
三类对象:Application / AppProject / ApplicationSet
Argo CD 在集群注册一组 CRD,核心是 3 个:
- Application:最小的"想交付到哪个集群"的单位。spec 里写明 source(Git repo URL + revision + path)、destination(目标集群名 + 命名空间)、sync policy、ignore differences 规则等。Argo CD 周期性地比对 spec 与目标集群状态,把结果分两轨写进 status:sync 状态(Synced / OutOfSync)和健康状态(Healthy / Degraded / Suspended)。
- AppProject:项目级别的"业务隔离面"。一个 AppProject 内有 source 仓库白名单、destination 集群白名单 + 命名空间白名单、cluster resource 白名单、可签发的 SyncWindow、可调的 RBAC policy 列表。Application 必须挂在 AppProject 下,越界就拒收。
- ApplicationSet:Application 的 generator(生成器)。用来从 Git 目录、Cluster list、PR/MR、ScmProvider 等输入"扇出"出大量 Application。同一份 helm chart 在 12 个环境部署,靠 ApplicationSet + 模板而不是写 12 个 YAML。
ApplicationSet 不直接部署东西,它是把 Git/Cluster/Scm 数据源拆成 N 份模板参数,每份产出一个 Application CR,让 Argo CD 控制器接手。Argo CD 控制器再走标准同步流程。
多集群:单 controller 联邦,凭证用 Secret
Argo CD 部署在"中心集群",管理一组"外部集群"(包括自身 in-cluster)。每个外部集群只是一个 Secret,存 kubeconfig 或 bearer token + API server URL。Application 的 destination 字段引用这些集群名称。
所有 reconcile 在中心集群发生;外部集群只暴露标准 kube-apiserver。中心集群宕机时 drift detection 跟着停——这是 controller model 的固有弱点,运维时必须考虑。
异步流水线:API Server → Controller → Repo Server → kube-apiserver
一次 reconcile 的关键链路:
- API Server 接收用户的 Application 创建/更新、或者收到 Git webhook 事件;
- Application Controller 看见 Application CR 变更后,把它丢进工作队列;
- Controller 用 Application 的 source 字段请求 Repository Server,请求给出最终的 K8s object 列表(即 manifest);
- Controller 用 manifest 里的 namespace + name 列表,从目的地 kube-apiserver 拉真实对象,做 diff;
- Controller 根据 diff + sync policy,决定是否触发 sync,把状态写回 Application.status;
- Sync 时 Controller 通过 Kubernetes API 写目标集群(不是 kubectl apply,而是走 client-go 的 patch 逻辑)。
每一步都用了 Kubernetes 的 watch/list 而不是轮询(Controller 端走 informer,repo-server 端走 git ls-remote + 内部缓存),所以 Argo CD 不会出现"高频 cron 拉 Git"的成本。
边界拆分:三种 Git 引用、三种渲染工具、三种隔离面
边界 1:Git 引用 vs 集群路径
| 概念 | 字段 | 含义 |
|---|---|---|
| Source | spec.source.repoURL + spec.source.targetRevision + spec.source.path | 在哪个 Git 仓库的哪个 commit/branch 的哪个目录 |
| Destination | spec.destination.server + spec.destination.namespace | 渲染后的对象要送到哪个集群的哪个 namespace |
| Sync status | status.conditions[] | sync / 健康 / suspended 状态码,不参与 manifest 决策 |
source 是"读哪里",destination 是"写哪里"。仓库只是 source 之一,OCI(Helm OCI)和 Plugin 也算 source;destination 也支持本地集群、外接集群等多种形态。
边界 2:三种 manifest 渲染工具
| 工具 | 何时启用 | 如何被 Argo CD 调起 |
|---|---|---|
| Plain (Directory) | repo 根目录直接就是 K8s YAML | repo-server 直接遍历,校验 K8s schema |
| Helm | source.path 指向含 Chart.yaml 的目录 | 通过 Helm v3 CLI / Helm libs 渲染,可填 helm.values |
| Kustomize | source.path 指向含 kustomization.yaml 的目录 | 通过 kubectl 内嵌或独立 kustomize binary 调用 |
| Jsonnet | source.path 是 .jsonnet 文件 | 通过 jsonnet 命令行渲染 |
| Plugin | sidecar / config management plugin | repo-server 启动时通过 ConfigMap 声明 <name>.yaml |
Argo CD 的 Application 不强制渲染工具,渲染由 repo-server 根据目录内容自动嗅探(详见 user-guide 里的 directory tool detection)。这种"按 source.path 自动发现"的策略让一个 Git 仓库可以混用多种工具,但对 retention 很复杂的 monorepo 来说要小心嵌套副作用。
边界 3:三种隔离面
| 隔离面 | 对象 | 控制字段 |
|---|---|---|
| Kubernetes 集群隔离 | destination | spec.destination.server 选哪个集群的 Secret |
| 命名空间隔离 | destination + AppProject | spec.destination.namespace + AppProject 的 clusterResourceWhitelist / namespaceResourceWhitelist |
| 逻辑项目隔离 | AppProject + RBAC | AppProject 内嵌 roles / policies;用户绑定到一个 role 后只能在自己 AppProject 内的 Application 上 sync/render |
Argo CD 的多租户能力来自四层隔离:datasource 隔离靠 Git 仓库 + repo URL;集群隔离靠 destination;命名空间隔离靠 AppProject 白名单;用户隔离靠 AppProject 内 RBAC + Policy。
关键机制:同步、漂移修复、孤儿资源清理
把 sync、Reconcile、drift detection、prune 四件事拆开看。
sync:一次幂等操作
sync 是单次操作:把"target state"应用一次到目标集群。核心代码在 Application Controller 的 appcontroller 包里,它的底层并不是"直接 apply 整个 yaml",而是先算出 desired object list,再逐对象写入 kube-apiserver。写入走哪条语义,取决于有没有开 Server-Side Apply:
- 默认(未开 SSA):走 kubectl 式 3-way merge——也就是给每个对象维护
kubectl.kubernetes.io/last-applied-configuration注解,算出 diff 后做 strategic merge patch(CRD 这类无 scheme 的类型退化为 JSON merge patch)。这和kubectl apply是同一套语义,能正确删除"从上次 apply 里消失的字段"。 - 开启 SSA(
syncOptions: [ServerSideApply=true]):改用 Kubernetes 原生的 Server-Side Apply,由 API server 管理字段所有权(field manager)和冲突检测,客户端不再需要 last-applied 注解。
这条区别值得记住:默认模式下 Argo CD 依赖客户端维护 last-applied,跨工具变更(比如同时被别的 CD 或 kubectl 碰过)容易踩"last-applied 不完整"的坑;SSA 把合并逻辑搬进了 API server,冲突时能给出明确报错。
sync 的几个关键开关,都写在 Application CR 里:
syncPolicy.automated开了之后,drift 被发现就会自动 sync。这是 self-heal 的来源。syncPolicy.automated.prune决定要不要清理"目标态里没有、集群里却有"的对象(即孤儿资源)。syncPolicy.automated.allowEmpty决定清空目录是否合法。syncOptions[].PrunePropagationPolicy决定依赖对象的删除顺序(foreground / background / orphan)。
sync 是幂等的——同一份源推到集群两次,最终结果一样;这意味着可以从 Argo CD 之外的工具(kubectl / Helm / terraform)做变更,Argo CD 检测到 drift 再 sync 一次就能拉回来。
Reconcile 与 drift detection
Application Controller 在每个 application 上跑一个 reconcile loop:
- 用 source hash 查 cache 命中,否则触发 repo-server 重新渲染;
- 拿渲染结果对象,和目标集群的对应对象做 server-side diff;
- 把 diff 写到 status 里;如果
automated开启且selfHeal为真,则触发 sync。
selfHeal 是 drift 修复的核心开关。打开它之后,任何外部方式(人手 kubectl / 别套 CD 工具 / 节点漂移)造成的偏差,都会在下一个 reconcile 周期被 Argo CD 追回。关掉它的话,Argo CD 只标记 OutOfSync 等用户主动 sync。
controller 默认按 --app-resync(120 秒加最多 60 秒抖动)周期性兜底轮询,即使没有 webhook 也不会永久停在旧状态;--self-heal-timeout-seconds(默认 5 秒)单独控制自愈检查间隔。配合 Git Webhook,commit 后可以立即触发 reconcile,不必等完整一轮轮询。
Prune:孤儿资源
Prune 是 sync 阶段同步处理"集群里多余的对象"。三种典型场景:
- 应用换 chart:旧 chart 里有个 ConfigMap,新 chart 里删了;开 prune 之后这条 ConfigMap 会被自动清掉。
- 团队手工改了 cluster 里某个 deployment(手 kubectl edit);下次 reconcile 时 OutOfSync + sync 会把它追回 Git(selfHeal)。
- 跨 application 共享对象:例如两个 Application 都创建 ConfigMap
foo,开 prune + 多 Application 容易互相踩,建议把共享对象放到独立 Application 或者关 prune。
--auto-prune 等开关在原 kubernetes 工具里没有,Argo CD 把这一层语义补上。代价是开 prune 容易误删——比如 CronJob successfulJobsHistoryLimit 管理的对象,如果另一个工具也在碰,prune 会删掉它。
Sync Window:变更节奏护栏
SyncWindow 不是控制同步频率,而是限制哪些时间窗口内允许 sync。常见用法:只在 22:00 到 06:00 允许生产环境自动 sync,其余时间 drift 留在 OutOfSync 状态等人工看。
一次真实任务穿过系统
应用场景
一家 SaaS 团队有两个 Application:
- Application
web:repo 是git@github.com/acme/web.git,path =deploy/prod,工具 = Helm,destination 是 production 集群、webnamespace; - AppProject
web-team:只允许web这个 Application 引用github.com/acme/*仓库,只允许部署到 production 集群的web和staging-webnamespace。
步骤 1:开发者 push PR → main 合并
开发者改了 deploy/prod/values.yaml,PR 合入 main。GitHub 通过 webhook 通知 Argo CD API Server(默认监听 /api/webhook),Argo CD 把这次 commit 信息写入 Application 的 spec.source.targetRevision 候选,并在 Application Controller 里记一个 hint。
步骤 2:Application Controller 触发 Reconcile
Controller 看到 hint 后立刻 reconcile(不等 3 分钟 reconcile 周期)。它拿着 Application 的 source hash 去问 Repository Server:“请帮我渲染 git@github.com/acme/web.git@main 在 deploy/prod 路径下的最终对象列表。”
步骤 3:Repository Server 渲染 manifest
repo-server 内部:
- 用 Application 的 Git 凭证(Secret 里的 ssh key / https token)先解析目标 revision:
git ls-remote把 branch/tag 解析成具体 commit; - 拉取并检出该 commit 的文件到缓存目录,交工具检测:
deploy/prod/Chart.yaml存在 → 调 Helm v3,把 Helm Values + 模板渲染成 K8s object list; - 渲染结果按 commit hash 缓存,下次相同 commit 命中直接复用。
如果 path 下同时存在 kustomization.yaml,用户可在 spec.source.kustomize 里强制走 kustomize;要混用工具,就在 Chart.yaml 内部再嵌入 Kustomize 钩子。
步骤 4:Application Controller 做 diff
拿到 desired object list(可能是 6 个 Deployment + 6 个 Service + 1 个 ConfigMap + 1 个 Ingress)后,Controller 走 informer 向 production 集群的 kube-apiserver 拉对应的 live object。然后按 name 做 server-side diff,计算出 OutOfSync 集合:比如只有 Deployment web-7c8f9b 的 image 从 v1.4.2 变成 v1.5.0,其余对象对齐。
步骤 5:sync 写入集群
automated=true + selfHeal=true 时,Controller 直接发起 sync:
- 按上文"默认 3-way merge / 开启后 SSA"的方式把新 Deployment patch 进 production 集群;selector 标签变化时由 Kubernetes 创建新 ReplicaSet、逐 pod 滚动;
- Spec 里有 sync hook(pre-sync/sync/post-sync)的对象按顺序执行(例如 Job);
- Prune 阶段跳过,因为没变更清单删除项。
status.sync.status 在 controller 写完 Deployments 之后被改成 Synced,status.health.status 在 Pod ready 之后被改成 Healthy。健康状态靠资源跟踪判断:Controller 按 Application 的跟踪规则找出所有派生对象,再据此推断是否 ready,而不是逐个读 Pod 细节。
这里有一层默认隐藏的机制:Argo CD 凭什么知道"这个 Deployment 属于哪个 Application"?靠的是给托管对象打的跟踪标记。默认的 trackingMethod 是 annotation.label——既打 app.kubernetes.io/instance: <appName> 标签、又写 argocd.argoproj.io/tracking-id 注解;另外还有纯 annotation、annotation+managedfields 等可选方案。它决定了三件事:健康推断时收集哪些对象、diff 时怎么对齐、prune 时哪些算"本应用该管的"。所以让两个 Application 控制同一批对象(共用 instance 标签)会出现健康状态互相干扰——这也是前面"跨 Application 共享对象容易互相踩"的底层原因。
步骤 6:失败回滚
如果 sync 后 controller 探测到 health Degraded 且未自愈,sync 后追加的 wave/phase 顺序不会自动回退。Argo CD 的"回滚"实际上是 sync 到上一个 Known Good Revision,这在 controller 的 --revision-history-limit 上限之内都能做。CLI 的 argocd app rollback 就是一个特殊形态的 sync。
多租户边界:AppProject + RBAC + 命名空间白名单
1. AppProject 的源仓库白名单
AppProject.spec.sourceRepos 只能写 git URL 字面匹配或前缀匹配(如 https://github.com/acme/*),Application 的 spec.source.repoURL 不在白名单里就会被 controller 拒收,写进 status 但不部署。这避免某个工程师随手配置仓库从任意地址拉代码。
2. AppProject 的集群 + 命名空间白名单
spec.destinations 可以列"哪些集群 + 哪些命名空间"。这是把"开发环境"和"生产环境"从同一 Argo CD 中分离的关键开关。每个 AppProject 也可单独禁用 cluster-scoped 资源(ClusterRole、CustomResourceDefinition 等),进一步缩小爆破半径。
3. AppProject 内嵌的 RBAC Policy
每个 AppProject 可以定义 policies,policy 是 RBAC 风格的 <action, resource, object> 元组,例如:
policies:
- p, proj:web-team:dev, applications, get, web/*, allow
- p, proj:web-team:dev, applications, sync, web/*, denypolicy 在 argocd-server 鉴权时被读取。它可以做到"张三只能在 staging 环境的 web 应用上 sync,不能看别的应用"。这条对中型平台团队很关键——直接省掉自建审批系统。
4. 集群级角色 Config
除了 AppProject 内嵌 policy,argocd-server 还支持全局角色(role/cluster role)。本地的 argocd-rbac-cm ConfigMap 写角色到用户的映射,外接 OIDC / SAML / LDAP / SSO 时由 dex 接驳(argocd-dex-server 组件)。这两层都按官方 README 和操作手册配。
这层边界不卡什么
AppProject 不是 namespace。多个 Application 即使分属不同 AppProject,依然跑在同一个 controller 进程里、共享同一个 repo-server 缓存、共享同一个 informer quota。一个 AppProject 内的 Application 配置错误(比如 tight loop 装 Helm chart 每秒 reconcile)会拉低整组性能。
另外,AppProject 不能阻止 Application 在 dest cluster 上 create 任意 cluster-scoped 资源,除非你显式把 clusterResourceWhitelist 关掉。一旦开了 cluster-scoped 准入,Argo CD 在多租户场景下需要慎重评估。
采用建议
推荐采用顺序
- 从 standalone 单集群开始:先在 dev/staging 集群起一个 Argo CD,托管 1-2 个不含状态的 microservice。
- 引入 Application 而不是 Bash:把 helm install 改成 Application CR,第一周保留手动 sync。
- 加 AppProject 隔离:每个业务团队一个 AppProject,先开 source repo 白名单,再开 cluster/namespace 白名单。
- 再加 selfHeal:开 automated + selfHeal,但关 prune。先让 drift detection 稳定一周。
- 开 prune:先在 staging 验证无误删,再带到生产。
- 接 ApplicationSet:模板相同的多环境从一份 ApplicationSet 扇出。
- 接 SyncWindow 与 Resource Hook:生产 sync 限制到夜间;用 pre-sync Job 做 migration。
- 观测与告警:开 Notification(Slack / Alertmanager),drift 或 sync 失败实时通知。
不适合 Argo CD 的场景
- 纯静态文件发布到 S3/OSS:没有 K8s 集群就上不了 Argo CD。
- 超大规模(>10k Application 单 controller):单个 controller 的 informer 有上限,要走 HA + sharding。
- 不希望任何远端拉代码:Argo CD 默认 pull 模型,必须能从集群内拉 Git。
- 一次性 Job 流水线:Argo Workflows 覆盖这个场景,Argo CD 适合持续运行的 deployable object。
常见翻车现场
翻车 1:sync 永远 OutOfSync,diff 不收敛
- 症状:status 显示 OutOfSync,但点开 diff 看不出差别。
- 原因:对象有 status 字段(CRD 常见)、时间戳、annotation 自带 hash 等"非 spec 差异"被算入 diff。
- 修法:写
ignoreDifferences规则指明哪些 json path 差异忽略,注意更新 reference doc 中说明的 schema。
翻车 2:开 prune 后日志大量 Object was not deleted
- 症状:prune 操作出现
Error from server (Forbidden): User cannot delete resource,但手动删没问题。 - 原因:Argo CD 的 service account 在目的地集群缺 delete 权限。
- 修法:检查目的地集群的 ClusterRole,确保 SA 包含
delete/list/patch等;或在 Application 上加syncOptions跳过 prune。
翻车 3:Helm chart 渲染后 secret 字段值丢
- 症状:Helm template 里有
{{ .Values.db.password }},sync 后 secret 字段是空。 - 原因:Argo CD 的 Helm 调用对外层 secret 注入靠
spec.source.helm.valueFiles+ 显式spec.source.helm.parameters;外面 secret 走 External Secrets Operator 注入,不能用--set方式传。 - 修法:把 secret 拆出去管理,或者用 Helm 的
--set-string在 Argo CD 端注入前先 dry-run 验证。
翻车 4:ApplicationSet 重渲染风暴
- 症状:template 改动后,几百个派生 Application 全部同时进入 OutOfSync → Reconcile 风暴,controller CPU 飙满。
- 原因:reconcile 是控制器频率触发,没有内建节流。
- 修法:ApplicationSet 用 progressive sync 或
applicationsync内的 spread 配置,或者通过 promotion generator 控制节奏。
翻车 5:selfHeal 反过来追回人工 debug 改动
- 症状:debug 时改了 cluster 的 Deployment 跑通测试,第二天被 Argo CD 自动 sync 回去。
- 原因:selfHeal 开启后等于"Git 永远赢"。
- 修法:debug 时把应用 syncPolicy 改成 manual;或者用
ignoreDifferences临时挂一条;或者改用一个独立 namespace 不进 AppProject。
常见问题
Argo CD 自己宕机会怎么样?
sync 流水线停。已部署的 workload 不受影响。从单集群多 controller HA(Dynamic Cluster Distribution,v2.4+)到跨集群灾备都要提前规划。
Argo CD 跟 Flux 怎么选?
Argo CD 是 pull + 多组件,UI 友好,多团队多租户优势明显;Flux 是 controller + 单进程,Kustomize 生态深。两者都毕业自 CNCF,按团队对 UI、CRD-first 还是 controller-first 的偏好选择。
跟 Argo Rollouts / Argo Workflows 是什么关系?
同属 argoproj 家族。Argo CD 管 sync 与 reconcile,Argo Rollouts 做渐进式交付(canary / blue-green),Argo Workflows 做 DAG / 批处理。Argo CD Application 的 sync hook 可以调 Rollouts / Workflows。
Argo CD 与 Helm 的边界在哪里?
Helm 是 templating + package 工具;Argo CD 是 controller + 多 source 渲染器 + Application CRD。Argo CD 可以渲染 Helm chart,但所有合规/GitOps 行为由 Argo CD 这一侧负责。
参与讨论
使用 GitHub 登录。欢迎补充事实、异议与实践。
讨论暂时无法加载。