Всем привет, меня зовут Пётр, я DevOps инженер компании Nixys. История с алертами и дежурствами для меня, как и для любого DevOps инженера, довольно противная, потому что не знаешь, что вызывает больше негатива: ложный звонок посреди ночи или критичный алерт, который почему-то не сработал.
Долгое время решением этого были Grafana OnCall, PagerDuty и другие платформы, которые предоставляли возможность установки open-source инструментов для оповещения инженеров, ведения смен и эскалации происшествий. Но в марте 2026 года состоялась полная архивация проекта Grafana OnCall OSS, а PagerDuty полностью прекратила сотрудничество с клиентами из России еще в 2022. И вот, время шло, а крепкой зарубежной opensource замены уровня Grafana так и не появлялось. Это побудило нас самостоятельно взяться за разработку локального проекта для управления алертингом, который мы бы хотели предоставить в открытый доступ.

nxs-anomaly - открытый инструмент, написанный на Go, который принимает алерты из существующего мониторинга, направляет их дежурному и выполняет цепочку эскалации, пока не получит подтверждение. Расписания, уведомления и история реакции доступны в веб-интерфейсе. Проект распространяется под лицензией Apache 2.0, разворачивается в вашей инфраструктуре и поддерживает установку через Docker Compose или Helm chart. С основной информацией разобрались, начнем краткое знакомство.
Путь одного алерта
Возьмем инфраструктуру, где Prometheus уже собирает метрики, а Alertmanager отправляет уведомления. nxs-anomaly подключается к этому процессу на этапе реакции: определяет, кого уведомить сейчас, что делать без ответа и где посмотреть историю происходящего. Источниками данных могут служить:
Prometheus
Alertmanager
Grafana Alerting
Системы, способные отправить JSON на webhook.
Важное уточнение: nxs-anomaly выступает в качестве роутера для алертов, а сбор метрик и вычисление правил остаются в вашей системе мониторинга.
Итак, представим, что мониторинг сообщил об ошибках сервиса. Мы хотим, чтобы:
Алерт прилетел дежурному инженеру;
Дежурный инженер мог отметить, что он взял алерт в работу;
Через 5 минут, при отсутствии отметки от инженера L1 о том, что алерт в работе - эскалация на инженера L2;
Через 10 минут, при отсутствии отметки от инженера L2 о том, что алерт в работе - эскалация на ответственного инженера;
Если алерт не долетел из-за сетевого сбоя или недоступности эндпоинта - мы хотим об этом знать;
В nxs-anomaly такой сценарий собирается из нескольких частей.
1. Интеграция принимает событие
Для источника алертов в nxs-anomaly создаётся интеграция - HTTP-адрес с секретным ключом в URL и набором правил обработки. В рамках интеграция задается:
Маршрут - в нем задаются каналы доставки (Telegram, Slack и т.п.)
Шаблоны - обогащенный общий шаблон для всех алертов группы
Группировки - имена меток, из которых строится ключ дедупликации
Событие проходит путь от HTTP-запроса до записи в группе алертов за одну транзакцию. После неё worker будится, чтобы сразу разослать уведомления.
Повторные события объединяются по настроенным признакам - например, по имени алерта и сервису. Так можно работать с группой связанных событий, а не разбирать каждое повторное сообщение отдельно.
2. Маршрут выбирает цепочку эскалации
Цепочка эскалации в nxs-anomaly - это заданный по порядку сценарий действий. Он запускается, когда по алерту открывается группа, и проходит шаг за шагом, пока на инцидент кто-нибудь не отреагирует. Обычно сценарий такой: разбудить дежурного, подождать, позвать следующего, потом команду, потом руководителя.
Например, критичные события направляются в одну цепочку, остальные - в другую. Правила определяют, кому и в каком порядке пойдут уведомления.
3. Расписание определяет текущего дежурного
В nxs-anomaly дежурные задаются через расписание. Когда цепочка доходит до шага уведомления, worker спрашивает у расписания, кто дежурит в текущий момент. Ответ ищется по трём источникам в строгом порядке, и отвечает только первый сработавший: замена → ротация → старые смены.
Расписание позволяет настроить классические ротации, часовые пояса и временные замены. Если инженер ушёл в отпуск или поменялся сменой, это можно отразить в расписании, сохранив остальную схему реагирования.
4. Worker выполняет эскалацию
Эскалация происходит через отдельный фоновый процесс. Он в цикле находит группы, у которых наступило время следующего шага, и продвигает их по цепочке. Это может быть как первый вызов дежурного L1, так и следующий шаг передачи алерта на L2. Сам он ничего не отправляет: шаги только создают уведомления в базе, а доставка идёт следующей стадией того же цикла.
Когда приходит время отправить уведомление об алерте, worker атомарно забирает пачку уведомлений и отправляет их параллельно. Каждое уведомление доставляет ровно одна реплика. Если worker упал посреди отправки, стадия reclaim следующего цикла вернёт уведомление в очередь, так что оно не потеряется. Неудачные отправки уходят в статус retry_scheduled, их обрабатывает стадия retries.
5. Инженер подтверждает, что взял алерт в работу
Для этого есть ACK (acknowledge) - это отметка дежурного «я увидел инцидент и занимаюсь им». Она останавливает эскалацию группы алертов: никого больше не будят, но инцидент ещё не закрыт. В интерфейсе остаются история группы и попытки доставки: можно посмотреть, что отправлялось и какие действия последовали.
Куда можно отправлять уведомления
Community поддерживает:
Telegram,
email,
webhooks,
Slack/Mattermost-совместимые endpoints,
Звонки через Asterisk.
Каналы требуют своей настройки: для почты нужен SMTP, для Telegram - бот, для звонков - доступ к Asterisk. ChatOps-действия доступны там, где настроена соответствующая интеграция.
Гарантии доставки
Уведомление доставляется по меньшей мере один раз (at-least-once): оно не теряется, но при падении worker в неудачный момент может прийти дважды. Защита держится на пяти механизмах:
транзакционная запись,
атомарный захват,
повторы с backoff,
возврат зависших захватов
dead letter.
Уведомление не теряется при создании
Шаг эскалации создаёт уведомление в той же транзакции, что и продвижение группы. Либо сохраняются и шаг, и уведомление, либо ничего: ситуации «шаг выполнен, а уведомления нет» не бывает.
У уведомления есть ключ идемпотентности (idempotency_key), и на него стоит уникальный индекс в PostgreSQL. Одно и то же уведомление нельзя создать дважды, например при повторном прогоне шага.
Созданное уведомление получает статус delivery_scheduled, и worker будится через pg_notify.
Одно уведомление берёт один worker
Даже при нескольких репликах уведомление достаётся ровно одной. Отсутствие двойной доставки при конкуренции проверяется тестами и нагрузочными профилями.
Отправка и результат
Параллельно: до 8 отправок одновременно (NXS_ANOMALY_NOTIFICATION_DELIVERY_CONCURRENCY), с таймаутом HTTP 5 с.
Три исхода:
delivered — провайдер принял, HTTP 2xx;
failed — ошибка, будет повтор;
skipped — для канала не настроен транспорт, например нет токена Telegram. Статус окончательный: повтор всё равно не поможет, и уведомление не должно выглядеть доставленным.
Журнал попыток: каждая попытка пишется в notification_delivery_attempts с кодом провайдера, фрагментом ответа до 512 символов с замаскированными токенами и длительностью.
Паника адаптера не роняет worker. Уведомление остаётся захваченным, и его подберёт механизм возврата из п. 5.
Повторы
По умолчанию используется до 3 попыток: первая, повтор через 1 мин и повтор через 5 мин. Это поведение настраивается через переменную окружения NXS_ANOMALY_NOTIFICATION_RETRY_DELAYS=1,5 и …_MAX_RETRIES.
Статусы: после неудачи — retry_scheduled с next_retry_at. Стадия retries захватывает такие уведомления так же атомарно, статус retrying.
HTTP 429: если провайдер вернул Retry-After (в заголовке или, как Telegram, в теле), ждём столько, сколько он попросил, но не больше часа.
Исчерпание попыток: статус failed, то есть dead letter:
растёт метрика nxs_anomaly_dead_letter_events_total, для неё есть готовое правило алерта;
если задан NXS_ANOMALY_DEAD_LETTER_WEBHOOK_URL, туда уходит событие.
Возврат зависших захватов
Worker может упасть посреди отправки, и уведомление останется в статусе delivering или retrying. В начале каждого цикла стадия reclaim возвращает такие захваты в очередь, если claimed_at старше таймаута ClaimTimeout. Таймаут по умолчанию равен удвоенному худшему времени стадии доставки, чтобы не отобрать работу у живого worker.
Что под капотом
В типовом развёртывании четыре компонента:
Компонент | За что отвечает | Язык |
API | Принимает алерты и обслуживает запросы интерфейса и API-клиентов | Go |
Worker | Выполняет эскалации, отправляет уведомления и обрабатывает повторы | Go |
PostgreSQL | Хранит состояние алертов, настройки, расписания и историю | |
Веб-интерфейс | Позволяет настроить процесс и работать с алертами | TypeScript |
Также, из полезных инструментов Community версии:
Helm chart для развертывания в Kubernetes
Terraform провайдер для контроля ресурсов через IaC
Метрики Prometheus
OpenTelemetry трейсинг для отслеживания причин ошибок
Нативная возможность проксирования алертов
Внутренний аудит - список всех изменения конфигурации и действия с алертами с указанием автора
Внутренняя аналитика - текущее состояние групп алертов и доставки, а также недавние инциденты
Регламентные работы - пока окно активно, входящие в него интеграции записывают алерты, но никого не вызывают. Полезно для проведения технических работ и глобальных сбоев, где нельзя повлиять
Что нам хочется проверить вместе с сообществом
Мы выпускаем nxs-anomaly, чтобы понять, будет ли он полезен сообществу и какие задачи действительно помогает решить. Поэтому, нам будет крайне полезна обратная связь о первом запуске, настройке дежурств и доставке в ваши каналы.
Посетите nxs-anomaly на GitHub: посмотрите Compose-пример, запустите одну интеграцию и проверьте путь до уведомления и ACK. Если на каком-то шаге пришлось разбираться дольше ожидаемого, расскажите об этом в Issues. Укажите версию, способ установки, ожидаемый результат и то, что получилось. Исправления документации и pull requests тоже приветствуются.
Для ознакомления вам могут быть полезны следующие ссылки:
Практическое руководство - что и в каком порядке создать, чтобы алерт, пришедший на webhook, разбудил дежурного.
Инвентаризация персональных данных - что nxs-anomaly хранит о людях, где именно, чем это ограничено по сроку и что с этим делать по запросу человека.
Справочник API nxs-anomaly
Terraform провайдер для управления ресурсами nxs-anomaly
А в комментариях интересно обсудить ваш текущий процесс: как команда узнаёт, что алерт уже взяли в работу, и что происходит, если дежурный не ответил?

