Argo CD ApplicationSet 多集群交付:生成器、扇出与删除安全
平台团队给一个新集群补上 environment=prod 标签,本来只想让它进入资产报表,几秒后却多出几十个 Application;另一次清理旧集群标签时,ApplicationSet 生成集合突然减项,Application 随 ownerReference 被删除,下游工作负载又因 finalizer 进入级联清理。事故的起点不是某个 Deployment YAML,而是“集群清单变化”被 generator 解释成一次批量发布事件。
ApplicationSet 不是第二个同步引擎,它是一座 Application 工厂:generator 产生参数,template 渲染 Application,Application controller 才继续拉取仓库、生成清单并同步目标集群。动手前需要一个装有 Argo CD 的隔离控制面、两个可重建的目标集群或等价 namespace 沙箱、一个只读示例仓库,以及能设计目标 RBAC 但不会把长期管理员凭证留给控制器的身份。CLI 与 server 使用同一完整版本,动态版本、generator 清单和 Beta/Alpha 成熟度应从 Releases、ApplicationSet 文档和对应 upgrade notes 就近确认。
从一座 Application 工厂理解扩散链
ApplicationSet controller 的输入是 generator,输出是一个或多个 Application。List 适合少量显式参数;Cluster 从 Argo CD 的 cluster Secret 读取集群及标签;Git 从目录或文件生成参数;Matrix 做笛卡尔组合;Merge 按键覆盖参数;SCM Provider 与 Pull Request 从代码托管平台发现仓库或 PR;Cluster Decision Resource 读取外部放置决策;Plugin 接受自定义服务返回。所有 generator 都可以再用 post selector 过滤。
每增加一层自动发现,输入面和故障半径都会扩大。List 的变化经过一次 Git 评审,最容易审计;Cluster 把集群注册和标签管理也变成发布入口;Git 目录发现会把重命名或误删解释成 Application 减项;SCM/PR/Plugin 还叠加外部 API、凭证、轮询、限流和不可信字段。generator 选择应由“参数从哪里产生、谁有权修改、一次错误会扇出多少对象、能否预览旧新集合”决定,而不是追求最自动。
运行时链路是:generator 读取输入并产生参数集合,Go template 把每组参数写入 metadata、source、destination 和 project,controller 比较现有 Application 集合并创建、更新或删除,随后每个 Application 独立调谐。故障可能停在生成阶段、Application 更新阶段、仓库渲染阶段或目标集群同步阶段;看到 100 个 Application Degraded 时,先判断是一个模板错误扩散了 100 次,还是 100 个目标集群分别失败。
用 List generator 跑通最小正向实验
ApplicationSet controller 随完整 Argo CD 安装清单交付,不需要再安装一套独立同步引擎。先确认 CRD 已建立、controller 可用且 CLI/server 同版;如果使用 namespace-install 形态,CRD 要按该安装形态单独应用。缺少 applicationsets.argoproj.io 时继续提交 YAML 只会得到资源类型不存在,controller 不 Available 时则不会产生任何 Application。
argocd version
kubectl get crd applicationsets.argoproj.io
kubectl -n argocd rollout status deployment/argocd-applicationset-controller --timeout=180s
kubectl -n argocd get pods -l app.kubernetes.io/name=argocd-applicationset-controller再从 List 开始,因为输入集合完全可见,适合验证模板、项目授权和命名规则。示例把两个环境投向同一个测试集群的不同 namespace;真实多集群接入留到凭证和 RBAC 准备完成后。启用 Go template 的严格缺失键处理,能让字段缺失立即失败,而不是悄悄生成空 project 或错误 destination。
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: demo-fleet
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:
- name: lab-a
namespace: demo-a
server: https://kubernetes.default.svc
- name: lab-b
namespace: demo-b
server: https://kubernetes.default.svc
template:
metadata:
name: 'demo-{{.name}}'
spec:
project: demo-fleet
source:
repoURL: https://git.example.com/your-org/platform-config.git
targetRevision: <commit-sha>
path: apps/demo
destination:
server: '{{.server}}'
namespace: '{{.namespace}}'
syncPolicy:
syncOptions:
- CreateNamespace=true
- FailOnSharedResource=true应用之前先使用 dry-run 预览,再保存现有与候选 Application 名称。预期只能生成 demo-lab-a 和 demo-lab-b,project、revision、server 和 namespace 都应与审批值一致。严格模式下把第二个元素的 namespace 删除,dry-run 应明确失败;若模板仍生成空字符串,说明严格设置或字段引用没有生效,不能进入控制器。
argocd appset create --dry-run applicationset.yaml -o yaml > candidate-applications.yaml
kubectl -n argocd get applications -l argocd.argoproj.io/application-set-name=demo-fleet \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | sort > current.txt
grep '^ name:' candidate-applications.yaml | sort > candidate.txt
diff -u current.txt candidate.txt || true
kubectl apply -f applicationset.yaml
kubectl -n argocd get applications正向实验的停止条件是生成数量等于 2、名称无碰撞、每个 Application 的 ownerReference 指向目标 ApplicationSet、两个目标都被 AppProject 允许,且同步后的真实请求分别成功。只看到 Application 创建出来不够;若其中一个目标失败,要记录它是模板字段、项目策略、仓库渲染还是目标 RBAC 的错误。
把 cluster Secret 当成高敏发布清单
argocd cluster add <context> 会连接目标集群并创建 Argo CD 所需访问对象,执行者通常需要较高权限。它适合隔离实验,不应成为生产默认的“快捷注册”:先设计目标 ServiceAccount、namespace allowlist、cluster-scoped resource 权限、凭证轮换和撤销,再注册。声明式注册则在 Argo CD namespace 创建带 argocd.argoproj.io/secret-type: cluster 标签的 Secret,字段包含 name、server、可选 namespaces、clusterResources、project 和 JSON config。
cluster Secret 中的 bearer token、client certificate、CA、代理或 exec provider 配置都是跨集群控制凭据。Secret 不进入普通 Git 仓库,不出现在 appset dry-run、支持日志和截图;更适合由 External Secret 或平台身份系统生成。使用云端短期凭据时,还要验证控制面 identity、目标信任、时钟、刷新失败和撤销。execProviderConfig 指定的二进制必须存在于 Argo CD 镜像,这会把自定义镜像和插件供应链也带入责任链。
权限要由三层同时收紧:cluster Secret 的 project 把注册入口限定给某个 AppProject;AppProject destinations 和 resource allow/deny 限制 Application 可声明的资源范围;目标集群 Kubernetes RBAC 决定凭据实际能够写入的资源集合。Secret 宣称只管理 namespace,而目标 RBAC 仍有 cluster-admin,是潜在越权;AppProject 允许写入,但目标 RBAC 拒绝,则会在 sync 阶段出现 forbidden。排障要逐层核对,不能把扩大 RBAC 当成通用修复。
设置 namespaces 后,Argo CD 会对每个 namespace 分别 list/watch;namespace 数量增长会放大 apiserver 连接和空闲连接压力。clusterResources 只有在 namespaces 已受限时才决定是否管理集群级资源。用几百个 namespace 换取“细粒度权限”可能造成连接与缓存成本,平台应按目标集群规模压测并比较独立实例、项目分区或更窄凭据。
用 Cluster generator 扇出但先固定选择器
Cluster generator 会输出 name、nameNormalized、server、project 和 Secret 的 labels/annotations。应使用专门的发布标签,例如 gitops.example.com/managed=true 与 environment=staging,不要复用资产、成本或临时排障标签。label 的 owner、允许值和变更评审必须明确,因为增删标签会直接改变 Application 生成集合。
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- clusters:
selector:
matchLabels:
gitops.example.com/managed: "true"
environment: staging
values:
revision: <commit-sha>
template:
metadata:
name: 'demo-{{.nameNormalized}}'
spec:
project: demo-fleet
source:
repoURL: https://git.example.com/your-org/platform-config.git
targetRevision: '{{.values.revision}}'
path: apps/demo
destination:
server: '{{.server}}'
namespace: demo名称必须使用归一化字段,并对碰撞做测试。两个原始集群名可能归一化成同一 DNS 名;目录名、branch 和 PR 字段也可能含非法字符。不要让外部输入直接模板化 project、repository URL 或认证目标。project 最稳妥的做法是硬编码受限值,否则能够修改 generator source 的人可能把 Application 生成到更高权限项目。
Cluster generator 的反例是把“删除 cluster Secret”当成仅仅断开连接。Secret 消失会使生成集合减项,默认策略可能删除 Application,Application finalizer 又可能删除工作负载;与此同时,目标 ServiceAccount 或云 IAM 可能仍然有效。退出集群前应先让 selector 排除动作经过集合 diff 和删除预算,再决定保留还是清理工作负载,最后分别撤销 Argo CD Secret、目标 RBAC、云身份、网络入口和监控。
给 generator 变更加集合级门禁
单个 Application diff 无法证明批量生成安全。每次 generator 或输入源变更都要比较旧集合与新集合,至少统计新增、更新、删除、名称碰撞、project 变化、destination 变化和 source URL 变化。阈值不是固定万能数字,而应由现有 Application 基线、变更单声明和环境风险决定;例如计划只新增一个集群时,候选集合新增十个就必须阻断。
可把预览结果转成稳定排序的结构化清单交给 CI:每行记录 Application name、project、repoURL、revision、server、namespace。CI 拒绝未声明 project、非 allowlist 仓库、移动 revision、重复目标,以及超出审批预算的新增或删除。SCM/PR generator 还要模拟 API 限流、分页不完整和空响应;Plugin generator 要对超时、重复键、恶意名称和部分返回做契约测试。
generator 返回空集时必须停止自动删除。空集可能代表“确实没有目标”,也可能来自 Git 文件解析失败、SCM token 过期、分页 bug 或网络超时。保护动作必须发生在危险输入进入运行控制器之前:CI 先生成候选集合并阻断异常减项,生产 controller 预先使用 create-update,或者在明确启用 per-AppSet override 后让该 ApplicationSet 使用 applicationsSync: create-update。等 sync 策略已经消费空集再临时改参数,Application 可能早已进入删除链,不能算保护。
生成集合的回滚也走输入源:恢复上一份已知安全的 generator revision 或 cluster label 集合,重新 dry-run,确认 Application 名称、project 和 destination 回到基线后再应用。preserveResourcesOnDeletion 必须在 Application 生成前决定,它不能追溯恢复已经被 finalizer 删除的下游对象。若错误减项后工作负载仍被保留,应先记录现有 tracking annotation、managedFields 与 owner,再验证重新生成的 Application 能否接管;发现双 owner 或字段争写时停止自动同步,不能靠反复创建掩盖冲突。
危险反例是只看 dry-run 成功。dry-run 能证明当前文件可生成对象,却不能证明控制器全局 --policy、per-AppSet override、ownerReference、finalizer 和目标集群状态符合预期。发布门禁必须同时读取控制器实际参数与运行对象;否则 YAML 中的 applicationsSync 可能根本没有获得覆盖全局策略的权限。
拆开 ApplicationSet、Application 与资源删除链
applicationsSync 有 create-only、create-update、create-delete 和 sync。create-only 只创建,适合首次观察生成集合;create-update 允许模板更新但不因集合减项删除 Application,常作为高风险扇出的稳健默认;create-delete 允许创建和删除但不更新;sync 允许完整调谐。controller 全局 --policy 优先,per-AppSet override 默认可能关闭,因此策略必须从控制器运行参数验证。
spec:
syncPolicy:
applicationsSync: create-update
preserveResourcesOnDeletion: truecreate-only 或 create-update 只限制 reconcile 比较产生的删除,不天然阻止删除 ApplicationSet 后 Kubernetes ownerReference 级联删除 Application。若要在删除 ApplicationSet 时保留 Application,官方组合是在 ApplicationSet 上设置 resources-finalizer.argocd.argoproj.io,并使用后台级联删除;前台级联不保证 Application 被保留。更直接的 kubectl delete applicationset demo-fleet -n argocd --cascade=orphan 会让 Kubernetes 保留 Application,但 Application 自己若仍带资源 finalizer,日后删除 Application 仍会删除下游工作负载。
preserveResourcesOnDeletion: true 解决的是下一层:它让生成的 Application 不带 resources-finalizer.argocd.argoproj.io,因此 Application 被删时保留下游资源。它不会保留 Application 管理对象、不会继续调谐被留下的 workload,也不能证明未来重新接管没有 field ownership 冲突。三种结果必须分别命名为“保留 Application”“只保留 workload”和“连同 workload 删除”,不能笼统写成非级联删除。
删除实验必须分三轮,且只用无状态测试对象。第一轮从 generator 减去一个元素,观察 applicationsSync 是否保留 Application;第二轮删除 Application,观察 preserveResourcesOnDeletion 生成的 finalizer 状态是否保留 workload;第三轮分别用 background 与 orphan 删除 ApplicationSet,观察 Application ownerReference 和下游对象。每轮都重新创建干净对象,开始前保存 finalizer、ownerReference、controller --policy、override 开关、applicationsSync 与 preserve 设置,结束后确认 Application、Deployment、Service、namespace 和凭证的实际状态。没有这组组合证据,不允许把策略推广到生产。
停止条件包括候选删除含生产集群、共享 namespace、PVC、CRD 或未知 owner;实际删除数超过审批集合;Application 卡在 Terminating;finalizer 所指资源不可达;下游出现仍有流量但 owner 已丢失。此时暂停 controller 对该对象的调谐,保留审计证据,恢复 generator 输入或改用 orphan 保留,不能直接强删 finalizer 来追求“清理完成”。
将 Progressive Sync 放在正确位置
Progressive Sync 可以按生成的 Application label 分组推进,例如先 staging、再一小组 production、最后其余集群。Argo CD 3.4 中该能力仍是 Beta,行为与升级兼容性需要从 Progressive Syncs及对应版本说明确认。它依据 managed Application 的 health 推进,不直接理解 ReplicaSet 流量权重、Argo Rollouts 分析结果或业务 SLO。
spec:
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: environment
operator: In
values: [staging]
maxUpdate: 100%
- matchExpressions:
- key: environment
operator: In
values: [production]
maxUpdate: 10%这里的 maxUpdate 约束同时更新多少 Application,不是流量灰度。若一个 Application 的自定义 health 过早返回 Healthy,下一批会提前推进;若 health 永远 Progressing,队列会停住。推进停止条件必须增加真实请求、错误率、关键依赖和人工批准,关闭自动晋级后再做故障实验。需要按流量权重、自动指标分析和 abort 执行回滚时,应使用 Argo Rollouts 或 Flagger,而不是把 RollingSync 扩大解释成渐进交付控制器。
反例是把 Progressive Sync 当成跨集群事务。前一批已经成功、后一批失败时,它不会自动撤销前一批;集群之间也不存在原子提交。团队要为“部分完成”定义状态记录、重试目标、回退提交和客户影响,确保恢复动作只作用于失败集合或经过批准的全部集合。
按生成链分层排障
第一层检查 generator 输入:cluster Secret 是否存在、标签是否匹配、Git 文件是否可解析、SCM token 是否有效、API 是否限流。第二层检查模板:缺失键、DNS 名称、project、repoURL、revision、destination 是否正确。第三层检查 ApplicationSet reconcile:condition、controller 日志、全局 policy 与生成对象 ownerReference。第四层才进入 Application 的 repo fetch、render、diff、sync 和 health;第五层检查目标集群 RBAC、网络、工作负载与业务请求。
“没有生成 Application”时,先运行 dry-run 并读取 ApplicationSet conditions;“生成数量突增”时,比较参数集合和 selector,不要逐个删 Application,因为 controller 会再次创建;“Application 存在但 OutOfSync”时,问题已经越过 generator,转查 source 与目标;“大量集群同时 forbidden”通常指向共享凭证轮换或目标 RBAC 模板,而不是每个应用同时出错。
暂停也分层。只有在确认全局 --policy 与 per-AppSet override 的实际配置后,才能把策略收紧为 create-update/create-only;该动作只阻止后续集合减项,不能撤销已经发出的删除。需要暂停某个集群的 Application 调谐时,可在 cluster Secret 上使用 argocd.argoproj.io/skip-reconcile: "true"。这项能力在 3.4 仍属 Alpha,而且 cluster Secret 仍可能参与 generator 选择;它不会冻结 ApplicationSet 生成集合、不会回滚,也不会阻止其他 actor 写集群。恢复前要同时重算生成集合并 diff desired/live,避免暂停期间的变化被一次性覆盖。
用容量、限流和成本决定控制面拓扑
多集群规模同时放大三类负载:ApplicationSet controller 计算 generator 与模板,application-controller 为每个集群维护缓存并调谐资源,repo-server 为 Application 生成清单。Cluster generator 的集群数乘应用模板数近似决定 Application 数;再乘每个应用资源数,才接近 controller 需要跟踪的对象规模。SCM/PR generator 还受外部 API rate limit、轮询间隔和分页影响。
容量观测应包含生成集合计算时延与错误、Application 创建/更新/删除速率、controller queue age、每集群 cache、Kubernetes API list/watch 与限流、repo render 并发、Redis、内存和 /tmp。只扩 argocd-server/UI 副本不会缓解 manifest generation 或 application-controller 队列。namespace 限权导致的多路 watch、跨区域网络延迟、私有仓库拉取和日志基数都应进入容量测试。
拓扑选择可以从故障域倒推:少量可信集群可共用一个控制面;租户权限、网络区或升级节奏差异大时,拆分 Argo CD 实例能缩小凭证与扇出半径,但增加升级、SSO、监控和备份成本。新 sharding 算法与动态分配在 3.4 仍属 Alpha,不能用实验能力代替明确的容量基线和故障恢复方案。Application 数量稳定后,再按队列、缓存和 API 配额决定 sharding,而不是先堆 controller 副本。
成本账要包含控制面节点、跨区域流量、SCM API 配额、Git/OCI 下载、日志指标保留、Secret 管理和平台值守。为每个 generator 指定 owner、刷新频率、最大生成数和停用日期;临时 PR 环境必须有 TTL 与孤儿扫描。生成集合增长但业务服务数没有相应增长,往往意味着标签过宽、目录重复或清理失效,应先修模型而非扩容。
建立多租户权限、凭证与审计制度
只有平台管理员应拥有 ApplicationSet 的创建、更新和删除权,因为一个模板可以在任意被允许的 Project 下批量生成 Application。即使应用团队不能修改 ApplicationSet,只要能写 generator 的 Git 文件、cluster label 或 SCM 查询源,也能制造 Application 风暴。授权必须覆盖输入源,而不只是 CR 的 RBAC。
SCM/PR generator 的 token 按组织和仓库最小授权,限制目标域名并监控 rate limit;不要让模板把不可信 URL 与认证 header 组合。Cluster Secret 按高敏凭据加密、轮换、审计和撤销,备份也采用同级保护。ApplicationSet in any namespace 在 3.4 仍是 Beta;启用会扩大 controller 监听与 source namespace 面,需要同时限制可监听 namespace、SCM provider、AppProject 和 Secret 引用,不能把自助服务理解成任意委托。
审计记录应能回答:哪个输入 revision 或 cluster label 改变了参数集合,候选与实际集合差多少,谁批准了新增/删除预算,生成了哪些 Application,它们最终同步到哪些目标,业务验证是否通过。对 project 模板化、任意 repoURL、任意 destination、无严格缺键、无最大集合阈值的 ApplicationSet,策略引擎应直接拒绝。
以退出和灾难演练验证多集群设计
退出一个集群时,先冻结该集群的新变更,导出 Application 与下游资源清单,明确工作负载是删除、孤儿保留还是交给新控制器。随后从生成集合中做受控减项,观察 ApplicationSet policy;按选择处理 Application finalizer 与资源;确认新 owner 接管或资源已清理;最后撤销 cluster Secret、目标 ServiceAccount、云 IAM、证书、网络规则和监控。删除 registration 不会自动撤销目标身份,也不会自动删除工作负载。
控制面恢复不能只依赖 Git。Git 保存期望模板,却未必包含 repository/cluster credential、AppProject、RBAC、SSO、encryption/signing key、运行时 ConfigMap 和 Application 状态。备份应通过 argocd admin export 等入口保存权威对象,并核对对象计数与关键类型;错误 namespace 可能得到“命令成功但内容为空”的假成功。恢复演练要在隔离控制面导入,先关闭自动删除,比较生成集合,再逐批恢复目标集群连接。
最后做一次破坏性反例:让 generator 暂时返回空集,同时把策略保持在不删除模式,证明门禁能阻止 Application 消失;再恢复输入并确认集合稳定。只有当团队能预览扇出、限制批量变化、暂停生成、区分三层删除、撤销跨集群凭证、恢复控制面并核销资源成本时,ApplicationSet 才从“批量 YAML 技巧”变成可治理的多集群交付能力。
