ArgoCD Traffic Management
Supported Versions: Argo Rollouts v1.6+, ArgoCD v2.9+ Last Updated: July 15, 2026
Table of Contents
- Argo Rollouts Overview
- Installation
- Blue-Green Deployments
- Canary Deployments
- Analysis and Verification
- Ingress Integration
- Rollback Strategies
- Experiments
- Notifications
Argo Rollouts Overview
Argo Rollouts is a Kubernetes controller that provides advanced deployment capabilities including blue-green deployments, canary deployments, and progressive delivery features.
Why Argo Rollouts?
Standard Kubernetes Deployments only support rolling updates. Argo Rollouts extends this with:
| Feature | K8s Deployment | Argo Rollouts |
|---|---|---|
| Rolling Update | Yes | Yes |
| Blue-Green | No | Yes |
| Canary | No | Yes |
| Traffic Splitting | No | Yes |
| Automated Rollback | No | Yes |
| Analysis/Verification | No | Yes |
| Pause/Resume | No | Yes |
| Experiments | No | Yes |
Architecture
Installation
Install Argo Rollouts Controller
# Create namespace
kubectl create namespace argo-rollouts
# Install controller
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
# Verify installation
kubectl get pods -n argo-rolloutsInstall kubectl Plugin
# macOS
brew install argoproj/tap/kubectl-argo-rollouts
# Linux
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
# Verify
kubectl argo rollouts versionInstall via Helm
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argo-rollouts argo/argo-rollouts \
--namespace argo-rollouts \
--create-namespace \
--set dashboard.enabled=trueHelm Values for Production
controller:
replicas: 2
metrics:
enabled: true
serviceMonitor:
enabled: true
dashboard:
enabled: true
ingress:
enabled: true
ingressClassName: nginx
hosts:
- rollouts.example.comBlue-Green Deployments
Blue-green deployment maintains two identical environments and switches traffic between them.
Basic Blue-Green Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
namespace: myapp
spec:
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myregistry/myapp:v1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
strategy:
blueGreen:
activeService: myapp-active
previewService: myapp-preview
autoPromotionEnabled: false
scaleDownDelaySeconds: 30
previewReplicaCount: 2
prePromotionAnalysis:
templates:
- templateName: smoke-tests
args:
- name: service-name
value: myapp-preview
postPromotionAnalysis:
templates:
- templateName: success-rate
---
apiVersion: v1
kind: Service
metadata:
name: myapp-active
namespace: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp-preview
namespace: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080Blue-Green Flow
Blue-Green with Auto-Promotion
strategy:
blueGreen:
activeService: myapp-active
previewService: myapp-preview
autoPromotionEnabled: true
autoPromotionSeconds: 60 # Wait 60s before auto-promoting
previewReplicaCount: 3Canary Deployments
Canary deployment gradually shifts traffic to the new version.
Basic Canary Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp-canary
namespace: myapp
spec:
replicas: 10
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myregistry/myapp:v1.0.0
ports:
- containerPort: 8080
strategy:
canary:
canaryService: myapp-canary
stableService: myapp-stable
trafficRouting:
nginx:
stableIngress: myapp-ingress
steps:
# Step 1: 5% traffic to canary
- setWeight: 5
- pause: {duration: 2m}
# Step 2: 10% traffic, run analysis
- setWeight: 10
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: myapp-canary
# Step 3: 25% traffic
- setWeight: 25
- pause: {duration: 5m}
# Step 4: 50% traffic
- setWeight: 50
- pause: {duration: 5m}
# Step 5: 75% traffic
- setWeight: 75
- analysis:
templates:
- templateName: success-rate
- templateName: latency-check
# Step 6: 100% traffic (full promotion)
- setWeight: 100
---
apiVersion: v1
kind: Service
metadata:
name: myapp-stable
namespace: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp-canary
namespace: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080Canary Steps Explained
| Step Type | Description |
|---|---|
setWeight | Set traffic percentage to canary |
pause | Wait for duration or manual approval |
analysis | Run AnalysisTemplate |
setCanaryScale | Set canary replica count |
setHeaderRoute | Route by header (for traffic routers) |
Canary with Manual Gates
strategy:
canary:
steps:
- setWeight: 10
- pause: {} # Indefinite pause - requires manual promotion
- setWeight: 50
- pause: {duration: 10m}
- setWeight: 100Promote manually:
# Promote to next step
kubectl argo rollouts promote myapp-canary
# Promote fully (skip remaining steps)
kubectl argo rollouts promote myapp-canary --fullCanary Traffic Flow
Analysis and Verification
AnalysisTemplates define how to verify deployment health.
Prometheus Analysis
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
namespace: myapp
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
count: 5
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(
http_requests_total{
service="{{args.service-name}}",
status=~"2.."
}[5m]
)) /
sum(rate(
http_requests_total{
service="{{args.service-name}}"
}[5m]
))Latency Analysis
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: latency-check
namespace: myapp
spec:
args:
- name: service-name
metrics:
- name: p99-latency
interval: 2m
count: 3
successCondition: result[0] < 500 # 500ms threshold
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
histogram_quantile(0.99,
sum(rate(
http_request_duration_seconds_bucket{
service="{{args.service-name}}"
}[5m]
)) by (le)
) * 1000Web Analysis (HTTP Endpoint)
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: smoke-tests
namespace: myapp
spec:
args:
- name: service-name
metrics:
- name: smoke-test
interval: 30s
count: 3
successCondition: result.status == "healthy"
failureLimit: 1
provider:
web:
url: "http://{{args.service-name}}/health"
jsonPath: "{$.status}"
timeoutSeconds: 10Datadog Analysis
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: datadog-success-rate
namespace: myapp
spec:
args:
- name: service-name
metrics:
- name: error-rate
interval: 5m
count: 3
successCondition: result < 0.05
failureLimit: 2
provider:
datadog:
apiVersion: v2
interval: 5m
query: |
sum:http.requests{service:{{args.service-name}},status:5xx}.as_count() /
sum:http.requests{service:{{args.service-name}}}.as_count()Job-Based Analysis
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: integration-tests
namespace: myapp
spec:
args:
- name: service-url
metrics:
- name: integration-tests
provider:
job:
spec:
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: test-runner
image: myregistry/integration-tests:latest
env:
- name: TARGET_URL
value: "{{args.service-url}}"
command:
- /bin/sh
- -c
- |
npm run test:integration
if [ $? -eq 0 ]; then
exit 0
else
exit 1
fiClusterAnalysisTemplate
Share analysis templates across namespaces:
apiVersion: argoproj.io/v1alpha1
kind: ClusterAnalysisTemplate
metadata:
name: global-success-rate
spec:
args:
- name: service-name
- name: namespace
metrics:
- name: success-rate
interval: 1m
count: 5
successCondition: result[0] >= 0.95
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(
http_requests_total{
namespace="{{args.namespace}}",
service="{{args.service-name}}",
status=~"2.."
}[5m]
)) /
sum(rate(
http_requests_total{
namespace="{{args.namespace}}",
service="{{args.service-name}}"
}[5m]
))Ingress Integration
Argo Rollouts supports more than 10 traffic providers. Providers with no native integration, such as Kong, are supported through the Gateway API plugin instead.
| Provider | Integration | Notes |
|---|---|---|
| NGINX Ingress | Native (trafficRouting.nginx) | Manipulates the canary-weight annotation directly |
| AWS ALB | Native (trafficRouting.alb) | The Ingress backend port must be use-annotation — see verification results |
| Istio | Native (trafficRouting.istio) | Manipulates the VirtualService/DestinationRule directly |
| SMI | Native (trafficRouting.smi) | The SMI project itself is effectively unmaintained — not recommended for new adoption |
| Ambassador, Apache APISIX, Traefik, Google Cloud | Native | Not covered in this document — see the official docs |
| Kong and other Gateway API-compliant implementations (kgateway, etc.) | Gateway API plugin (trafficRouting.plugins) | There is no native trafficRouting.kong field |
NGINX Ingress
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
namespace: myapp
spec:
strategy:
canary:
stableService: myapp-stable
canaryService: myapp-canary
trafficRouting:
nginx:
stableIngress: myapp-ingress
additionalIngressAnnotations:
canary-by-header: X-Canary
canary-by-header-value: "true"
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 100
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: myapp
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-stable
port:
number: 80AWS ALB Ingress
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
namespace: myapp
spec:
strategy:
canary:
stableService: myapp-stable
canaryService: myapp-canary
trafficRouting:
alb:
ingress: myapp-ingress
rootService: myapp-root
servicePort: 80
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 100
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: myapp
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/actions.myapp-root: |
{
"type": "forward",
"forwardConfig": {
"targetGroups": [
{
"serviceName": "myapp-stable",
"servicePort": 80,
"weight": 100
},
{
"serviceName": "myapp-canary",
"servicePort": 80,
"weight": 0
}
]
}
}
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-root
port:
name: use-annotation⚠️ Verified in testing: If the Ingress backend port is accidentally set to a real port number (e.g.
number: 80) instead ofname: use-annotation, the AWS Load Balancer Controller silently ignores thealb.ingress.kubernetes.io/actions.*annotation — no error, no warning. It keeps a plain single-target-group rule instead of the weighted forward rule, sokubectl get rolloutshowsSetWeightclimbing normally while the real ALB traffic never actually shifts. Always cross-check the live listener rule'sForwardConfig.TargetGroupsweights withaws elbv2 describe-rules.
Istio Traffic Splitting
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
namespace: myapp
spec:
strategy:
canary:
stableService: myapp-stable
canaryService: myapp-canary
trafficRouting:
istio:
virtualService:
name: myapp-vsvc
routes:
- primary
destinationRule:
name: myapp-destrule
canarySubsetName: canary
stableSubsetName: stable
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 100
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp-vsvc
namespace: myapp
spec:
hosts:
- myapp.example.com
gateways:
- myapp-gateway
http:
- name: primary
route:
- destination:
host: myapp-stable
weight: 100
- destination:
host: myapp-canary
weight: 0
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: myapp-destrule
namespace: myapp
spec:
host: myapp
subsets:
- name: stable
labels:
app: myapp
- name: canary
labels:
app: myappGateway API Plugin (Universal)
Gateway API-compliant implementations with no native Argo Rollouts integration — Kong, Traefik, kgateway, and others — are supported through the Gateway API plugin maintained by argoproj-labs. The plugin manipulates the standard HTTPRoute's backendRefs[].weight field directly, so it applies identically to any controller that implements Gateway API. It also supports TLSRoute and header-based routing; the latest release as of 2026 is v0.16.0.
Install the plugin by registering it in the argo-rollouts-config ConfigMap so the controller downloads the binary on startup:
apiVersion: v1
kind: ConfigMap
metadata:
name: argo-rollouts-config
namespace: argo-rollouts
data:
trafficRouterPlugins: |-
- name: "argoproj-labs/gatewayAPI"
location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.16.0/gatewayapi-plugin-linux-amd64"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argo-rollouts-gateway-api-plugin
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes", "grpcroutes", "tcproutes", "tlsroutes"]
verbs: ["get", "list", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argo-rollouts-gateway-api-plugin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argo-rollouts-gateway-api-plugin
subjects:
- kind: ServiceAccount
name: argo-rollouts
namespace: argo-rolloutsThe Rollout references the HTTPRoute through trafficRouting.plugins:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
namespace: myapp
spec:
replicas: 5
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: myapp:v2.0.0
ports:
- containerPort: 8080
strategy:
canary:
stableService: myapp-stable
canaryService: myapp-canary
trafficRouting:
plugins:
argoproj-labs/gatewayAPI:
httpRoute: myapp-route
namespace: myapp
steps:
- setWeight: 20
- pause: {duration: 1m}
- setWeight: 50
- pause: {duration: 1m}
- setWeight: 100
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp-route
namespace: myapp
spec:
parentRefs:
- name: myapp-gateway
rules:
- backendRefs:
- name: myapp-stable
kind: Service
port: 80
weight: 100
- name: myapp-canary
kind: Service
port: 80
weight: 0At each setWeight step, the plugin updates these two backendRefs[].weight values directly.
Kong (via the Gateway API Plugin)
The Kong Ingress Controller (KIC) has no native Argo Rollouts integration — it uses the Gateway API plugin above. After installing KIC in Gateway API mode, the GatewayClass must be marked as an unmanaged gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: kong
annotations:
konghq.com/gatewayclass-unmanaged: "true" # required — without it the Gateway stays stuck on "Waiting for controller"
spec:
controllerName: konghq.com/kic-gateway-controller # note: different from KIC's IngressClass controller stringFrom here, apply the same Gateway API plugin configuration as above — the Rollout and HTTPRoute YAML are identical.
Verification Results on EKS
We validated all four providers in isolated test namespaces on an EKS 1.36 cluster (Argo Rollouts v1.9.0, AWS Load Balancer Controller v3.2.1, Istio 1.30, Kong Ingress Controller 3.5 + Gateway API plugin v0.16.0). All test resources (namespaces, Helm releases, the ALB, the GatewayClass) were torn down after verification.
| Provider | What was checked | Result |
|---|---|---|
| NGINX | canary-weight annotation transitioning 20→50→100% | ✅ Confirmed — live curl traffic ratio matched the annotation value |
| Istio | VirtualService weight transitioning 20→50→100%, and immediate revert to 0% on abort | ✅ Confirmed — curl ratio matched the weight, and traffic snapped back to the previous stable version right after abort |
| AWS ALB | Listener rule forward weight transition, cross-checked against the live AWS state with aws elbv2 describe-rules | ✅ Confirmed (but requires the use-annotation caveat above) |
| Kong (Gateway API plugin) | HTTPRoute.backendRefs[].weight transition, and real traffic through Kong's data plane | ✅ Confirmed — though the gatewayclass-unmanaged annotation and exact controllerName are easy to get wrong (see above) |
Rollback Strategies
Automatic Rollback on Analysis Failure
strategy:
canary:
steps:
- setWeight: 10
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: myapp-canary
# Analysis failure automatically triggers rollbackManual Rollback
# Abort current rollout and rollback
kubectl argo rollouts abort myapp
# Undo to previous version
kubectl argo rollouts undo myapp
# Undo to specific revision
kubectl argo rollouts undo myapp --to-revision=2Rollback Configuration
spec:
strategy:
canary:
abortScaleDownDelaySeconds: 30
dynamicStableScale: true
steps:
- setWeight: 10
- analysis:
templates:
- templateName: success-rate
# Analysis runs continuously
# Failure at any point triggers rollbackExperiments
Run A/B tests with multiple versions simultaneously.
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
name: myapp-experiment
namespace: myapp
spec:
duration: 1h
progressDeadlineSeconds: 300
templates:
- name: baseline
replicas: 2
selector:
matchLabels:
app: myapp
variant: baseline
template:
metadata:
labels:
app: myapp
variant: baseline
spec:
containers:
- name: myapp
image: myregistry/myapp:v1.0.0
ports:
- containerPort: 8080
- name: canary
replicas: 2
selector:
matchLabels:
app: myapp
variant: canary
template:
metadata:
labels:
app: myapp
variant: canary
spec:
containers:
- name: myapp
image: myregistry/myapp:v2.0.0
ports:
- containerPort: 8080
analyses:
- name: compare-metrics
templateName: compare-experiment
args:
- name: baseline-hash
valueFrom:
podTemplateHashValue: baseline
- name: canary-hash
valueFrom:
podTemplateHashValue: canaryNotifications
Integrate rollout events with notification systems.
Configure Notifications in Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
namespace: myapp
annotations:
notifications.argoproj.io/subscribe.on-rollout-completed.slack: deployments
notifications.argoproj.io/subscribe.on-rollout-aborted.slack: deployments
notifications.argoproj.io/subscribe.on-analysis-run-failed.slack: alerts
spec:
# ...Notification Triggers and Templates
apiVersion: v1
kind: ConfigMap
metadata:
name: argo-rollouts-notification-configmap
namespace: argo-rollouts
data:
service.slack: |
token: $slack-token
trigger.on-rollout-completed: |
- when: rollout.status.phase == 'Healthy'
send: [rollout-completed]
trigger.on-rollout-aborted: |
- when: rollout.status.phase == 'Degraded'
send: [rollout-aborted]
template.rollout-completed: |
message: |
Rollout {{.rollout.metadata.name}} completed successfully!
Revision: {{.rollout.status.currentPodHash}}
Image: {{(index .rollout.spec.template.spec.containers 0).image}}
template.rollout-aborted: |
message: |
Rollout {{.rollout.metadata.name}} was aborted!
Reason: {{.rollout.status.message}}Quiz
To test what you've learned, try the ArgoCD traffic management quiz.