Progressive Delivery with Argo Rollouts: Canary & Blue-Green

Photo by Charles J. Sharp via Wikimedia Commons (CC BY-SA 4.0)
A Deployment only offers a rolling update guarded by readiness probes, which confirm a process is up but not that it serves correct responses. A Rollout is a drop-in CRD replacement whose spec is nearly identical but adds a strategy block for canary or blue-green plus metric-driven analysis. That lets you gate promotion on real SLOs instead of just liveness.
You attach an AnalysisTemplate that queries a provider like Prometheus on an interval and evaluates a successCondition. Each failing sample counts against failureLimit; once that limit is crossed the AnalysisRun fails, Rollouts marks the rollout Degraded, aborts progression, and scales the canary weight back to zero. Traffic returns to the last stable ReplicaSet almost instantly with no pipeline step.
Use canary for stateless HTTP services where you can tolerate two versions serving at once and want a small blast radius at low pod cost. Use blue-green when concurrent versions would break something — a non-backward-compatible schema change, for example — and you can afford to run a full second stack during cutover. Both use the same AnalysisTemplate mechanism.
Not strictly. With plain Service objects, Rollouts approximates traffic weight by pod count, so a 20 percent step needs at least five pods to be meaningful. For precise request-level weighting you should integrate an ingress controller or service mesh such as NGINX, Istio, or the Kubernetes Gateway API.
No. An abort rolls back the cluster by shifting traffic to the previous stable ReplicaSet, but the desired image tag committed in Git is still the bad one. With Argo CD a subsequent sync can re-trigger the same failing rollout in a loop, so you must fix or revert the tag in Git after an automated abort.

Photo by Charles J. Sharp via Wikimedia Commons (CC BY-SA 4.0)
Key Takeaway
Argo Rollouts replaces the Kubernetes Deployment object with a Rollout CRD that ships new versions gradually. Canary shifts traffic in weighted steps; blue-green flips all traffic at once after a preview. AnalysisTemplates query Prometheus mid-rollout, and when an SLO like error rate or latency breaks a threshold, the rollout aborts and reverts automatically.
A plain Kubernetes Deployment gives you exactly one knob for safety: the rolling update. It swaps pods a few at a time and watches readiness probes — but a readiness probe only tells you the process is up, not that it is serving correct responses at an acceptable latency. I have shipped a build that passed every probe and still doubled the p95 on a checkout path. The pods were healthy; the users were not.
Progressive delivery closes that gap. Instead of trusting a probe, you expose the new version to a slice of real traffic, measure what it actually does using your real metrics, and only widen the blast radius if the numbers hold. Argo Rollouts is the CNCF tool I reach for to do this on Kubernetes. The Argo project graduated at the CNCF in December 2022, and Rollouts is now on the 1.8 line — mature enough to run in production without surprises.
You do not add Argo Rollouts alongside a Deployment — you convert the Deployment into a Rollout. The spec is almost identical: same pod template, same selectors, same replica count. The difference is the strategy block, where you declare either canary or blueGreen instead of the default rolling update. Everything else your team already knows about pod specs carries over unchanged, which is what makes the migration cheap.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: checkout-api
spec:
replicas: 6
selector:
matchLabels:
app: checkout-api
template:
metadata:
labels:
app: checkout-api
spec:
containers:
- name: checkout-api
image: registry.example.com/checkout-api:1.4.2
ports:
- containerPort: 8080
strategy:
canary:
steps:
- setWeight: 20
- pause: { duration: 2m }
- setWeight: 50
- pause: { duration: 5m }
- setWeight: 80
- pause: { duration: 5m }Canary is my default for stateless HTTP services. The steps array reads top to bottom: send 20 percent of traffic to the new version, hold for two minutes, then 50, then 80, then Rollouts promotes the canary to 100 automatically once the last step clears. A pause with no duration halts indefinitely until you promote it by hand — useful for a first production run when you want a human to eyeball a dashboard before widening.
Weighted steps only route traffic accurately if something can actually split it. With plain Service objects the split is approximated by pod count, so 20 percent needs at least five pods to be meaningful. For precise weights, wire Rollouts to an ingress controller or service mesh (NGINX, Istio, or the Kubernetes Gateway API) that does true request-level weighting.
Blue-green suits releases that cannot tolerate two versions serving at once — a schema change that is not backward compatible, or a service where mixed responses would corrupt a workflow. Rollouts brings up the full green stack in parallel, exposes it on a separate preview Service so you can smoke-test it, and only redirects the active Service when you (or an analysis) approve. The trade-off is cost: you run double the pods during the cutover, so it is heavier than canary.
strategy:
blueGreen:
activeService: checkout-api-active
previewService: checkout-api-preview
autoPromotionEnabled: false
prePromotionAnalysis:
templates:
- templateName: success-rate
scaleDownDelaySeconds: 30The whole point of progressive delivery is removing the human from the happy path. An AnalysisTemplate is a reusable object that queries a metrics provider on an interval and evaluates a condition. Reference it from a canary step or a blue-green prePromotionAnalysis, and Rollouts runs it as an AnalysisRun tied to that specific rollout. The key fields are interval (how often to sample), count (how many samples), successCondition (the expression that must hold), and failureLimit (how many failing samples are tolerated before the whole analysis fails).
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 30s
count: 6
successCondition: result[0] >= 0.99
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring.svc:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}", code!~"5.."
}[2m]))
/
sum(rate(http_requests_total{
service="{{args.service-name}}"
}[2m]))That template samples the non-5xx success ratio every 30 seconds, six times. successCondition requires at least 99 percent success; failureLimit of 2 means a couple of noisy blips are forgiven, but a third failing sample fails the analysis. I usually run this as a background analysis so it evaluates continuously across every canary step rather than only at one pause — a regression that appears at 80 percent weight should still get caught.
This is the payoff. When an AnalysisRun crosses its failureLimit, Rollouts marks the rollout Degraded, aborts progression, and scales the canary weight back to zero — traffic snaps back to the last known-good version with no pipeline step and no pager. Because a Rollout tracks the previous stable ReplicaSet, the revert is near-instant; you are not rebuilding an image or re-running a deploy, just shifting weight back to pods that were already running.
An abort does not roll your Git state back. In a GitOps setup with Argo CD, the desired image tag in the repo is still the bad one, so the next sync can re-trigger the same failing rollout in a loop. After an automated abort, fix or revert the tag in Git before anything re-syncs — the cluster rolled back, your source of truth did not.
| Concern | Canary | Blue-green |
|---|---|---|
| Traffic shift | Gradual, weighted steps | All at once after preview |
| Extra pod cost | Low — only the canary slice | High — full second stack |
| Blast radius on a bad build | Limited to current weight | Zero pre-flip, full post-flip |
| Needs traffic splitting | Yes for precise weights | No — one service swap |
| Best fit | Stateless HTTP APIs | Non-backward-compatible releases |
In practice I run canary for the vast majority of services and reserve blue-green for the handful where two concurrent versions would genuinely break something. Both strategies use the exact same AnalysisTemplate mechanism, so the investment in good Prometheus queries pays off no matter which one a given service picks. Start with one non-critical service, wire a single success-rate analysis, and prove the automatic abort works by deliberately shipping something bad in staging.