ArgoCD ApplicationSets for Multi-Environment Deployment: Enterprise GitOps at Scale
ArgoCD ApplicationSets revolutionize how enterprises manage applications across multiple environments, clusters, and tenants. This comprehensive guide explores advanced ApplicationSet patterns, generators, and automation strategies for production-scale GitOps implementations.
ArgoCD ApplicationSets for Multi-Environment Deployment: Enterprise GitOps at Scale
Executive Summary
ApplicationSets extend ArgoCD’s capabilities to automatically generate and manage Applications across multiple targets using templated specifications. This guide covers advanced ApplicationSet patterns including cluster generators, matrix generators, progressive delivery strategies, and enterprise-scale multi-tenancy implementations that enable teams to manage thousands of applications across hundreds of clusters.
Understanding ApplicationSets Architecture
Core Concepts
ApplicationSets introduce a controller that watches ApplicationSet resources and generates Application resources based on specified generators:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: multi-environment-app
namespace: argocd
spec:
# Generators produce parameters for templating
generators:
- list:
elements:
- cluster: prod-us-east
environment: production
replicas: "5"
resources:
cpu: "1000m"
memory: "2Gi"
- cluster: prod-eu-west
environment: production
replicas: "5"
resources:
cpu: "1000m"
memory: "2Gi"
- cluster: staging
environment: staging
replicas: "2"
resources:
cpu: "500m"
memory: "1Gi"
- cluster: dev
environment: development
replicas: "1"
resources:
cpu: "250m"
memory: "512Mi"
# Template for generating Applications
template:
metadata:
name: '{{cluster}}-myapp'
labels:
environment: '{{environment}}'
annotations:
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: 'deployments'
spec:
project: default
source:
repoURL: https://github.com/example/myapp
targetRevision: HEAD
path: deploy/{{environment}}
helm:
parameters:
- name: replicas
value: '{{replicas}}'
- name: resources.cpu
value: '{{resources.cpu}}'
- name: resources.memory
value: '{{resources.memory}}'
destination:
server: https://kubernetes.default.svc
namespace: myapp-{{environment}}
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
ApplicationSet Controller Configuration
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-applicationset-controller
namespace: argocd
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: argocd-applicationset-controller
template:
metadata:
labels:
app.kubernetes.io/name: argocd-applicationset-controller
spec:
serviceAccountName: argocd-applicationset-controller
containers:
- name: argocd-applicationset-controller
image: quay.io/argoproj/argocd:v2.9.0
command:
- argocd-applicationset-controller
args:
- --metrics-addr=:8080
- --probe-addr=:8081
- --webhook-addr=:7000
# Performance tuning
- --concurrent-reconciliations=10
- --max-reconciliation-rate=100
# Enable all generators
- --enable-progressive-syncs=true
- --enable-scm-providers=true
# Policy configuration
- --policy=create-update
- --enable-policy-override=false
# Logging
- --loglevel=info
- --log-format=json
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS
value: "true"
- name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING
value: "true"
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
ports:
- containerPort: 8080
name: metrics
- containerPort: 8081
name: probe
- containerPort: 7000
name: webhook
livenessProbe:
httpGet:
path: /healthz
port: 8081
readinessProbe:
httpGet:
path: /readyz
port: 8081
Generator Patterns
Cluster Generator
Automatically discover and deploy to clusters:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
argocd.argoproj.io/secret-type: cluster
matchExpressions:
- key: environment
operator: In
values: [production, staging]
values:
# Default values
revision: main
logLevel: info
# Template override values
clusterResourceWhitelist: |
- group: '*'
kind: '*'
template:
metadata:
name: '{{name}}-addons'
labels:
cluster: '{{name}}'
environment: '{{metadata.labels.environment}}'
spec:
project: cluster-addons
source:
repoURL: https://github.com/example/cluster-addons
targetRevision: '{{values.revision}}'
path: 'addons/{{metadata.labels.environment}}'
directory:
recurse: true
jsonnet:
extVars:
- name: clusterName
value: '{{name}}'
- name: environment
value: '{{metadata.labels.environment}}'
- name: logLevel
value: '{{values.logLevel}}'
destination:
server: '{{server}}'
namespace: kube-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
---
apiVersion: v1
kind: Secret
metadata:
name: prod-us-east-cluster
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
environment: production
region: us-east-1
tier: production
type: Opaque
stringData:
name: prod-us-east
server: https://prod-us-east.k8s.example.com
config: |
{
"bearerToken": "<token>",
"tlsClientConfig": {
"insecure": false,
"caData": "<base64-ca-cert>"
}
}
Git File Generator
Generate applications from files in Git repositories:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: tenant-applications
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/example/tenant-config
revision: HEAD
files:
- path: "tenants/*/config.json"
template:
metadata:
name: '{{tenant}}-{{app}}'
labels:
tenant: '{{tenant}}'
app: '{{app}}'
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: '{{tenant}}'
source:
repoURL: '{{repoUrl}}'
targetRevision: '{{targetRevision}}'
path: '{{path}}'
helm:
parameters:
- name: namespace
value: '{{namespace}}'
- name: replicas
value: '{{replicas}}'
valueFiles:
- 'values-{{environment}}.yaml'
destination:
server: '{{clusterUrl}}'
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
---
# Example tenant config file: tenants/acme/config.json
{
"tenant": "acme",
"app": "web-portal",
"repoUrl": "https://github.com/acme/web-portal",
"targetRevision": "main",
"path": "deploy/k8s",
"environment": "production",
"clusterUrl": "https://prod-cluster.k8s.example.com",
"namespace": "acme-web",
"replicas": "3"
}
Matrix Generator
Combine multiple generators for complex scenarios:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: multi-cluster-multi-env
namespace: argocd
spec:
generators:
# Matrix combines cluster and git generators
- matrix:
generators:
# First dimension: clusters
- clusters:
selector:
matchLabels:
argocd.argoproj.io/secret-type: cluster
values:
# Cluster-specific defaults
monitoring: "true"
# Second dimension: applications from git
- git:
repoURL: https://github.com/example/apps
revision: HEAD
directories:
- path: apps/*
- path: apps/excluded-app
exclude: true
template:
metadata:
name: '{{path.basename}}-{{name}}'
labels:
app: '{{path.basename}}'
cluster: '{{name}}'
environment: '{{metadata.labels.environment}}'
spec:
project: default
source:
repoURL: https://github.com/example/apps
targetRevision: HEAD
path: '{{path}}'
helm:
parameters:
# Cluster-specific parameters
- name: cluster.name
value: '{{name}}'
- name: cluster.region
value: '{{metadata.labels.region}}'
# Environment-specific parameters
- name: environment
value: '{{metadata.labels.environment}}'
- name: monitoring.enabled
value: '{{values.monitoring}}'
# Dynamic resource allocation
- name: resources.requests.cpu
value: '{{metadata.annotations.default-cpu}}'
- name: resources.requests.memory
value: '{{metadata.annotations.default-memory}}'
destination:
server: '{{server}}'
namespace: '{{path.basename}}-{{metadata.labels.environment}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
# Environment-specific sync waves
info:
- name: Environment
value: '{{metadata.labels.environment}}'
- name: Cluster
value: '{{name}}'
Pull Request Generator
Automatically create preview environments for pull requests:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: pr-preview-environments
namespace: argocd
spec:
generators:
- pullRequest:
github:
owner: example
repo: myapp
tokenRef:
secretName: github-token
key: token
labels:
- preview
requeueAfterSeconds: 60
template:
metadata:
name: 'pr-{{number}}-{{branch_slug}}'
labels:
preview: "true"
pr-number: "{{number}}"
annotations:
argocd.argoproj.io/compare-options: IgnoreExtraneous
notifications.argoproj.io/subscribe.on-deployed.github: ""
spec:
project: preview-environments
source:
repoURL: '{{head_repo_url}}'
targetRevision: '{{head_sha}}'
path: deploy/k8s
helm:
parameters:
- name: image.tag
value: 'pr-{{number}}'
- name: ingress.host
value: 'pr-{{number}}.preview.example.com'
- name: resources.limits.cpu
value: "500m"
- name: resources.limits.memory
value: "512Mi"
destination:
server: https://kubernetes.default.svc
namespace: 'preview-pr-{{number}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# Auto-cleanup after 7 days
info:
- name: PR
value: '{{number}}'
- name: Branch
value: '{{branch}}'
- name: Author
value: '{{head_sha_short}}'
---
apiVersion: v1
kind: Secret
metadata:
name: github-token
namespace: argocd
type: Opaque
stringData:
token: <github-personal-access-token>
SCM Provider Generator
Automatically discover repositories from SCM providers:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: all-microservices
namespace: argocd
spec:
generators:
- scmProvider:
github:
organization: example
tokenRef:
secretName: github-token
key: token
allBranches: false
filters:
# Only repositories with specific topics
- repositoryMatch: ".*-service$"
- pathExists: deploy/kubernetes
- labelMatch: "deploy-to-prod"
cloneProtocol: https
requeueAfterSeconds: 300
template:
metadata:
name: '{{repository}}'
labels:
app: '{{repository}}'
team: '{{organization}}'
annotations:
app-url: '{{url}}'
spec:
project: microservices
source:
repoURL: '{{url}}'
targetRevision: '{{branch}}'
path: deploy/kubernetes
kustomize:
namePrefix: '{{repository}}-'
commonLabels:
app: '{{repository}}'
version: '{{sha_short}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{repository}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
# Resource customization
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
Multi-Environment Patterns
Environment Promotion Strategy
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: progressive-deployment
namespace: argocd
spec:
generators:
- list:
elements:
# Development environment - auto-sync
- environment: dev
cluster: dev-cluster
autoSync: "true"
prune: "true"
selfHeal: "true"
namespace: myapp-dev
replicaCount: "1"
imageTag: "latest"
# Staging environment - auto-sync with approval
- environment: staging
cluster: staging-cluster
autoSync: "true"
prune: "true"
selfHeal: "false"
namespace: myapp-staging
replicaCount: "2"
imageTag: "{{GIT_TAG}}"
# Production environment - manual sync only
- environment: prod
cluster: prod-cluster
autoSync: "false"
prune: "false"
selfHeal: "false"
namespace: myapp-prod
replicaCount: "5"
imageTag: "{{APPROVED_TAG}}"
template:
metadata:
name: 'myapp-{{environment}}'
labels:
environment: '{{environment}}'
annotations:
notifications.argoproj.io/subscribe.on-sync-running.slack: 'deployments-{{environment}}'
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: 'deployments-{{environment}}'
notifications.argoproj.io/subscribe.on-sync-failed.slack: 'deployments-{{environment}}'
spec:
project: myapp
source:
repoURL: https://github.com/example/myapp
targetRevision: HEAD
path: deploy/{{environment}}
helm:
parameters:
- name: replicaCount
value: '{{replicaCount}}'
- name: image.tag
value: '{{imageTag}}'
- name: environment
value: '{{environment}}'
destination:
server: '{{cluster}}'
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: '{{prune}}' == 'true'
selfHeal: '{{selfHeal}}' == 'true'
syncOptions:
- CreateNamespace=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 1m
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: myapp
namespace: argocd
spec:
description: MyApp project with environment-specific policies
sourceRepos:
- https://github.com/example/myapp
destinations:
# Development
- namespace: myapp-dev
server: https://dev-cluster.example.com
# Staging
- namespace: myapp-staging
server: https://staging-cluster.example.com
# Production with restrictions
- namespace: myapp-prod
server: https://prod-cluster.example.com
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceWhitelist:
- group: apps
kind: Deployment
- group: ''
kind: Service
- group: ''
kind: ConfigMap
- group: ''
kind: Secret
- group: networking.k8s.io
kind: Ingress
# Deny certain resources in production
namespaceResourceBlacklist:
- group: ''
kind: ResourceQuota
# Require manual approval for production
syncWindows:
- kind: deny
schedule: '* * * * *'
duration: 24h
applications:
- myapp-prod
manualSync: true
Blue-Green Deployment Pattern
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: blue-green-deployment
namespace: argocd
spec:
generators:
- matrix:
generators:
# Environments
- list:
elements:
- environment: production
cluster: https://prod.k8s.example.com
# Blue/Green slots
- list:
elements:
- slot: blue
active: "true"
weight: "100"
- slot: green
active: "false"
weight: "0"
template:
metadata:
name: 'myapp-{{environment}}-{{slot}}'
labels:
environment: '{{environment}}'
slot: '{{slot}}'
active: '{{active}}'
spec:
project: default
source:
repoURL: https://github.com/example/myapp
targetRevision: HEAD
path: deploy/base
kustomize:
commonLabels:
slot: '{{slot}}'
patches:
- target:
kind: Service
name: myapp
patch: |
- op: replace
path: /spec/selector/slot
value: {{slot}}
- target:
kind: Ingress
name: myapp
patch: |
- op: add
path: /metadata/annotations/nginx.ingress.kubernetes.io~1canary
value: "{{active == 'false'}}"
- op: add
path: /metadata/annotations/nginx.ingress.kubernetes.io~1canary-weight
value: "{{weight}}"
destination:
server: '{{cluster}}'
namespace: 'myapp-{{environment}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Canary Deployment with Progressive Delivery
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: canary-deployment
namespace: argocd
spec:
generators:
- list:
elements:
# Stable version
- version: stable
tag: v1.0.0
weight: "90"
replicas: "9"
# Canary version
- version: canary
tag: v1.1.0
weight: "10"
replicas: "1"
template:
metadata:
name: 'myapp-{{version}}'
labels:
version: '{{version}}'
annotations:
argocd.argoproj.io/sync-wave: '{{version == "stable" && "1" || "2"}}'
spec:
project: default
source:
repoURL: https://github.com/example/myapp
targetRevision: '{{tag}}'
path: deploy/k8s
helm:
parameters:
- name: replicaCount
value: '{{replicas}}'
- name: image.tag
value: '{{tag}}'
- name: service.version
value: '{{version}}'
- name: canary.weight
value: '{{weight}}'
destination:
server: https://kubernetes.default.svc
namespace: myapp-production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# Analysis template for automated promotion
info:
- name: Analysis
value: |
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result >= 0.95
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{status=~"2.."}[5m]))
/
sum(rate(http_requests_total[5m]))
Multi-Cluster Management
Cluster Registration Automation
apiVersion: batch/v1
kind: CronJob
metadata:
name: cluster-discovery
namespace: argocd
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
serviceAccountName: cluster-discovery
containers:
- name: discover
image: bitnami/kubectl:1.28
command:
- /bin/bash
- -c
- |
#!/bin/bash
set -e
# Discover clusters from cloud provider
CLUSTERS=$(kubectl get clusters.cluster.x-k8s.io -A -o json | \
jq -r '.items[] | @base64')
for cluster in $CLUSTERS; do
_jq() {
echo ${cluster} | base64 -d | jq -r ${1}
}
NAME=$(_jq '.metadata.name')
NAMESPACE=$(_jq '.metadata.namespace')
SERVER=$(_jq '.spec.controlPlaneEndpoint.host')
CA_CERT=$(_jq '.spec.controlPlaneRef.caData')
# Get cluster token
SA_SECRET=$(kubectl get sa argocd-manager -n kube-system \
--context=${NAME} -o jsonpath='{.secrets[0].name}')
TOKEN=$(kubectl get secret ${SA_SECRET} -n kube-system \
--context=${NAME} -o jsonpath='{.data.token}' | base64 -d)
# Register cluster with ArgoCD
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: cluster-${NAME}
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
environment: ${ENVIRONMENT:-production}
region: ${REGION:-us-east-1}
discovered: "true"
type: Opaque
stringData:
name: ${NAME}
server: https://${SERVER}
config: |
{
"bearerToken": "${TOKEN}",
"tlsClientConfig": {
"insecure": false,
"caData": "${CA_CERT}"
}
}
EOF
echo "Registered cluster: ${NAME}"
done
restartPolicy: OnFailure
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: cluster-discovery
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-discovery
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: ["cluster.x-k8s.io"]
resources: ["clusters"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-discovery
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-discovery
subjects:
- kind: ServiceAccount
name: cluster-discovery
namespace: argocd
Multi-Region Deployment
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: global-application
namespace: argocd
spec:
generators:
- matrix:
generators:
# Regions
- list:
elements:
- region: us-east-1
cluster: https://prod-us-east-1.k8s.example.com
replicas: "5"
storageClass: gp3
- region: us-west-2
cluster: https://prod-us-west-2.k8s.example.com
replicas: "3"
storageClass: gp3
- region: eu-west-1
cluster: https://prod-eu-west-1.k8s.example.com
replicas: "5"
storageClass: gp3
- region: ap-southeast-1
cluster: https://prod-ap-southeast-1.k8s.example.com
replicas: "3"
storageClass: gp3
# Availability zones per region
- list:
elements:
- zone: a
priority: "high"
- zone: b
priority: "high"
- zone: c
priority: "medium"
template:
metadata:
name: 'myapp-{{region}}-{{zone}}'
labels:
region: '{{region}}'
zone: '{{zone}}'
priority: '{{priority}}'
spec:
project: global-apps
source:
repoURL: https://github.com/example/myapp
targetRevision: HEAD
path: deploy/k8s
helm:
parameters:
- name: replicaCount
value: '{{replicas}}'
- name: affinity.zone
value: '{{region}}{{zone}}'
- name: storage.class
value: '{{storageClass}}'
- name: tolerations
value: |
- key: "zone"
operator: "Equal"
value: "{{zone}}"
effect: "NoSchedule"
destination:
server: '{{cluster}}'
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- RespectIgnoreDifferences=true
ignoreDifferences:
# Allow regional differences
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
Multi-Tenancy Implementation
Tenant Isolation Pattern
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: tenant-workloads
namespace: argocd
spec:
generators:
- matrix:
generators:
# Tenant definitions
- git:
repoURL: https://github.com/example/tenant-config
revision: HEAD
files:
- path: "tenants/*/tenant.yaml"
# Application definitions per tenant
- git:
repoURL: https://github.com/example/tenant-config
revision: HEAD
files:
- path: "tenants/{{tenant}}/apps/*/app.yaml"
template:
metadata:
name: '{{tenant}}-{{app}}'
labels:
tenant: '{{tenant}}'
app: '{{app}}'
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: 'tenant-{{tenant}}'
source:
repoURL: '{{repoUrl}}'
targetRevision: '{{branch}}'
path: '{{path}}'
helm:
parameters:
- name: tenant
value: '{{tenant}}'
- name: app
value: '{{app}}'
# Tenant resource limits
- name: resources.limits.cpu
value: '{{quota.cpu}}'
- name: resources.limits.memory
value: '{{quota.memory}}'
# Tenant network policies
- name: networkPolicy.enabled
value: 'true'
- name: networkPolicy.ingress
value: '{{networkPolicy.ingress}}'
destination:
server: '{{cluster}}'
namespace: '{{tenant}}-{{app}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
# Sync waves for proper ordering
syncPolicy:
syncOptions:
- CreateNamespace=true
---
# Example tenant.yaml
apiVersion: v1
kind: Config
tenant: acme
quota:
cpu: "4000m"
memory: "8Gi"
storage: "100Gi"
networkPolicy:
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: acme
cluster: https://kubernetes.default.svc
---
# Example app.yaml
apiVersion: v1
kind: Config
app: web-portal
repoUrl: https://github.com/acme/web-portal
branch: main
path: deploy/k8s
AppProject Per Tenant
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: tenant-projects
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/example/tenant-config
revision: HEAD
files:
- path: "tenants/*/tenant.yaml"
template:
metadata:
name: 'project-{{tenant}}'
spec:
# Generate AppProject for tenant
template:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: 'tenant-{{tenant}}'
namespace: argocd
spec:
description: 'Project for tenant {{tenant}}'
sourceRepos:
- '{{sourceRepos}}'
destinations:
- namespace: '{{tenant}}-*'
server: '{{cluster}}'
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceWhitelist:
- group: '*'
kind: '*'
# Tenant-specific quotas
resourceQuotas:
- hard:
requests.cpu: '{{quota.cpu}}'
requests.memory: '{{quota.memory}}'
persistentvolumeclaims: '{{quota.pvcs}}'
# Role-based access
roles:
- name: developer
description: Developer role for tenant {{tenant}}
policies:
- p, proj:tenant-{{tenant}}:developer, applications, get, tenant-{{tenant}}/*, allow
- p, proj:tenant-{{tenant}}:developer, applications, sync, tenant-{{tenant}}/*, allow
groups:
- '{{tenant}}-developers'
- name: admin
description: Admin role for tenant {{tenant}}
policies:
- p, proj:tenant-{{tenant}}:admin, applications, *, tenant-{{tenant}}/*, allow
- p, proj:tenant-{{tenant}}:admin, repositories, *, *, allow
groups:
- '{{tenant}}-admins'
Progressive Sync Strategies
Wave-Based Deployment
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: wave-deployment
namespace: argocd
spec:
generators:
- list:
elements:
# Wave 0: Infrastructure
- name: namespace
wave: "0"
path: infrastructure/namespace
- name: secrets
wave: "0"
path: infrastructure/secrets
# Wave 1: Dependencies
- name: database
wave: "1"
path: dependencies/database
- name: cache
wave: "1"
path: dependencies/cache
# Wave 2: Application
- name: backend
wave: "2"
path: application/backend
- name: frontend
wave: "2"
path: application/frontend
# Wave 3: Ingress
- name: ingress
wave: "3"
path: infrastructure/ingress
template:
metadata:
name: '{{name}}'
annotations:
argocd.argoproj.io/sync-wave: '{{wave}}'
spec:
project: default
source:
repoURL: https://github.com/example/myapp
targetRevision: HEAD
path: 'deploy/{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 1m
Health-Based Progressive Rollout
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: progressive-rollout
namespace: argocd
spec:
generators:
- list:
elements:
# Phase 1: 10% traffic
- phase: "1"
percentage: "10"
duration: "5m"
# Phase 2: 25% traffic
- phase: "2"
percentage: "25"
duration: "10m"
# Phase 3: 50% traffic
- phase: "3"
percentage: "50"
duration: "15m"
# Phase 4: 100% traffic
- phase: "4"
percentage: "100"
duration: "0m"
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: phase
operator: In
values: ["1"]
- matchExpressions:
- key: phase
operator: In
values: ["2"]
maxUpdate: 25%
- matchExpressions:
- key: phase
operator: In
values: ["3"]
maxUpdate: 50%
- matchExpressions:
- key: phase
operator: In
values: ["4"]
template:
metadata:
name: 'myapp-phase-{{phase}}'
labels:
phase: '{{phase}}'
annotations:
argocd.argoproj.io/sync-options: "SkipDryRunOnMissingResource=true"
spec:
project: default
source:
repoURL: https://github.com/example/myapp
targetRevision: HEAD
path: deploy/k8s
helm:
parameters:
- name: canary.weight
value: '{{percentage}}'
- name: canary.duration
value: '{{duration}}'
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: false
syncOptions:
- CreateNamespace=true
# Health assessment
health:
timeout: 300
retries: 3
Monitoring and Observability
Metrics Collection
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-metrics-config
namespace: argocd
data:
application.metrics: |
- name: applicationset_info
help: Information about ApplicationSet
type: gauge
labels:
name: $.metadata.name
namespace: $.metadata.namespace
value: 1
- name: applicationset_applications_count
help: Number of applications managed by ApplicationSet
type: gauge
labels:
name: $.metadata.name
value: $.status.applicationsCount
- name: applicationset_conditions
help: ApplicationSet conditions
type: gauge
labels:
name: $.metadata.name
type: $.status.conditions[*].type
status: $.status.conditions[*].status
value: 1
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-applicationset-controller
namespace: argocd
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-applicationset-controller
endpoints:
- port: metrics
interval: 30s
path: /metrics
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: applicationset-alerts
namespace: argocd
spec:
groups:
- name: applicationset
interval: 30s
rules:
- alert: ApplicationSetSyncFailure
expr: |
sum by (name, namespace) (
argocd_app_sync_total{phase="Failed"}
) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "ApplicationSet {{ $labels.name }} sync failed"
description: "ApplicationSet {{ $labels.name }} in namespace {{ $labels.namespace }} has failed syncs"
- alert: ApplicationSetHighReconciliationTime
expr: |
histogram_quantile(0.95,
sum(rate(argocd_applicationset_reconcile_duration_seconds_bucket[5m])) by (le, name)
) > 30
for: 10m
labels:
severity: warning
annotations:
summary: "ApplicationSet {{ $labels.name }} slow reconciliation"
description: "95th percentile reconciliation time is {{ $value }} seconds"
Grafana Dashboard
apiVersion: v1
kind: ConfigMap
metadata:
name: applicationset-dashboard
namespace: argocd
data:
dashboard.json: |
{
"dashboard": {
"title": "ArgoCD ApplicationSets",
"panels": [
{
"title": "ApplicationSets Count",
"targets": [
{
"expr": "count(argocd_applicationset_info)"
}
]
},
{
"title": "Applications Per ApplicationSet",
"targets": [
{
"expr": "argocd_applicationset_applications_count"
}
]
},
{
"title": "Reconciliation Duration",
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(argocd_applicationset_reconcile_duration_seconds_bucket[5m])) by (le, name))"
}
]
},
{
"title": "Sync Success Rate",
"targets": [
{
"expr": "sum(rate(argocd_app_sync_total{phase=\"Succeeded\"}[5m])) / sum(rate(argocd_app_sync_total[5m]))"
}
]
},
{
"title": "Generator Errors",
"targets": [
{
"expr": "sum(rate(argocd_applicationset_generator_errors_total[5m])) by (generator)"
}
]
}
]
}
}
Best Practices
Performance Optimization
- Use Appropriate Generators: Choose the most efficient generator for your use case
- Limit Generator Frequency: Set appropriate
requeueAfterSecondsvalues - Implement Caching: Use Git repository caching to reduce API calls
- Resource Limits: Set appropriate resource limits for ApplicationSet controller
- Selective Sync: Use
ApplyOutOfSyncOnlyto reduce unnecessary syncs
Security Considerations
- RBAC Configuration: Implement least-privilege access for ApplicationSets
- Secret Management: Use external secret managers for sensitive data
- Repository Access: Use read-only tokens for Git repositories
- Network Policies: Implement network segmentation for multi-tenancy
- Audit Logging: Enable comprehensive audit logging for compliance
Operational Excellence
- Monitoring: Implement comprehensive monitoring and alerting
- Testing: Test ApplicationSet changes in non-production environments
- Documentation: Document generator patterns and deployment strategies
- Backup: Regular backup of ApplicationSet configurations
- Disaster Recovery: Implement DR procedures for GitOps infrastructure
Conclusion
ArgoCD ApplicationSets provide powerful capabilities for managing applications at enterprise scale. By leveraging advanced generator patterns, progressive delivery strategies, and multi-tenancy implementations, organizations can achieve highly automated, scalable GitOps workflows.
Key takeaways:
- Use matrix and cluster generators for complex multi-environment scenarios
- Implement progressive sync strategies for safe production rollouts
- Leverage multi-tenancy patterns for organizational isolation
- Monitor ApplicationSet performance and application health
- Follow security best practices for enterprise deployments
With proper implementation of these patterns, teams can manage thousands of applications across hundreds of clusters while maintaining consistency, security, and operational excellence.