管理四套 K8s 环境(US/CN Prod + QA + PRE)、几十个微服务,如果每个应用都手写一个 ArgoCD Application 资源,光 YAML 维护就是灾难。ArgoCD 的 ApplicationSet、Sync Waves 和 Image Updater 这几个高级特性正是为解决规模化问题而生。这篇文章聚焦实战:如何用这些特性把 GitOps 落地到真实的企业环境中。
ApplicationSet:一份模板管理所有集群#
ApplicationSet 是 ArgoCD 的「模板引擎」,用一个 CR 生成多个 Application 对象。核心概念是 Generator —— 负责产生参数列表,模板用这些参数渲染出 Application。
List Generator:固定集群列表#
最简单的场景:你知道要部署到哪些集群,集群列表固定不变。
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-service
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: us-prod
url: https://us-prod.k8s.example.com
env: prod
region: us-west-2
- cluster: cn-prod
url: https://cn-prod.k8s.example.com
env: prod
region: cn-hangzhou
- cluster: qa
url: https://qa.k8s.example.com
env: qa
region: us-west-2
template:
metadata:
name: 'my-service-{{cluster}}'
spec:
project: my-team
source:
repoURL: https://github.com/myorg/gitops
targetRevision: HEAD
path: 'apps/my-service/overlays/{{env}}'
destination:
server: '{{url}}'
namespace: my-service
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
List Generator 的问题是需要手动维护集群列表。接入新集群时要改 ApplicationSet,容易遗漏。
Cluster Generator:动态发现已注册集群#
更灵活的方案:从 ArgoCD 已注册的集群中动态筛选,用集群的 label 来区分环境。
spec:
generators:
- clusters:
selector:
matchLabels:
env: prod # 只匹配带 env=prod 标签的集群
values:
region: '{{metadata.annotations.region}}' # 从集群注解读取额外参数
注册集群时打好标签:
argocd cluster add my-cluster \
--label env=prod \
--annotation region=us-west-2
这样新集群接入后,只要打了对应标签,ApplicationSet 会自动生成 Application,不需要改任何配置。
Git Generator:目录结构即部署配置#
Git Generator 根据 Git 仓库的目录结构自动生成 Application。适合「每个服务一个目录」的 monorepo 风格:
spec:
generators:
- git:
repoURL: https://github.com/myorg/gitops
revision: HEAD
directories:
- path: apps/*/overlays/prod # 匹配所有服务的 prod overlay
ArgoCD 会扫描仓库,找到所有匹配 apps/*/overlays/prod 的目录,每个目录生成一个 Application。新增服务只需要在仓库里创建对应目录,无需手动创建 Application。
也可以用文件模式,读取每个目录下的 config.json 来获取参数:
generators:
- git:
repoURL: https://github.com/myorg/gitops
revision: HEAD
files:
- path: apps/*/config.json
config.json 内容示例:
{
"service_name": "order-service",
"team": "commerce",
"replicas": 3,
"memory_limit": "512Mi"
}
模板里用 {{service_name}}、{{team}} 引用这些参数。
Matrix Generator:组合生成#
Matrix Generator 把两个 Generator 的输出做笛卡尔积。典型场景:所有服务 × 所有集群:
spec:
generators:
- matrix:
generators:
- git:
repoURL: https://github.com/myorg/gitops
revision: HEAD
files:
- path: services/*/config.json
- clusters:
selector:
matchLabels:
env: prod
结果:每个服务 × 每个 prod 集群 = N×M 个 Application,全部自动管理。
注意:Matrix Generator 很强大但也很危险。如果服务数量 × 集群数量 > 100,ArgoCD controller 的压力会显著增大。大规模使用前要调整 --status-processors 和 --operation-processors 参数。
Sync Waves:精确控制部署顺序#
默认情况下,ArgoCD 会尽可能并行应用所有资源。但有些场景需要严格顺序:数据库迁移 Job 必须在 Deployment 之前完成;CRD 必须在使用它的资源之前创建。
Sync Waves 通过 annotation 给资源指定波次编号,ArgoCD 按编号从小到大逐波部署,每波都等所有资源 healthy 后再进行下一波:
# 1. 先部署 CRD(wave -2,确保最先)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: myresource.example.com
annotations:
argocd.argoproj.io/sync-wave: "-2"
---
# 2. 创建 Namespace 和 ConfigMap(wave -1)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
# 3. 数据库迁移 Job(wave 0,默认值)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
template:
spec:
containers:
- name: migrate
image: myapp:v1.2.0
command: ["python", "manage.py", "migrate"]
restartPolicy: Never
backoffLimit: 3
---
# 4. 主应用部署(wave 1,等迁移完成)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
annotations:
argocd.argoproj.io/sync-wave: "1"
Wave 之间的等待条件:前一波的所有资源必须达到 healthy 状态。对于 Job,healthy 意味着 Job 成功完成(Complete 状态)。所以这个模式能确保数据库迁移完成后再启动应用,不用在应用里加重试逻辑。
Sync Hooks:更精细的生命周期控制#
Sync Waves 控制顺序,Sync Hooks 控制时机。Hook 资源在特定同步阶段执行:
apiVersion: batch/v1
kind: Job
metadata:
name: pre-sync-backup
annotations:
argocd.argoproj.io/hook: PreSync # 同步前执行
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation # 下次同步前删除旧 Job
spec:
template:
spec:
containers:
- name: backup
image: postgres:15
command:
- sh
- -c
- pg_dump $DATABASE_URL > /backup/$(date +%Y%m%d).sql
restartPolicy: Never
Hook 类型:
PreSync:同步开始前(数据库备份、前置检查)Sync:同步过程中(和普通资源一起,但有独立生命周期管理)PostSync:同步成功后(冒烟测试、发送通知)SyncFail:同步失败时(回滚操作、告警)
hook-delete-policy 决定 Hook Job 何时清理:
BeforeHookCreation:下次创建前删除(推荐,避免 Job 名冲突)HookSucceeded:成功后立即删除HookFailed:失败后删除(调试时不要用,因为你看不到日志)
ArgoCD Notifications:部署事件推送钉钉#
ArgoCD Notifications 是独立组件,监听 Application 事件并推送到各种渠道。配置分两部分:模板(消息格式)和触发器(触发条件)。
安装后配置 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
# 钉钉服务配置
service.webhook.dingtalk: |
url: https://oapi.dingtalk.com/robot/send?access_token=$DINGTALK_TOKEN
headers:
- name: Content-Type
value: application/json
# 消息模板
template.app-deployed: |
webhook:
dingtalk:
method: POST
body: |
{
"msgtype": "markdown",
"markdown": {
"title": "部署成功",
"text": "### ✅ {{.app.metadata.name}} 部署成功\n\n**环境**: {{.app.spec.destination.server}}\n\n**版本**: {{.app.status.sync.revision | truncate 8 \"\"}}\n\n**时间**: {{now | date \"2006-01-02 15:04:05\"}}"
}
}
template.app-sync-failed: |
webhook:
dingtalk:
method: POST
body: |
{
"msgtype": "markdown",
"markdown": {
"title": "部署失败",
"text": "### ❌ {{.app.metadata.name}} 同步失败\n\n**原因**: {{.app.status.operationState.message}}\n\n**操作人**: {{.app.status.operationState.operation.initiatedBy.username}}"
}
}
# 触发器配置
trigger.on-deployed: |
- when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'
send: [app-deployed]
trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]
# 默认订阅(所有 app 都推送)
defaultTriggers: |
- on-sync-failed
在 Application 上开启通知:
metadata:
annotations:
notifications.argoproj.io/subscribe.on-deployed.dingtalk: ""
notifications.argoproj.io/subscribe.on-sync-failed.dingtalk: ""
或者用 AppProject 级别统一订阅,不用每个 Application 都加注解。
Image Updater:打通镜像自动更新#
ArgoCD Image Updater 监听镜像仓库(ECR/ACR/Docker Hub),发现新 tag 后自动更新 GitOps 仓库里的镜像版本,触发 ArgoCD 同步。
配置示例(Application 注解方式):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-service
annotations:
# 监听这个镜像的更新
argocd-image-updater.argoproj.io/image-list: >
myapp=123456789.dkr.ecr.us-west-2.amazonaws.com/my-service
# 更新策略:semver 匹配
argocd-image-updater.argoproj.io/myapp.update-strategy: semver
argocd-image-updater.argoproj.io/myapp.allow-tags: regexp:^v[0-9]+\.[0-9]+\.[0-9]+$
# 写回 Git(而不是直接改 Application)
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: main
Image Updater 在检测到新镜像后,会向 Git 仓库提交一个 .argocd-source-<app-name>.yaml 文件(或更新 Kustomize 的 image override),然后 ArgoCD 检测到 Git 变化触发同步。整个流程:
CI 构建推送镜像 → ECR → Image Updater 轮询发现 → 提交 Git → ArgoCD 同步 → 集群更新
对接 ECR 需要给 Image Updater 的 ServiceAccount 配置 IAM 权限,或挂载 ECR 凭据 Secret。
OutOfSync 排查:区分真实漂移和误判#
OutOfSync 不一定意味着有人手动改了集群,很多时候是 ArgoCD 的「误判」。常见原因:
1. server-side apply 导致的 managedFields 差异#
K8s 1.22+ 默认使用 Server-Side Apply,会在资源上添加 managedFields。ArgoCD 在 diff 时如果没有正确处理,会显示这些字段的差异。
解决方案:开启 --server-side 同步选项:
syncPolicy:
syncOptions:
- ServerSideApply=true
2. Helm chart 生成的随机内容#
某些 Helm chart 在每次 template 渲染时会生成随机值(比如自动生成密码)。ArgoCD 每次 reconcile 都重新渲染,导致持续显示 OutOfSync。
解决方案:用 ignoreDifferences 忽略这些字段:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/template/metadata/annotations/rollme # 随机 rollout 注解
- group: ""
kind: Secret
name: auto-generated-secret
jsonPointers:
- /data # 忽略自动生成的 Secret 内容
3. 控制器修改的字段#
某些控制器(如 HPA)会修改 Deployment 的 spec.replicas。如果 GitOps 里固定了副本数,HPA 改了之后就会显示 OutOfSync。
解决方案:Git 里不设置 replicas,让 HPA 完全控制:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
managedFieldsManagers:
- kube-controller-manager # 忽略 controller manager 管理的字段
或者直接在 Kustomize 里删除 replicas 字段,让 HPA 接管。
多租户 RBAC:AppProject 隔离#
企业环境中多个团队共用一个 ArgoCD,AppProject 是关键隔离边界:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: commerce-team
namespace: argocd
spec:
description: 商业化团队项目
# 只允许从这个 Git 仓库同步
sourceRepos:
- https://github.com/myorg/gitops
# 只允许部署到这些集群和命名空间
destinations:
- server: https://us-prod.k8s.example.com
namespace: commerce-*
- server: https://qa.k8s.example.com
namespace: '*'
# 禁止使用的资源类型(防止越权)
clusterResourceBlacklist:
- group: ""
kind: Namespace
- group: rbac.authorization.k8s.io
kind: ClusterRole
# RBAC 规则
roles:
- name: developer
policies:
- p, proj:commerce-team:developer, applications, get, commerce-team/*, allow
- p, proj:commerce-team:developer, applications, sync, commerce-team/*, allow
groups:
- commerce-developers # 对应 SSO 组
- name: admin
policies:
- p, proj:commerce-team:admin, applications, *, commerce-team/*, allow
groups:
- commerce-admins
这样商业化团队的开发者只能操作 commerce-team 项目下的应用,只能部署到 commerce-* 命名空间,无法创建 Namespace 或 ClusterRole,与其他团队完全隔离。
大集群性能调优#
管理 100+ Application 时,ArgoCD 默认配置会成为瓶颈。几个关键参数:
# argocd-application-controller 参数
--status-processors 20 # 并发处理 Application 状态的 goroutine(默认 20)
--operation-processors 10 # 并发执行同步操作的 goroutine(默认 10)
--app-resync-period 180 # 每个 Application 的 reconcile 间隔(秒,默认 180)
# argocd-repo-server 参数
--parallelismlimit 10 # 并发 manifest 生成数量
repo-server 通常是瓶颈,因为所有 manifest 生成都在这里。可以水平扩展 repo-server(它是无状态的),但 application-controller 是 StatefulSet,扩展需要开启 sharding:
# application-controller 开启 sharding
env:
- name: ARGOCD_CONTROLLER_REPLICAS
value: "3" # 3 个副本分片管理所有 Application
另外,如果 Git 仓库很大,每次 resync 都 clone 会很慢。确保 repo-server 的 --repo-cache-expiration 设置合理(默认 24h),避免频繁重新 clone。
企业级 ArgoCD 的成熟标志不是会用,而是能在几十个团队、几百个应用的规模下稳定运行,同时保持每个团队的操作自主性。ApplicationSet + AppProject 的组合是目前最主流的解法。






