# Alertmanager: route tree, inhibit, dedup, on-call _Observability и мониторинг · LinuxLab Knowledge Base_ **TL;DR:** Alertmanager - принимает alerts от Prometheus, дедупит, группирует, роутит по route tree в PagerDuty/Slack/email. Inhibit suppress'ит зависимые alerts при upstream-падении. Silence на время maintenance. Шум убивает on-call. ## Зачем 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 ├─► Email └─► 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 ``` ## Конфиг, структура ```yaml 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: 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. ```yaml 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». ```bash 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: ```yaml 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](/kb/sli-slo-error-budget.md)) вместо 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**: ```yaml - 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 в данный момент. ```yaml receivers: - name: db-pagerduty pagerduty_configs: - service_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 `alertmanagers` config + `alertmanager_notifications_total` counter. 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](/kb/chrony-and-ntp.md)). - **Notification template error**, Go-template errors молча. Логи AM покажут. ## Команды ```bash amtool config routes test --config.file=alertmanager.yml severity=critical team=db ``` Симуляция: куда уйдёт alert с этими labels - dry-run route tree ```bash amtool silence add severity=warning service=batch --duration=4h --comment='ticket-1234' ``` Silence все warnings batch-сервиса на 4 часа с обоснованием ```bash amtool silence query ``` Список активных silences - регулярно смотри, удаляй забытые ```bash curl -s http://alertmanager:9093/api/v2/alerts | jq '.[] | {alertname: .labels.alertname, status: .status.state}' ``` Текущие alerts через API - filter по state (active/suppressed/unprocessed) ```bash amtool check-config alertmanager.yml ``` Валидация config до reload (CI-friendly) ```bash curl -X POST http://alertmanager:9093/-/reload ``` Reload config без рестарта (требует --web.enable-lifecycle) ```bash promtool check rules /etc/prometheus/rules/*.yml ``` Проверка alerting rules в Prometheus - синтаксис, expr-валидность ## См. также - [Prometheus: scrape, TSDB, PromQL и production-pitfalls](/kb/prometheus-basics.md) - [SLI / SLO / error budget: SRE-метрики без шума](/kb/sli-slo-error-budget.md) - [Типы метрик: counter, gauge, histogram, summary](/kb/metric-types.md) - [Service discovery в Prometheus: k8s, Consul, file_sd, relabel](/kb/service-discovery-prometheus.md) - [OpenTelemetry: signals, OTLP, Collector pipeline](/kb/opentelemetry.md)