Our first canary process was a person watching Grafana for 15 minutes while 10 percent of traffic went to the new version. It caught big failures and missed everything else: a 3 percent error increase on one endpoint, a memory leak that took an hour to matter, and a latency regression that only showed at peak. Worse, the person was often also answering Slack.
Automated canary analysis replaces the watcher with queries and thresholds. Tools like Argo Rollouts and Flagger implement the mechanics on Kubernetes: shift traffic in steps, run analysis queries at each step, promote or roll back. This guide is about the part the tools cannot do for you: choosing what to measure and how to judge it.
The shape of a canary
A typical progression:
strategy:
canary:
steps:
- setWeight: 5
- pause: {duration: 10m}
- analysis: {templates: [{templateName: http-health}]}
- setWeight: 25
- pause: {duration: 15m}
- analysis: {templates: [{templateName: http-health}]}
- setWeight: 50
- pause: {duration: 15m}
- analysis: {templates: [{templateName: http-health}]}
Steps limit blast radius; pauses collect enough data for analysis. With background analysis running continuously, a failure at any time aborts the rollout and sends traffic back to the stable version.
What to measure
Gate on signals that represent user experience and are attributable to the canary:
- Error rate of the canary's requests (5xx, plus application-level failures that return 200 with an error body if your API does that).
- Latency, at p95 or p99, not average.
- Saturation of the canary pods: memory growth, restarts, CPU throttling.
- Business signals where possible: checkout completion, login success, messages sent. These catch bugs that return fast, clean 200s with wrong results.
Each metric must be labelled so you can separate canary from stable. In Prometheus terms, the canary pods need a distinguishing label (Argo Rollouts adds a pod template hash; we also add track="canary").
Compare to the baseline, not to a fixed number
A fixed threshold like "error rate below 1 percent" fails in two directions: it passes a canary that doubled errors from 0.2 to 0.4 percent, and it fails a canary during an upstream incident that affects both versions equally. Compare canary with stable over the same window:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: {name: http-health}
spec:
args:
- name: service
metrics:
- name: error-rate-vs-stable
interval: 2m
count: 5
failureLimit: 1
successCondition: result[0] <= 1.5
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
(
sum(rate(http_requests_total{service="{{args.service}}",track="canary",code=~"5.."}[5m]))
/ sum(rate(http_requests_total{service="{{args.service}}",track="canary"}[5m]))
)
/
clamp_min(
sum(rate(http_requests_total{service="{{args.service}}",track="stable",code=~"5.."}[5m]))
/ sum(rate(http_requests_total{service="{{args.service}}",track="stable"}[5m])),
0.001
)
The ratio must stay under 1.5. clamp_min keeps the division meaningful when stable has almost no errors. We pair the ratio with an absolute ceiling (canary error rate never above 2 percent) so a canary cannot pass just because stable is also failing badly.
Durations and the low-traffic problem
Analysis needs data. A service with 2 requests per second sends about 6 requests per minute to a 5 percent canary, and one error is a 17 percent error rate. Options:
- Higher initial weight for low-traffic services (25 percent) and longer pauses.
- Minimum sample size in the query: treat the result as inconclusive until the canary has served at least N requests, and keep waiting rather than passing.
- Synthetic traffic against the canary for critical paths that real users rarely hit.
- Longer total duration for leak-type problems: memory growth often needs 30 to 60 minutes to show.
Inconclusive is a valid outcome. Argo Rollouts supports it: the rollout pauses for a human instead of guessing.
Abort and rollback
Make abort fast and boring:
- Rolling back is shifting traffic weight to stable, which is near-instant with a service mesh or traffic-splitting ingress.
- Database migrations must be backward compatible (expand and contract), or rollback is not really possible. Canary analysis is worthless if the old version cannot run against the new schema.
- After an abort, the rollout stays degraded until someone looks; we post the failing metric and the query result to the team channel automatically.
Traps
- Sticky sessions or caching can route almost no real traffic to the canary even at 25 percent weight. Check actual request counts, not configured weights.
- Shared dependencies mean a canary can break things outside its own metrics, such as writing bad data to a shared cache. Watch downstream error rates too.
- Metric scrape intervals longer than analysis intervals produce repeated stale values.
- Too many metrics with tight thresholds cause random failures and teach the team to override. Start with three or four.
Rolling it out to a team
Start with one service that deploys often and has clean metrics. Run analysis in dry-run mode for two weeks: record what it would have decided without acting on it, and compare with what humans decided. Tune thresholds on that data, then switch to enforcing. Teams trust automated rollbacks much more when they have seen the analysis agree with them first.
Common mistakes
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Fixed absolute thresholds only | Misses relative regressions, fails on shared incidents | Compare canary to stable, plus an absolute ceiling |
| Short pauses on low-traffic services | Decisions on noise | Minimum sample sizes, longer pauses, synthetic traffic |
| Non-backward-compatible migrations | Rollback impossible | Expand and contract schema changes |
| Measuring only HTTP status | Wrong results pass as 200 | Add business success metrics |
| Trusting configured weight | Sticky routing starves canary | Verify per-track request counts |
Key takeaways
- Automate the judgment: queries and thresholds, not a person watching dashboards.
- Gate on errors, latency, saturation and at least one business signal.
- Compare canary to stable over the same window, with absolute ceilings.
- Handle low traffic explicitly; inconclusive should pause, not pass.
- Make rollback fast and keep migrations backward compatible.
References
- Argo Rollouts canary strategy — https://argoproj.github.io/argo-rollouts/features/canary/
- Argo Rollouts analysis and progressive delivery — https://argoproj.github.io/argo-rollouts/features/analysis/
- Flagger — https://docs.flagger.app/
- Google SRE workbook, canarying releases — https://sre.google/workbook/canarying-releases/
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.