Всем привет, меня зовут Пётр, я 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
А в комментариях интересно обсудить ваш текущий процесс: как команда узнаёт, что алерт уже взяли в работу, и что происходит, если дежурный не ответил?
Комментарии (2)

RukInDaHouse Автор
18.09.2026 10:11Спасибо за отличный вопрос. С версии 1.3.0 предусмотрен внешний heartbeat и отдельный набор алертов «процесс отсутствует». Мы рекомендуем три независимых уровня контроля:
Heartbeat прямо от воркера, без Prometheus: задаётся переменная NXS_ANOMALY_WORKER_HEARTBEAT_URL, туда подходит адрес проверки во внешнем сервисе, например healthchecks.io. После каждого успешного цикла, воркер делает GET на этот адрес, но не чаще раза в минуту. Если цикл упал из-за базы, пинга не будет. Поэтому одна такая проверка ловит падение воркера, недоступность PostgreSQL и обрыв сети между ними.
Prometheus-алерты на отсутствие процесса, а не на его деградацию. Правила лежат в prometheus-rules.yaml. В Helm они включаются через prometheusRule.enabled и serviceMonitor.enabled.
Отдельная маршрутизация в Alertmanager: эти алерты отправляются отдельным ресивером напрямую, в Telegram, почту или SMS, а не в nxs-anomaly.
Также у API и воркера есть эндпоинты /live и /ready. На воркерае это порт NXS_ANOMALY_WORKER_ADDR, по умолчанию :8081. /ready воркера отвечает 503, если пропал доступ к БД или цикл не завершался дольше допустимого окна. Эти адреса можно проверять через blackbox_exporter или внешний uptime-монитор.
Rawiiw
Благодарю за статью, хороший инструмент для алертинга!
Вопрос: а как вы рекомендуете мониторить сам nxs-anomaly? Если упадёт API, worker или PostgreSQL, основной канал оповещения тоже перестанет работать. Предусмотрен ли внешний heartbeat/blackbox-check либо резервный канал, не зависящий от nxs-anomaly? Не нашла в статье информацию