跳到正文

目录

Argo CD 深度拆解:GitOps 控制器的同步、漂移修复与多租户边界

Argo 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 状态,并按策略触发 synckube-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 的关键链路:

  1. API Server 接收用户的 Application 创建/更新、或者收到 Git webhook 事件;
  2. Application Controller 看见 Application CR 变更后,把它丢进工作队列;
  3. Controller 用 Application 的 source 字段请求 Repository Server,请求给出最终的 K8s object 列表(即 manifest);
  4. Controller 用 manifest 里的 namespace + name 列表,从目的地 kube-apiserver 拉真实对象,做 diff;
  5. Controller 根据 diff + sync policy,决定是否触发 sync,把状态写回 Application.status;
  6. 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 集群路径

概念字段含义
Sourcespec.source.repoURL + spec.source.targetRevision + spec.source.path在哪个 Git 仓库的哪个 commit/branch 的哪个目录
Destinationspec.destination.server + spec.destination.namespace渲染后的对象要送到哪个集群的哪个 namespace
Sync statusstatus.conditions[]sync / 健康 / suspended 状态码,不参与 manifest 决策

source 是"读哪里",destination 是"写哪里"。仓库只是 source 之一,OCI(Helm OCI)和 Plugin 也算 source;destination 也支持本地集群、外接集群等多种形态。

边界 2:三种 manifest 渲染工具

工具何时启用如何被 Argo CD 调起
Plain (Directory)repo 根目录直接就是 K8s YAMLrepo-server 直接遍历,校验 K8s schema
Helmsource.path 指向含 Chart.yaml 的目录通过 Helm v3 CLI / Helm libs 渲染,可填 helm.values
Kustomizesource.path 指向含 kustomization.yaml 的目录通过 kubectl 内嵌或独立 kustomize binary 调用
Jsonnetsource.path 是 .jsonnet 文件通过 jsonnet 命令行渲染
Pluginsidecar / config management pluginrepo-server 启动时通过 ConfigMap 声明 <name>.yaml

Argo CD 的 Application 不强制渲染工具,渲染由 repo-server 根据目录内容自动嗅探(详见 user-guide 里的 directory tool detection)。这种"按 source.path 自动发现"的策略让一个 Git 仓库可以混用多种工具,但对 retention 很复杂的 monorepo 来说要小心嵌套副作用。

边界 3:三种隔离面

隔离面对象控制字段
Kubernetes 集群隔离destinationspec.destination.server 选哪个集群的 Secret
命名空间隔离destination + AppProjectspec.destination.namespace + AppProject 的 clusterResourceWhitelist / namespaceResourceWhitelist
逻辑项目隔离AppProject + RBACAppProject 内嵌 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:

  1. 用 source hash 查 cache 命中,否则触发 repo-server 重新渲染;
  2. 拿渲染结果对象,和目标集群的对应对象做 server-side diff;
  3. 把 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 集群、web namespace;
  • AppProject web-team:只允许 web 这个 Application 引用 github.com/acme/* 仓库,只允许部署到 production 集群的 webstaging-web namespace。

步骤 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@maindeploy/prod 路径下的最终对象列表。”

步骤 3:Repository Server 渲染 manifest

repo-server 内部:

  1. 用 Application 的 Git 凭证(Secret 里的 ssh key / https token)先解析目标 revision:git ls-remote 把 branch/tag 解析成具体 commit;
  2. 拉取并检出该 commit 的文件到缓存目录,交工具检测:deploy/prod/Chart.yaml 存在 → 调 Helm v3,把 Helm Values + 模板渲染成 K8s object list;
  3. 渲染结果按 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 之后被改成 Syncedstatus.health.status 在 Pod ready 之后被改成 Healthy。健康状态靠资源跟踪判断:Controller 按 Application 的跟踪规则找出所有派生对象,再据此推断是否 ready,而不是逐个读 Pod 细节。

这里有一层默认隐藏的机制:Argo CD 凭什么知道"这个 Deployment 属于哪个 Application"?靠的是给托管对象打的跟踪标记。默认的 trackingMethodannotation.label——既打 app.kubernetes.io/instance: <appName> 标签、又写 argocd.argoproj.io/tracking-id 注解;另外还有纯 annotationannotation+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/*, deny

policy 在 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 在多租户场景下需要慎重评估。

采用建议

推荐采用顺序

  1. 从 standalone 单集群开始:先在 dev/staging 集群起一个 Argo CD,托管 1-2 个不含状态的 microservice。
  2. 引入 Application 而不是 Bash:把 helm install 改成 Application CR,第一周保留手动 sync。
  3. 加 AppProject 隔离:每个业务团队一个 AppProject,先开 source repo 白名单,再开 cluster/namespace 白名单。
  4. 再加 selfHeal:开 automated + selfHeal,但关 prune。先让 drift detection 稳定一周。
  5. 开 prune:先在 staging 验证无误删,再带到生产。
  6. 接 ApplicationSet:模板相同的多环境从一份 ApplicationSet 扇出。
  7. 接 SyncWindow 与 Resource Hook:生产 sync 限制到夜间;用 pre-sync Job 做 migration。
  8. 观测与告警:开 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 登录。欢迎补充事实、异议与实践。