Alerting and rules¶
Play it
Quiet Hours: Write the alert rules, then survive a night on call: page for real incidents, sleep through the noise.
The game links back to this page from: Anatomy of an alerting rule, What makes a good alert, Predictive alerts, Alertmanager routing, briefly.
Prometheus evaluates alerting rules on the same schedule as recording rules
(evaluation_interval), but instead of storing a new metric, a rule whose expression
returns any results becomes a firing alert — which Prometheus pushes to Alertmanager,
a separate process that handles routing, deduplication, and notification.
flowchart LR
Rules["rules.yml\nalerting rules"]
Prom["Prometheus\nevaluates every 15s"]
AM["Alertmanager\n(dedup, group, route)"]
Slack["Slack / PagerDuty / email"]
Rules --> Prom
Prom -- "expr result non-empty" --> AM
AM --> Slack
Anatomy of an alerting rule¶
# examples/prometheus/rules.yml
- alert: HighErrorRate
expr: job:http_requests:rate5m{job="sample-app"} > 100
for: 5m
labels:
severity: warning
annotations:
summary: "High request rate on {{ $labels.job }}"
description: "sample-app request rate has been above 100/s for 5 minutes."
expr— any PromQL expression; the alert fires for every distinct label combination the expression returns, not just once.for: 5m— the condition must stay true on every evaluation for this long (one good evaluation resets it) before the alert actually fires (state goespending→firing). Withoutfor, a single noisy scrape fires an alert — this is the single most common cause of alert fatigue, and the fix is almost always adding or lengtheningfor, not deleting the alert.labels— used by Alertmanager for routing (severity: critical→ page;severity: warning→ Slack channel) and grouping.annotations— human-readable text shown in the notification;{{ $labels.job }}templates in the label values from the specific series that fired.
promtool check rules examples/prometheus/rules.yml
promtool test rules alert_tests.yml # unit-test rules against synthetic input series (not included here)
What makes a good alert¶
- Alert on symptoms, not causes — "error rate > 1%" (a symptom users feel), not "CPU
80%" (a cause that may or may not actually be hurting anyone). A cause-based alert without user impact just trains people to ignore pages.
- Every alert should be actionable — if firing it doesn't lead to a specific action,
it belongs on a dashboard, not in
alertmanager. - Route by severity, not by team guesswork —
criticalpages someone now;warningcan wait for business hours in a Slack channel. Encode that inlabels.severityand let Alertmanager's routing tree do the rest.
Predictive alerts¶
A static threshold says where a gauge is; for something that fills up, what matters is where it
is heading. predict_linear fits a straight line to the last window of samples and extends it:
- alert: DiskWillFillSoon
expr: predict_linear(node_filesystem_avail_bytes{mountpoint="/data"}[1h], 4 * 3600) < 0
for: 5m
labels:
severity: critical
This fires when the disk is on course to run out within four hours, while there is still time to act.
node_filesystem_avail_bytes < 5e9 only fires when 5 GB is left, which on a fast leak can be
minutes from the outage. Use a short window for fast leaks and a long one for slow growth, and keep the horizon
longer than the time it takes to fix the problem.
Alertmanager routing, briefly¶
route:
receiver: slack-default
group_by: [alertname, job]
routes:
- match:
severity: critical
receiver: pagerduty
receivers:
- name: slack-default
slack_configs:
- channel: "#alerts"
- name: pagerduty
pagerduty_configs:
- service_key: "<key>"
group_by batches related alerts (e.g. the same alertname firing across many
instances) into one notification instead of one per series — without it, a single bad
deploy can page someone 50 times in a minute.
Group by labels that identify the incident, not each series: with group_by: [alertname, job] a
rack of 15 dead instances is one notification, while adding instance brings back fifteen pages,
and group_by: ['...'] turns grouping off altogether.