Зачем Alertmanager
[[prometheus-basics|Prometheus]] оценивает alerting rules и пушит fired alerts в Alertmanager. Сам Prom не умеет:
- Дедуплицировать (3 ингестера → 3 копии alert'а)
- Группировать (10 нод down = 1 нотификация, не 10)
- Роутить (
team=db→ DBAs Slack,team=net→ on-call) - Inhibit (если cluster-down, не алерти про каждый pod)
- Silence (maintenance window)
- Templating (Slack message с runbook-ссылкой)
Это всё в Alertmanager. Standalone Go-binary, обычно 2-3 реплики HA через mesh-cluster.
Архитектура
Prometheus ─┐
Prometheus ─┼─► Alertmanager ─┬─► PagerDuty
Prometheus ─┘ (HA cluster) ├─► Slack
└─► generic webhook
3 Prom отправляют одинаковые alerts → Alertmanager dedupes по
(labels, alertname). Кластер AM синхронизирует state через mesh-gossip,
только один шлёт notification (gossip lock).
Lifecycle alert'а
┌──────────────────┐
│ Prometheus rule │
│ expr: ... > X │
│ for: 5m │
└────────┬─────────┘
│ POST /api/v2/alerts
▼
┌──────────────────┐
│ Alertmanager │
│ ┌──────────────┐ │
│ │ dedup │ │ по labels
│ ├──────────────┤ │
│ │ inhibit │ │ блок если parent fired
│ ├──────────────┤ │
│ │ silence │ │ блок если matched mute
│ ├──────────────┤ │
│ │ route │ │ выбор receiver
│ ├──────────────┤ │
│ │ group │ │ буферизация N сек
│ └──────────────┘ │
└────────┬─────────┘
│
▼
PagerDuty / Slack
Конфиг, структура
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/...'
route:
receiver: default-receiver
group_by: ['alertname', 'cluster']
group_wait: 30s # ждём собрать batch
group_interval: 5m # batch updates
repeat_interval: 4h # повтор если не решено
routes:
- matchers:
- severity = critical
- team = database
receiver: db-pagerduty
continue: true # НЕ останавливаем дальше route tree
- matchers:
- severity = warning
receiver: slack-warnings
group_interval: 30m
- matchers:
- team = network
receiver: net-slack
receivers:
- name: default-receiver
slack_configs:
- channel: '#alerts'
- name: db-pagerduty
pagerduty_configs:
- service_key: <key>
description: '{{ .CommonAnnotations.summary }}'- name: slack-warnings
slack_configs:
- channel: '#alerts-warnings'
inhibit_rules:
- source_matchers: [severity = critical, alertname = ClusterDown]
target_matchers: [severity =~ "warning|critical"]
equal: [cluster]
Route tree, иерархия
Tree-структура: alert spускается сверху вниз, на каждом узле matchers проверяются. Первый match → отправлен в receiver.
continue: true, после match продолжать дерево (один alert → N receivers)matchers,label = value,label != value,label =~ regex,label !~ regex- Дочерние routes наследуют
group_by,group_wait,group_intervalот родителя если не override
Pattern: branching по team label, тогда severity определяет
receiver внутри.
Inhibit, suppress dependent
Сценарий: cluster-API упал → 50 alert'ов про каждый pod.
inhibit_rules:
- source_matchers: [alertname = APIServerDown]
target_matchers: [alertname = PodNotReady]
equal: [cluster]
Если в кластере fire APIServerDown, все PodNotReady (с тем же
cluster) blocked. Сразу как APIServerDown resolve, pods
unblock и могут fire.
Best practice: иерархия inhibit от high-level к низкому (datacenter > cluster > node > pod).
Silence, temporary mute
Maintenance window: «глушим alerts по service=db до 16:00».
amtool silence add \
service=db env=prod \
--duration=2h --comment="DB maintenance ticket DBA-1234"
Silence, labels matcher + expiration. AM не отправляет matched alerts. UI/CLI показывают alerts в suppressed state.
Best practice: всегда --comment с ссылкой на ticket. Без
обоснования silences копятся, забываешь, alerts реально глохнут.
Grouping, анти-шум
10 нод одновременно вышли из rotation. Без grouping:
- 10 PagerDuty инцидентов
- 10 Slack pings
- On-call злой
С group_by: [alertname, cluster]:
- 1 PagerDuty с 10 nodes в payload
- 1 Slack message: «10 nodes down: node-01 ... node-10»
group_wait = 30s, ждём всех чтобы собрать batch. Trade-off:
быстрее notify vs больше шума.
Notification templates
Default Slack message, generic, мало пользы. Кастомная Go-template:
receivers:
- name: slack-detailed
slack_configs:
- channel: '#alerts'
title: '{{ .Status | toUpper }} {{ .CommonLabels.alertname }}'text: |
*Severity:* {{ .CommonLabels.severity }} *Cluster:* {{ .CommonLabels.cluster }} *Description:* {{ .CommonAnnotations.description }} *Runbook:* {{ .CommonAnnotations.runbook }} *Dashboard:* {{ .CommonAnnotations.dashboard }} {{ range .Alerts }} • {{ .Labels.instance }}: {{ .Annotations.summary }} {{ end }}Хороший alert содержит: severity, что сломано, runbook, dashboard, affected scope.
Alert hygiene, anti-patterns
Шум убивает on-call. Симптомы:
- On-call устал, игнорит alerts → реальный инцидент пропустили
- 50 alerts/день, 49 ignored
- «alert fatigue»
Anti-patterns:
- Алерт на raw метрику без
for:, flap'ит на короткие spike'и - Алерт на average latency, не отражает p99 опыт
- Алерт на абсолютное значение CPU, нерелевантно (90% CPU = OK на batch worker, problem на user-facing)
- Алерт без runbook, on-call не знает что делать
- Алерт на
up == 0без deadman, Prom сам упал, alert не отправится. См. ниже.
Best practices:
- Алертить только на симптомы, не причины (RED method, Google SRE «4 golden signals»)
- Использовать error budgets (sli-slo-error-budget) вместо threshold-alerts
- Multi-window burn-rate для SLO
- Каждый alert имеет runbook
- Регулярно ревьюить noisy alerts, top-N по fire-count за месяц, тюнить threshold или удалять
Deadman alert
Кто алертит, что Prometheus сам упал? Если Prom down, alerting rules не оцениваются, AM не получает notifications.
Deadman:
- alert: DeadMansSwitch
expr: vector(1) # всегда firing
annotations:
summary: "Prometheus alive"
Прометей всегда шлёт. AM шлёт в receiver deadman каждые 5 минут.
Receiver, внешний watchdog (Healthchecks.io, Cronitor): если
notification не пришла за 10 минут, внешний alert «Prom мёртв».
On-call rotation
Alertmanager не управляет rotation сам, это PagerDuty/Opsgenie.
AM шлёт в team_X service, PagerDuty знает кто on-call в данный
момент.
receivers:
- name: db-pagerduty
pagerduty_configs:
- service_key: <key>
severity: critical
group: database
class: '{{ .CommonLabels.alertname }}'PagerDuty class, для grouping инцидентов в их UI.
HA Alertmanager
3 instances в mesh-cluster:
alertmanager --cluster.peer=am-01:9094 --cluster.peer=am-02:9094 \
--cluster.peer=am-03:9094
Все 3 принимают alerts от Prom (Prom отправляет всем). Gossip синхронизирует state (silences, inhibits). Только один шлёт notification через clock-based leader-election (избегает дублей).
Когда что-то пошло не так
- Alert fired в Prom, но AM не получил, проверь Prometheus
alertmanagersconfig +alertmanager_notifications_totalcounter. Network/auth. - AM получил, но не отправил, проверь route tree (matchers
могут быть слишком strict).
amtool config routes testсимулирует routing. - PagerDuty/Slack rate limit, AM ретраит, экспоненциальный
backoff. Если flood, повышай
group_interval. - «stuck» alerts (resolved, но AM держит),
resolve_timeoutне сработал, Prom послалEndsAtв прошлом. Bug, обнови AM. - Silence не работает, matchers неточные. Test через
amtool silence query. - HA AM шлёт дубли, clock skew между нодами > 30s. NTP обязателен (chrony-and-ntp).
- Notification template error, Go-template errors молча. Логи AM покажут.