Git, CI/CD & GitOpsCXIII · Observability IntegrationErrorRate
Error rate and deploys — the deploy-to-error graph
What you'll learn
- Decompose the error rate by the service.version label to separate new-code errors from old-code errors
- Read a deploy-to-error graph where each deploy is a column and the post-deploy error rate is the row
- Calibrate error-rate alert thresholds to the deploy frequency of the service so high-frequency deploys do not page on every deploy
- Recognise the difference between a deploy-caused error spike (concentrated on the new version) and a deploy-unrelated spike (spread across versions)
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The error rate panel shows the production error rate over time. The deploy marker shows when each release reached production. The join between the two is the deploy-to-error graph: a panel where every deploy is visible, and the error rate attributable to that deploy is queryable separately. The graph answers the question “is each deploy making the service better, worse, or unchanged?”.
Decomposing the error rate by version
The aggregate error rate - the rate of 5xx responses across all traffic - is the wrong primitive for deploy-caused error analysis. The aggregate hides which version produced the error. A service with 100 deploys in production over the last month will have a mix of versions running; the aggregate error rate is a weighted average across all of them.
The decomposition by service.version separates the
versions:
NAME=api
SHA=$(git rev-parse --short HEAD)
MESSAGE="deploy $SHA release v3.4.1"
kubectl annotate deploy $NAME \
kubernetes.io/change-cause="$MESSAGE" --overwrite
# error rate for the new version
# = rate(http_requests_total{service="$NAME",version=~"$SHA",code=~"5.."}[5m])
# / rate(http_requests_total{service="$NAME",version=~"$SHA"}[5m])
The query returns the error rate for the specific version that was deployed. The aggregate query returns the same metric for all versions; the comparison is the difference. A deploy-caused error spike concentrates on the new version; the old version’s error rate stays at baseline.
flowchart TB
A["Deploy marker\n14:21"] --> B["New version\nservice.version=$SHA"]
A --> C["Old versions\nprevious releases"]
B --> D["New version\nerror rate"]
C --> E["Old versions\nerror rate"]
D --> F{"New version\nspike?"}
E --> G{"Old version\nspike?"}
F -->|Yes| H["Deploy-caused\nerror spike"]
F -->|No| I["Background\nnoise"]
G -->|Yes| J["Unrelated\nshared dependency"]
The decomposition is the primitive that distinguishes “this deploy broke the service” from “the service is breaking anyway and the deploy is correlated by timestamp”.
The deploy-to-error graph
The graph is the panel that joins every deploy marker to its post-deploy error rate. The horizontal axis is time; the vertical axis is the error rate; each deploy appears as a vertical line; the post-deploy error rate is the colour or the line weight.
The graph reveals the structural pattern. A team that ships deploys whose post-deploy error rate is consistently low has a graph that is mostly green. A team that ships deploys whose post-deploy error rate spikes has a graph that is mostly red. The graph is the on-call interface for the question “is each deploy making the service better?”.
flowchart LR
A["Deploy 1\n10:00"] --> B["Post-deploy\nerror rate 0.1%"]
C["Deploy 2\n11:30"] --> D["Post-deploy\nerror rate 0.4%"]
E["Deploy 3\n13:00"] --> F["Post-deploy\nerror rate 5.0%"]
D --> G["Trend\nincreasing"]
F --> G
G --> H["Deploy-caused\nregression cluster"]
The graph is also the input to the change failure rate metric from the DORA lesson. Each spike in the graph contributes to the change failure rate; each stable deploy contributes to the deploy frequency. The two metrics share the same data source.
The structure of a deploy-caused error spike
Three structural properties distinguish a deploy-caused error spike from background noise:
- Version concentration. The new version’s error rate spikes; the old version’s error rate stays at baseline. The spike is concentrated on the deploy that introduced it.
- Timing. The spike appears within minutes of the deploy marker. The timing is consistent with the rollout speed; a slow rollout produces a slow rise, a fast rollout produces a fast rise.
- Persistence. The spike persists across the observation window. A transient spike is a transient; a deploy-caused spike is sustained.
A spike that satisfies all three is a deploy-caused spike. A spike that satisfies one is a candidate; a spike that satisfies none is background noise.
Production discipline
- Set
service.versionfrom the pipeline, not manually. The label is a property of the deploy, not of the operator’s memory. - Decompose the error rate by version in the dashboard. The aggregate hides the deploy-caused signal; the decomposition exposes it.
- Calibrate alert thresholds per service, per deploy frequency. A single threshold across services produces alerts that over-page or under-page.
- Surface the deploy-to-error graph on the on-call dashboard. The graph is the structural answer to “is each deploy making the service better?”.
- Treat a persistent version-concentrated spike as a deploy-caused error spike. The version concentration is the strongest signal.
Cross-course references
- Observability course - Part LII (Exemplars) covers the version label that ties the metric to the deploy.
- Observability course - Part CX (MajorIncidents) covers the annotation surfaces that complement the version decomposition.
- This course, Part LXIII-05 (ChangeFailureRate) covers the DORA stability metric that the deploy-to-error graph feeds.
- This course, Part LX (Decision) covers the rollback decision that follows from a confirmed deploy-caused error spike.
Quiz
Knowledge check · 4 questions
Q1. The aggregate error rate spikes at 14:23. Decomposing by service.version shows the new version at 8% error rate, the old versions at 0.1%. What does this say?
Q2. An error-rate alert threshold should be the same across all services regardless of deploy frequency.
Q3. Name the three structural properties that distinguish a deploy-caused error spike from background noise.
Q4. Diagnose whether the error spike is deploy-caused and recommend the next action.
Team T deploys the api service at 14:21. The deploy annotation is written correctly. The service.version label is set by the pipeline from the commit SHA. At 14:23 the aggregate error rate goes from 0.2% to 2.5%. Decomposing by version: the new version (7a3f9d2) shows 8% error rate; the previous version (5b2e1a0) shows 0.2% error rate - unchanged from baseline. The spike persists across the 30-minute observation window. The team deploys 5 times per day; the alert threshold is 1% for 5 minutes.
Passing score: 75%. Answers are checked in this browser.