跳过正文
ArgoCD 高级模式:ApplicationSet、Sync Waves 与 GitOps 企业级实践

ArgoCD 高级模式:ApplicationSet、Sync Waves 与 GitOps 企业级实践

·967 字·5 分钟·
目录
DevOps 工程师成长路径 - 这篇文章属于一个选集。
§ : 本文

管理四套 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 的组合是目前最主流的解法。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。
DevOps 工程师成长路径 - 这篇文章属于一个选集。
§ : 本文

相关文章

多集群 Kubernetes 运维:跨集群管理与统一可观测

·1202 字·6 分钟
从单集群到多集群,运维复杂度不是线性增加,而是指数级。这篇文章总结了我们管理跨地域、跨环境多套 K8s 集群的实际经验:如何用 ArgoCD ApplicationSet 统一部署、如何用 Thanos 聚合多集群指标、以及一次真实的跨集群迁移过程。

vcluster 虚拟集群实战:比 namespace 强一百倍的多租户方案

·1391 字·7 分钟
namespace 不是隔离边界,它只是一层命名约定。ClusterRole、CRD、webhook、LimitRange 全都穿透 namespace。真正的多租户需要每个租户有自己的 kube-apiserver。vcluster 让这件事便宜到几乎免费——一个 namespace 里起一个完整的 Kubernetes 控制平面。