Automating Canary Analysis with Prometheus Metrics
The gate that makes a canary release safe is only as good as its metric queries — this page shows how to write Prometheus-backed analysis that catches real regressions without false alarms.
When to use this pattern
- You run canary rollouts (for example with Argo Rollouts) and want promotion decisions driven by data.
- You have Prometheus scraping request metrics labelled by version.
- You need to distinguish a real regression from normal baseline noise.
Prerequisites
Complete working example
# analysis-template.yaml — error-rate (baseline + floor) and p95 latency
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: { name: canary-health }
spec:
args:
- name: app
value: web
metrics:
# 1. Primary gate: canary error rate relative to the stable baseline.
- name: error-rate-ratio
interval: 1m
count: 5 # five samples across the pause window
failureLimit: 1 # one breach aborts
successCondition: result < 2.0 # canary no worse than 2x baseline
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
(
sum(rate(http_requests_total{app="{{args.app}}",version="canary",code=~"5.."}[2m]))
/ clamp_min(sum(rate(http_requests_total{app="{{args.app}}",version="canary"}[2m])), 0.001)
)
/
clamp_min(
sum(rate(http_requests_total{app="{{args.app}}",version="stable",code=~"5.."}[2m]))
/ clamp_min(sum(rate(http_requests_total{app="{{args.app}}",version="stable"}[2m])), 0.001)
, 0.0001)
# 2. Backstop: absolute error-rate floor, independent of baseline.
- name: error-rate-floor
interval: 1m
count: 5
failureLimit: 1
successCondition: result < 0.05 # abort if canary 5xx exceeds 5% outright
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{app="{{args.app}}",version="canary",code=~"5.."}[2m]))
/ clamp_min(sum(rate(http_requests_total{app="{{args.app}}",version="canary"}[2m])), 0.001)
# 3. Performance gate: p95 latency in seconds.
- name: p95-latency
interval: 1m
count: 5
failureLimit: 2
successCondition: result < 0.4 # p95 under 400ms
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket{app="{{args.app}}",version="canary"}[2m])) by (le)
)Step-by-step walkthrough
Metric 1 — error-rate ratio (primary gate). The query computes the canary’s 5xx ratio and divides it by the stable version’s 5xx ratio. successCondition: result < 2.0 passes as long as the canary is no more than twice as error-prone as the live baseline. Comparing to the baseline — not to zero — means a normal 0.5% error rate does not fail the gate. clamp_min guards against divide-by-zero when a series is momentarily empty.
Metric 2 — absolute floor (backstop). A pure ratio has a blind spot: if a shared dependency outage pushes both versions to 10% errors, the ratio stays near 1.0 and the canary passes while production burns. The floor query aborts whenever the canary’s absolute 5xx rate exceeds 5%, regardless of baseline.
Metric 3 — p95 latency. Error rate misses slow-but-successful regressions. histogram_quantile(0.95, …) reads p95 from the latency histogram; failureLimit: 2 tolerates one noisy spike but aborts on a sustained one.
Sample sizing. interval: 1m × count: 5 gives a five-minute analysis with five decision points. The [2m] range in each query smooths per-scrape noise. On a low-traffic service, widen interval and the range so each sample still covers hundreds of requests — sizing by request volume, not clock time, is what keeps the gate trustworthy.
Verification
Test every query in the Prometheus UI before binding it, then dry-run the analysis:
# Paste each query into http://prometheus:9090/graph and confirm it returns a value
# Then run the template standalone against a live canary:
kubectl argo rollouts get analysisrun --rollout app
# Force a regression check: deploy a version that returns 500s to the canary and
# confirm the AnalysisRun transitions to Failed within one interval.Expected: a healthy canary shows all three metrics ✔; an injected 500-heavy build flips error-rate-floor to ✖ Failed and aborts the rollout.
Common pitfalls
- Ratio without a floor. As above, a baseline-relative gate alone misses shared outages. Always pair it with an absolute floor.
- Querying an instant vector where a range is needed.
rate()requires a range selector ([2m]); using an instant selector returns no data and the run fails spuriously. failureLimit: 0on a noisy metric. Zero tolerance turns a single scrape blip into an abort. Allow one or two breaches on latency, and rely oncountfor confidence rather than hair-trigger limits.
Related
- Canary Releases & Progressive Rollouts — the parent guide with the traffic-laddering mechanics.
- Progressive Canary Rollouts with Argo Rollouts — the rollout spec this analysis plugs into.
- Wiring Rollback to Error-Rate and Latency Thresholds — the same thresholds applied to post-deploy rollback.
- Deployment Strategies & Progressive Delivery — the strategy overview.
← Back to Canary Releases & Progressive Rollouts