Звонки по оповещениям Prometheus Alertmanager: будить только ради критического

У Alertmanager нет голосового ресивера. Как превратить оповещения Prometheus в звонок только для severity critical: конфиг вебхука, условия и ловушка Watchdog.

Обновлено

Содержание

У Alertmanager нет голосового ресивера. Чтобы получать звонок при срабатывании оповещения Prometheus, добавьте ресивер webhook_configs, указывающий на канал Echobell с типом подписки Звонок. В этом руководстве — точный YAML, условие, которое не даёт звонить по уже устранённым оповещениям, маршрутизация по severity и оповещение Watchdog, которое иначе будет звонить вам каждые четыре часа бесконечно.

Prometheus — стек метрик по умолчанию для большей части инфраструктуры, построенной за последнее десятилетие, а Alertmanager действительно хорош в сложных частях: дедупликация оповещений, группировка, глушение на время обслуживания и подавление нижестоящего шума при отказе вышестоящей зависимости.

Единственное, чего он не сделает, — не разбудит человека.

Почему Alertmanager сам не может позвонить

В Alertmanager есть ресиверы для e-mail, Slack, PagerDuty, OpsGenie, Discord, Telegram, Pushover, Webex, MS Teams и ещё десятка сервисов. Но все они доставляют сообщение, а сообщения подчиняются переключателю звонка, режиму «Не беспокоить» и режимам фокусирования в iOS. В 03:00 это означает: оповещение пришло, и ничего не произошло.

Никакого voice_configs не существует. Обычно люди приходят к таким вариантам:

  • PagerDuty / OpsGenie / Splunk On-Call — они действительно звонят, но это полноценные платформы управления инцидентами с соответствующей ценой за место. Правильный ответ, если вам нужны ротации и деревья эскалации; избыточный, если нужно просто, чтобы телефон зазвонил. (К тому же OpsGenie закрывается — именно поэтому столько команд сейчас пересматривают этот слой.)
  • SMS-мосты вроде Sachet — вы поднимаете ещё один сервис, платите шлюзу за сообщение, а SMS всё равно приходит как сообщение. В iOS текст не пробивает режим фокусирования, если отправитель не в списке разрешённых.
  • Самописная обвязка над Twilio — написать маленький приёмник вебхуков, купить номер, платить за звонок, и вот у вас появился кусок продакшен-инфраструктуры, единственная задача которого — заставить телефон зазвонить.

Универсальный webhook-ресивер и есть выход. Он отправляет POST с документированным JSON на любой URL — а больше ничего и не нужно.

Что понадобится

  • Работающие Prometheus + Alertmanager и доступ к редактированию alertmanager.yml
  • Установленный Echobell (App Store / Google Play)
  • Десять минут

Руководство написано для Alertmanager 0.31. Полезная нагрузка вебхука уже много лет имеет version: "4", поэтому версии 0.2x ведут себя точно так же.

Вашему Alertmanager нужен исходящий HTTPS до hook.echobell.one. Он не обязан быть доступен из интернета — Alertmanager внутри кластера, VPC или домашней лаборатории работает прекрасно.

Шаг 1 — Создайте канал, который вам позвонит

В Echobell создайте канал с названием вроде Prometheus Critical. Установите тип уведомления подписки на Звонок. Именно эта настройка важна: оповещения типа «Звонок» приходят как экран входящего вызова и звонят сквозь режим фокусирования и «Не беспокоить» в iOS, чего push-уведомление не делает.

Настройте шаблоны так, чтобы они читали полезную нагрузку Alertmanager напрямую:

Title: 🔴 {{commonLabels.alertname}} on {{commonLabels.instance}}
Body: {{commonAnnotations.summary}}
{{commonAnnotations.description}}

И в дополнительных настройках — шаблон ссылки, чтобы из записи об уведомлении сразу открывался график:

{{alerts[0].generatorURL}}

Затем скопируйте Webhook URL канала:

https://hook.echobell.one/t/<channel-token>

Относитесь к этому URL как к секрету: любой, у кого он есть, может заставить ваш телефон звонить.

Шаг 2 — Добавьте webhook-ресивер

В alertmanager.yml:

route:
  group_by: ["alertname", "cluster", "service"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: echobell-critical

receivers:
  - name: echobell-critical
    webhook_configs:
      - url: "https://hook.echobell.one/t/<channel-token>"
        send_resolved: false

Перезагрузите конфигурацию через curl -X POST http://localhost:9093/-/reload или SIGHUP.

Обратите внимание на send_resolved: false. Значение по умолчанию для webhook-ресивера — true, в отличие от большинства других ресиверов Alertmanager. Пропустив эту строку, вы получите звонок и когда сервис сломался, и когда он починился сам. Именно второй звонок учит людей игнорировать первый. В шаге 4 показано, как вернуть уведомление о восстановлении без звонка.

Шаг 3 — Разберитесь, что именно приходит

Alertmanager группирует оповещения и отправляет по одному POST на группу:

{
  "version": "4",
  "groupKey": "{}:{alertname=\"HighErrorRate\"}",
  "truncatedAlerts": 0,
  "status": "firing",
  "receiver": "echobell-critical",
  "groupLabels": { "alertname": "HighErrorRate" },
  "commonLabels": { "alertname": "HighErrorRate", "severity": "critical" },
  "commonAnnotations": { "summary": "Error rate above 5% for 10m" },
  "externalURL": "http://alertmanager.internal:9093",
  "alerts": [
    {
      "status": "firing",
      "labels": { "alertname": "HighErrorRate", "instance": "api-7d9f:8080" },
      "annotations": { "summary": "Error rate above 5% for 10m" },
      "startsAt": "2026-09-04T02:41:07.351Z",
      "endsAt": "0001-01-01T00:00:00Z",
      "generatorURL": "http://prometheus:9090/graph?g0.expr=...",
      "fingerprint": "a1b2c3d4e5f60718"
    }
  ]
}

Echobell читает тело JSON как есть, поэтому все эти поля доступны в шаблонах и условиях. Вложенный доступ работает в обоих вариантах синтаксиса — {{commonLabels.severity}} или {{alerts[0].labels["instance"]}}.

Два свойства этой нагрузки определяют всё дальнейшее:

Поле status верхнего уровня равно firing, если активно хотя бы одно оповещение в группе. Оно становится resolved только когда все оповещения группы устранены. Это делает его отличным критерием фильтрации.

commonLabels содержит только метки, общие для всех оповещений группы. Это самый частый сюрприз. Если group_by достаточно широкий и один вебхук несёт HighErrorRate с трёх разных инстансов, то commonLabels.instance отсутствует, и {{commonLabels.instance}} отрисуется пустой строкой. Ниже есть отдельный раздел о том, как с этим быть.

Шаг 4 — Отправляйте восстановления тихим пушем

Вы всё же хотите знать, когда что-то восстановилось, — просто не хотите, чтобы вам за это звонили. Создайте второй канал Echobell Prometheus Recovered, задайте тип Обычный и такие шаблоны:

Title: ✅ {{commonLabels.alertname}} resolved
Body: {{commonAnnotations.summary}}

а в дополнительных настройках — такое условие:

status == "resolved"

Условия — это выражения, которые вычисляются до какой-либо доставки. Если выражение ложно, Echobell примет запрос и ничего не отправит.

Затем направьте один и тот же ресивер на оба канала — ресивер может содержать несколько webhook_configs:

receivers:
  - name: echobell-critical
    webhook_configs:
      # Звонит на телефон. Только при firing.
      - url: "https://hook.echobell.one/t/<calling-channel-token>"
        send_resolved: false
      # Тихий пуш. Условие канала отбрасывает половину firing.
      - url: "https://hook.echobell.one/t/<recovery-channel-token>"
        send_resolved: true

Канал восстановления получает и firing-, и resolved-нагрузки и отбрасывает первые. Итог: сбой звонит, восстановление приходит пушем, который вы прочитаете утром.

Шаг 5 — Маршрутизируйте по severity, а не всё подряд

Маршрут-«ловушка всего», отправляющий каждое оповещение в звонящий канал, — это машина по производству проигнорированных звонков. Разделяйте по severity в Alertmanager, где дереву маршрутизации и место:

route:
  group_by: ["alertname", "cluster", "service"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: echobell-warning

  routes:
    # Watchdog никогда не доходит до человека. См. шаг 6.
    - matchers:
        - alertname = "Watchdog"
      receiver: "null"

    - matchers:
        - severity = "critical"
      receiver: echobell-critical
      group_wait: 10s
      repeat_interval: 1h

receivers:
  - name: "null"

  - name: echobell-critical
    webhook_configs:
      - url: "https://hook.echobell.one/t/<calling-channel-token>"
        send_resolved: false

  - name: echobell-warning
    webhook_configs:
      - url: "https://hook.echobell.one/t/<normal-channel-token>"
        send_resolved: true

Маршруты вычисляются сверху вниз, и побеждает первое совпадениеcontinue по умолчанию false. Поэтому порядок важен: маршрут Watchdog должен стоять выше всего, что могло бы его поглотить.

Если вы предпочитаете оставить один канал и фильтровать на стороне Echobell, эквивалентное условие такое:

status == "firing" && commonLabels.severity == "critical"

Обычно лучше делать это в Alertmanager, потому что там severity заодно управляет group_wait и repeat_interval. Вариант с Echobell лучше, когда изменение конфигурации сегодня не пройдёт ревью.

Шаг 6 — Ловушка Watchdog

Если вы используете kube-prometheus-stack, у вас есть оповещение Watchdog с выражением vector(1). Оно спроектировано так, чтобы срабатывать всегда: оно существует, чтобы внешняя система могла заметить, что сам Prometheus остановился. Конфигурация по умолчанию направляет его в ресивер null.

Направьте маршрут-«ловушку всего» на звонящий канал, не исключив его, — и Watchdog будет звонить вам каждый repeat_interval, вечно, начиная прямо сейчас. Это причина номер один, по которой люди приходят к выводу, что звонковые оповещения «не работают».

Оставьте маршрут null из шага 5. А потом, по желанию, сделайте с ним нечто полезное: превратите Watchdog в настоящий «выключатель мертвеца».

    - matchers:
        - alertname = "Watchdog"
      receiver: deadmansswitch
      group_wait: 0s
      group_interval: 1m
      repeat_interval: 50s

receivers:
  - name: deadmansswitch
    webhook_configs:
      - url: "https://hc-ping.com/<your-check-uuid>"
        send_resolved: false

Echobell не может быть таким выключателем сам: он оповещает, когда запрос приходит, а не когда запросы прекращаются. Поэтому отправляйте пинг Watchdog в сервис, созданный для обнаружения тишины (Healthchecks.io, Cronitor, Dead Man's Snitch), а затем направьте вебхук «проверка упала» этого сервиса на ваш звонящий канал Echobell. Теперь звонок означает «умер сам мониторинг» — то самое оповещение, ради которого вы больше всего хотите быть разбужены, и то, которое никто не настраивает.

Настройка, чтобы не кричать «Волки!»

Три настройки Alertmanager делают большую часть работы, плюс одна в Prometheus:

НастройкаГдеЧто делает
for:Правило оповещенияКак долго условие должно держаться, прежде чем оповещение вообще сработает. Первая линия защиты от двухсекундного сбоя.
group_waitМаршрутСколько ждать другие оповещения перед первым уведомлением. По умолчанию 30s; для critical снизьте до 10s.
group_intervalМаршрутМинимальный интервал перед уведомлением о новых оповещениях в существующей группе. По умолчанию 5m.
repeat_intervalМаршрутКак часто повторно уведомлять о неустранённом оповещении. По умолчанию 4h — значит ночной сбой позвонит вам в 03:00 и ещё раз в 07:00.

repeat_interval — та настройка, над которой стоит подумать. Четыре часа — долго для чего-то сломанного; двадцать минут — машина, которая заставит вас отключить канал. Час для critical — разумная отправная точка.

Если вы хотите, чтобы неотвеченный звонок повторялся сразу, а не ждал следующего repeat_interval, включите Повторять неудавшийся звонок в настройках приложения Echobell.

Звонить только вне рабочего времени

В рабочее время вы, скорее всего, и так смотрите в дашборд. Системные переменные времени Echobell (все в UTC) позволяют одному каналу вести себя по-разному в зависимости от часа, без второго маршрута в Alertmanager:

status == "firing" && (hour >= 17 || hour < 9)

Это звонит вам только вне 09:00–17:00 UTC. Направьте второй канал типа «Обычный» на обратный интервал для дневных пушей:

status == "firing" && hour >= 9 && hour < 17

Добавьте dayOfWeek >= 1 && dayOfWeek <= 5, чтобы выходные тоже считались нерабочим временем. Помните, что всё считается в UTC — сместите под свой часовой пояс. Подробнее — в статье Уведомления по временным окнам с UTC-условиями.

Что делать с пустым commonLabels

Когда группа содержит оповещения с нескольких инстансов, commonLabels.instance исчезает, и заголовок уведомления выглядит как 🔴 HighErrorRate on .

Три выхода, по убыванию предпочтительности:

  1. Добавьте метку в group_by. Если group_by включает instance, все оповещения группы её разделяют, и commonLabels.instance всегда есть. Цена — больше уведомлений: одно на инстанс вместо одного на имя оповещения.
  2. Читайте первое оповещение. {{alerts[0].labels.instance}} всегда заполнено. Это лишь одно из, возможно, многих, поэтому дополните его счётчиком: {{alerts[0].labels.instance}} (+{{alerts.length}} оповещ.).
  3. Спроектируйте подпись так, чтобы пустое значение читалось. В Echobell нет оператора значения по умолчанию — {{a || "unknown"}} отрисует буквальный текст true, а не запасное значение — поэтому пишите Instance: {{commonLabels.instance}} отдельной строкой, где пустое значение очевидно пусто, а не ломает фразу.

Держите полезную нагрузку небольшой

Группа, охватывающая сотню подов, даёт большое тело JSON, а Echobell отклоняет тела триггеров больше 1 МиБ с HTTP 413. Ограничьте это в Alertmanager:

      - url: "https://hook.echobell.one/t/<channel-token>"
        send_resolved: false
        max_alerts: 20

Тогда Alertmanager отправит не более двадцати оповещений и запишет в truncatedAlerts количество отброшенных, которое можно вывести в теле:

Body: {{commonAnnotations.summary}}
Alerts: {{alerts.length}} (+{{truncatedAlerts}} отброшено)

Поделиться оповещением с командой

Канал Echobell можно разделить по ссылке подписки, и каждый подписчик выбирает свой тип уведомления. Поэтому один и тот же маршрут может звонить дежурному инженеру и приходить обычным пушем всем остальным — без оплаты за место и без дополнительных правил маршрутизации в Alertmanager.

Это же соответствует причине, по которой многие команды вообще держат Prometheus у себя: метрики и правила оповещений остаются на вашей инфраструктуре, а Echobell хранит содержимое и историю уведомлений на устройстве, а не на своих серверах.

Чего эта схема не даёт

Честность о границах избавит вас от плохой миграции в будущем. Echobell — слой доставки, а не платформа управления инцидентами. В нём нет:

  • Графиков дежурств и передачи смен follow-the-sun
  • Деревьев эскалации, вызывающих второго человека, если первый не ответил
  • Хронологии инцидентов, отслеживания подтверждений и инструментов постмортема

Если вашей команде это нужно, вам нужен PagerDuty, Grafana Cloud IRM или аналог. Здесь закрыт конкретный пробел, который оставляет Alertmanager: превратить сработавшее оповещение в телефон, который действительно звонит. Для одиночных админов, небольших команд и домашних лабораторий этого обычно достаточно.

Диагностика

Вообще ничего не приходит. Сначала посмотрите логи самого Alertmanager (level=error component=dispatcher), затем убедитесь, что маршрут действительно разрешается в ваш ресивер: amtool config routes test severity=critical alertname=HighErrorRate покажет, в какой ресивер попадёт оповещение, не дожидаясь реального срабатывания.

Echobell возвращает HTTP 404. Токен канала неверен или канал удалён. Неизвестный токен даёт 404, а не тихий успех.

Echobell возвращает 200 с "notificationTriggered": false. Ваше условие оказалось ложным. В теле ответа также есть "conditionsMet": false — самый быстрый способ отличить «условие написано неверно» от «вебхук вообще не дошёл». Сверьте status == "firing" с тем, что реально прислал Alertmanager, — это статус верхнего уровня, а не alerts[0].status.

HTTP 413. Нагрузка превысила 1 МиБ. Задайте max_alerts, как показано выше.

HTTP 405. У канала включён POST Only, а что-то отправило GET. Alertmanager использует POST, так что обычно это значит, что вы открыли URL в браузере.

В заголовке пустой пробел. В commonLabels для этой группы не было такой метки. См. раздел выше.

Не звонит, но уведомление приходит. Тип уведомления подписки — «Обычный» или «Срочный», а не «Звонок». Тип выбирается каждым подписчиком, поэтому проверьте на том устройстве, которое не звонит.

Тестирование без вреда для продакшена. Добавьте правило с expr: vector(1), отдельным alertname и severity: critical, дайте ему сработать один раз и удалите. Либо отправьте оповещение вручную:

curl -X POST http://localhost:9093/api/v2/alerts -H 'Content-Type: application/json' -d '[
  {"labels":{"alertname":"EchobellTest","severity":"critical"},
   "annotations":{"summary":"Testing the phone call path"}}
]'

Частые вопросы

Может ли Prometheus Alertmanager позвонить сам по себе?

Нет. У Alertmanager есть ресиверы для e-mail, Slack, PagerDuty, OpsGenie и многих других сервисов, но нет ни голосового, ни SMS-ресивера. Для звонков нужно направить универсальный webhook-ресивер в сервис, умеющий звонить, например Echobell, либо оплачивать платформу управления инцидентами.

Пробьёт ли звонок режим «Не беспокоить»?

Да. Тип уведомления «Звонок» в Echobell отображается как входящий вызов и звонит сквозь режим фокусирования и «Не беспокоить» в iOS. Подробности и нужные настройки — в статье Как обойти «Фокусирование» в iOS для критических оповещений.

Работает ли это с Alertmanager за файрволом или внутри Kubernetes?

Да. Вебхук — это исходящий HTTPS-запрос от Alertmanager, ему достаточно доступа до hook.echobell.one. Вашему Alertmanager не нужен ни публичный адрес, ни ingress.

Как перестать получать звонки, когда оповещение устраняется?

Установите send_resolved: false в конфигурации вебхука, указывающего на звонящий канал. По умолчанию webhook-ресивер использует true, в отличие от большинства других ресиверов Alertmanager, так что это отказ, а не подписка. Чтобы всё же тихо получать восстановления, добавьте второй канал с условием status == "resolved".

Почему телефон звонит каждые четыре часа из-за одного и того же оповещения?

Это repeat_interval со значением по умолчанию 4h. Alertmanager повторно уведомляет о всё ещё активном оповещении с этой периодичностью. Задавайте его на уровне маршрута — 1h для critical частый выбор. Если звонки начались сразу после добавления маршрута-«ловушки всего», виновником, скорее всего, является всегда активное оповещение Watchdog; см. шаг 6.

Можно ли позвонить нескольким людям по одному оповещению?

Да. Поделитесь каналом с коллегами, и каждый подписчик выберет свой тип уведомления. Все подписанные на звонящий канал получат вызов, без оплаты за место.

Где фильтровать severity — в Alertmanager или в условиях Echobell?

Лучше в Alertmanager: маршрутизация там же позволяет задавать group_wait и repeat_interval для каждой severity, а дерево маршрутов остаётся в системе контроля версий вместе с остальной конфигурацией. Условия Echobell пригодятся, когда изменить конфиг Alertmanager нельзя, или для фильтров, которых в Alertmanager попросту нет, — например по времени суток.

Итог

Вся настройка — это один ресивер, одна строка send_resolved: false и дерево маршрутов, которое держит всё, кроме severity: critical, подальше от звонящего канала. Ваши правила оповещений, группировки, глушения и подавления остаются ровно такими, какими были, а разрыв между «Prometheus заметил» и «человек заметил» закрывается.

Скачайте Echobell для iPhone или возьмите его в Google Play, а затем отправьте оповещение EchobellTest из примера выше, прежде чем доверять этому пути что-то по-настоящему важное.


Похожие материалы