🚨 Alerting Systems
Collecting metrics and logs is only half the job. If nobody is watching the dashboards at 3 a.m., you're still relying on angry users to tell you the site is down. Alerting closes the loop — it watches for you and pages a human when something genuinely needs attention. Done well, it catches problems early. Done badly, it trains your team to ignore it.
Week 13 · Day 4 (Thursday: Monitoring and Logging) · Lecture 3
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain why alerting on symptoms and SLOs beats alerting on causes
- Write Prometheus alert rules with a threshold and a
forduration window - Route, group, and inhibit alerts with Alertmanager to cut noise
- Choose the right notification channel for each severity level
- Design an on-call escalation path that never leaves an alert unanswered
- Recognise and prevent alert fatigue so every page stays trustworthy
Estimated Time: 75 minutes
Practice: Write a symptom-based alert rule and an Alertmanager route that escalates critical alerts to a pager.
In This Lesson
Why Alert at All?
An alerting system is the smoke detector for your application. It sits quietly and watches your metrics and logs; when the pattern of "trouble" appears, it sounds the alarm — ideally before anyone smells smoke. The goal isn't to notify you of everything that happens. It's to notify you of the few things that need a human decision, right now.
for long enough?"} D -->|"No"| E["Stay quiet"] D -->|"Yes"| F["Fire Alert"] F --> G["Route & Group"] G --> H["Notify On-Call"] H --> I["Acknowledge & Fix"] H -->|"No response"| J["Escalate"]
Notice the diamond: a good alert doesn't fire the instant a number crosses a line. It waits to confirm the problem is real and sustained. That single idea — patience before paging — is what separates a trusted alerting system from a noisy one.
Alert on Symptoms & SLOs
Here's the most important principle in this lesson: alert on symptoms your users feel, not on internal causes.
A doctor doesn't wire a patient to alarms for every internal fluctuation — a slightly elevated white-cell count doesn't sound a siren. The alarms are for symptoms that matter: the heart stops, breathing fails. Your system is the same. High CPU is a cause; it might mean trouble, or it might just be a busy afternoon doing useful work. Users don't feel CPU. They feel slow responses and errors.
SLOs make "how bad is too bad" concrete
An SLO (Service Level Objective) is a target you commit to, like "99.9% of requests succeed over 30 days." That budget of allowed failure — the error budget — turns vague worry into math. Alert when you're burning through the budget fast enough to miss the target, and you alert on exactly what matters, no sooner.
💡 The dashboard-vs-page distinction
If a signal requires a human to act now, it's an alert (a page). If it's useful context but nobody needs to wake up, it's a metric on a dashboard. Most things are dashboards. Be stingy with pages.
Writing Alert Rules
In the Prometheus world, alerts are just queries with a threshold and a duration. The for clause is the "patience" from our diagram — the condition must hold continuously before the alert fires, which filters out momentary blips.
# alert_rules.yml
groups:
- name: service_slos
rules:
# SYMPTOM: users are getting errors. Fire if 5xx rate exceeds 5%
# of traffic, sustained for 5 minutes (not a one-second spike).
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "Error rate above 5% on {{ $labels.service }}"
description: "Current error rate: {{ $value | humanizePercentage }}"
runbook: "https://wiki.example.com/runbooks/high-error-rate"
# SYMPTOM: the site is slow. Fire if 95th-percentile latency
# stays above 1s for 10 minutes.
- alert: HighLatency
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 1
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "p95 latency above 1s"
description: "p95 is {{ $value }}s over the last 10 minutes"
✅ Every good alert rule has four things
- A symptom-based expression (errors, latency) — not a raw cause
- A
forwindow so transient blips don't page anyone - A severity label to drive routing
- An actionable annotation with a runbook link — the responder knows what to do
Routing, Grouping & Inhibition
Once an alert fires, Alertmanager decides who hears about it and how. Three features do the heavy lifting of noise reduction.
Routing — send it to the right team
Match on labels to direct alerts. Critical ones go to the pager; everything else goes to chat.
# alertmanager.yml
route:
group_by: ['alertname', 'service']
group_wait: 30s # buffer briefly so related alerts arrive together
group_interval: 5m # wait before sending an update for a group
repeat_interval: 4h # re-notify a still-firing alert only every 4h
receiver: 'team-chat' # default
routes:
- matchers: [ severity="critical" ]
receiver: 'pagerduty'
- matchers: [ team="frontend" ]
receiver: 'frontend-chat'
receivers:
- name: 'pagerduty'
pagerduty_configs:
- routing_key: '<your-integration-key>'
- name: 'team-chat'
slack_configs:
- api_url: '<your-slack-webhook>'
channel: '#alerts'
send_resolved: true
Grouping — one notification, not a hundred
If a database dies and forty services all error at once, you want one grouped notification ("40 services failing") rather than forty pages. group_by and group_wait handle this automatically.
Inhibition — silence the redundant
When a whole cluster is down, you don't also need "high latency" warnings for every service on it. Inhibition suppresses the lesser alert while the bigger one is firing:
inhibit_rules:
# If a critical alert is firing, mute the matching warning-level one
- source_matchers: [ severity="critical" ]
target_matchers: [ severity="warning" ]
equal: ['alertname', 'service']
Notification Channels & Severity
Match the intrusiveness of the channel to the urgency of the problem. Waking someone at 3 a.m. for a cosmetic issue is how you lose their trust; emailing a total outage is how you lose customers.
| Severity | Meaning | Response | Channel |
|---|---|---|---|
| P1 Critical | Outage or data-loss risk, all users | Immediately, 24/7 | Phone call / pager push |
| P2 High | Major feature broken or badly degraded | Within ~30 min, 24/7 | Pager / SMS |
| P3 Medium | Minor issue or early warning | Business hours | Chat (Slack/Teams) |
| P4 Low | Non-urgent, trending toward a threshold | Next business day | Ticket / email |
⚠️ If it's not urgent, it's not a page
A page (phone/pager) interrupts a human's life. Reserve it for P1/P2 — things that genuinely can't wait. Push a P3 to chat and file a P4 as a ticket. Every page that turns out to be ignorable erodes the reflex to respond to the next one.
On-Call & Escalation
An alert that fires but is never acknowledged is worse than no alert — it creates a false sense of coverage. An escalation path guarantees that if the first responder doesn't answer, someone else does.
Tools like PagerDuty and Grafana OnCall manage the schedule and the escalation timers. Alertmanager just hands the alert off with a routing key:
receivers:
- name: 'pagerduty'
pagerduty_configs:
- routing_key: '<your-pagerduty-integration-key>'
severity: '{{ .CommonLabels.severity }}'
description: '{{ .CommonAnnotations.summary }}'
💡 Humane on-call
Rotate on-call fairly, keep shifts short, and make handoffs explicit. For global teams, "follow-the-sun" scheduling means nobody is paged at 4 a.m. A well-designed rotation is as much a people problem as a technical one — burned-out engineers respond slower to real emergencies.
Avoiding Alert Fatigue
Remember the boy who cried wolf. A system that pages constantly for non-issues trains its responders to ignore it — and then misses the one alert that mattered. Alert fatigue is a real, studied phenomenon: in hospitals, staff exposed to hundreds of alarms a day respond measurably slower to critical ones. The same applies to engineers.
Measure your alert quality
- Signal-to-noise ratio — what fraction of alerts led to real action? Aim high.
- Alert frequency — how many pages per on-call shift? More than a couple is a smell.
- Time to acknowledge / resolve — rising times can mean people are tuning out.
Cut the noise
- Use a
forwindow so brief blips never page - Group related alerts into a single notification
- Inhibit redundant alerts under a bigger one
- Silence alerts during planned maintenance windows
- Delete alerts that fire repeatedly without ever needing action
✅ Make every alert actionable
The test: when this fires, is there something a human should do? If yes, it's a good alert — attach a runbook. If the honest answer is "just watch it," it belongs on a dashboard, not in someone's pocket at midnight.
Practice & Quiz
🏋️ Exercise 1: A symptom-based alert rule
Goal: Write a Prometheus alert named ServiceDown that fires when a service's success rate — the fraction of non-5xx responses — drops below 95%, sustained for 3 minutes, at critical severity.
💡 Hint
Success rate is 1 - (5xx rate / total rate), or equivalently fire when the 5xx fraction exceeds 0.05. Add for: 3m and a severity: critical label.
✅ Solution
- alert: ServiceDown
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/ sum(rate(http_requests_total[5m])) by (service) > 0.05
for: 3m
labels:
severity: critical
annotations:
summary: "Success rate below 95% on {{ $labels.service }}"
description: "5xx rate is {{ $value | humanizePercentage }}"
🏋️ Exercise 2: Route critical alerts to a pager
Goal: Add an Alertmanager route so that any alert with severity="critical" goes to a pagerduty receiver, while everything else falls through to the default chat receiver.
✅ Solution
route:
receiver: 'team-chat' # default for non-critical
routes:
- matchers: [ severity="critical" ]
receiver: 'pagerduty'
receivers:
- name: 'team-chat'
slack_configs:
- api_url: '<webhook>'
channel: '#alerts'
- name: 'pagerduty'
pagerduty_configs:
- routing_key: '<key>'
🎯 Quick Quiz
Question 1: Which is the better thing to alert on?
Question 2: What does the for: 5m clause on a Prometheus alert do?
Question 3: What is the main purpose of an escalation path?
Best Practices & Pitfalls
✅ Do
- Alert on user-facing symptoms (errors, latency) and SLO burn, not raw causes
- Add a
forwindow so momentary spikes don't page anyone - Attach a runbook link and clear context to every alert
- Match channel intrusiveness to severity — pager for P1, chat for P3
- Define an escalation path so no critical alert goes unanswered
- Review alert quality regularly and delete the noisy ones
❌ Don't
- Page for every threshold cross, including harmless blips
- Alert on causes like CPU when what users feel is latency and errors
- Send non-urgent alerts through intrusive channels
- Fire a hundred alerts for one root cause — group and inhibit instead
- Leave alerts without an owner or a documented response
⚠️ The quietest alerting system is often the best
A team that gets two meaningful pages a week and acts on both has a far healthier system than one drowning in fifty daily alerts it has learned to swipe away. Fewer, better alerts win.
Summary
🎉 Key Takeaways
- Alert on symptoms and SLOs users feel — not internal causes like CPU
- Every rule needs a threshold, a
forwindow, a severity, and an actionable runbook - Use Alertmanager routing, grouping, and inhibition to turn a storm into one clear notification
- Match the channel to the severity — pager for critical, chat for minor
- An escalation path guarantees coverage; ruthless pruning prevents alert fatigue
📚 Additional Resources
- Prometheus — Alertmanager documentation
- Grafana — Alerting documentation
- Google SRE Workbook — Alerting on SLOs
- PagerDuty — Incident response guide
🚀 What's Next?
You've now completed Week 13's monitoring-and-logging arc — you can measure, understand, and be alerted about your system. Next you'll pull it all together in the Production Checklist: the final review of readiness, security, scaling, and reliability before you flip the switch and go live.
🎉 Alerts people actually trust.
Symptom-based, actionable, and quiet by default — that's an alerting system your team will answer at 3 a.m.