Когда система мониторинга отправляет алерт, он редко выглядит так, как хотелось бы дежурному инженеру.
Один источник пишет critical, другой — disaster, третий передаёт severity: 5. В одном payload сервис называется checkout, в другом — payments-api, а в третьем его приходится угадывать по namespace.
Если все эти события приходят через общий Alertmanager или webhook, одной статической настройки маршрута быстро становится недостаточно.
Обычно проблему решают одним из трёх способов:
Усложняют правила на стороне каждой системы мониторинга.
Создают много почти одинаковых endpoint’ов и маршрутов.
Добавляют промежуточный сервис, который преобразует payload перед отправкой в on-call-систему.
Все три варианта работают. А потом появляется ещё одна команда, меняется схема меток, кто-то копирует правило с ошибкой — и распределённая конфигурация начинает жить собственной жизнью.
В IncidentRelay 2.0 мы добавили Event Orchestration. В этой статье разберём одну из основных частей новой функции — Global Orchestration: зачем она нужна, где находится в потоке обработки, как устроены правила и почему мы не ограничились обычным rule engine с кнопкой «Включить».

Какую задачу решает Global Orchestration
Global Orchestration — это набор упорядоченных правил, принадлежащий группе IncidentRelay. Он обрабатывает нормализованное событие до того, как будет окончательно выбран сервис и запущен обычный жизненный цикл алерта.
Упрощённо поток выглядит так:
Payload интеграции ↓ Аутентификация и нормализация ↓ Global Orchestration группы ↓ Оркестрация выбранного сервиса, если он известен ↓ Обычный жизненный цикл IncidentRelay ↓ Группа алертов, дочерний алерт, эскалация и уведомления

На глобальном уровне можно:
выбрать команду, маршрут и сервис;
нормализовать severity, title и другие поля события;
назначить приоритет и политики;
добавить или удалить метки;
настроить
group_keyиdedup_key;подавить уведомления;
отложить создание алерта;
полностью отбросить событие;
поставить безопасный асинхронный webhook в очередь.
При этом оркестрация не заменяет маршруты, сервисы, политики эскалации или хранилище алертов. Она подготавливает событие и явно выбирает, как существующие части системы должны его обработать.
Иными словами, мы добавили не второй жизненный цикл алерта, а слой принятия решений перед ним.
Почему global и service — разные области
В IncidentRelay есть две области оркестрации:
Область | Когда запускать | Типичные задачи |
|---|---|---|
| Владелец события ещё неизвестен или правило относится к нескольким сервисам | Нормализация, выбор команды и сервиса, общие правила подавления |
| Сервис уже выбран | Политики конкретного сервиса, локальное обогащение, задержка известного сигнала, запуск диагностики |
Разделение оказалось важным. Без него глобальное определение быстро превращается в огромный файл со знаниями обо всех особенностях всех сервисов.
Хорошая граница ответственности выглядит так:
Global Orchestration: Кому принадлежит событие? В какой общий формат его привести? Service Orchestration: Как именно этот сервис хочет его обработать?
Глобальное правило может выбрать сервис. После этого IncidentRelay запускает оркестрацию, прикреплённую к выбранному сервису.
Если service orchestration передаёт событие другому сервису, runtime защищает цепочку от циклов и чрезмерного количества переходов. Потому что бесконечная маршрутизация — это тоже вид мониторинга, только уже за состоянием CPU.
Так общие соглашения хранятся в одном месте, а знания о конкретном сервисе остаются рядом с самим сервисом.
Пример: один Alertmanager и несколько команд
Представим, что общий Alertmanager принимает алерты всей production-платформы.
В IncidentRelay приходит нормализованное событие:
{ "source": "alertmanager", "title": "High error rate", "message": "Error rate is above 20%", "severity": "fatal", "status": "firing", "dedup_key": "checkout-db-2:high-error-rate", "labels": { "environment": "production", "application": "checkout", "component": "database", "instance": "checkout-db-2", "customer_impacting": true } }
Из payload уже понятно почти всё необходимое. Но статический маршрут не знает, какую комбинацию решений следует принять.
Глобальная оркестрация может выполнить такую последовательность:
Преобразовать
fatalв каноническое значениеcritical.По
application=checkoutвыбрать команду Payments.По
component=databaseвыбрать сервис Checkout Database.Для критического события с
customer_impacting=trueзадать приоритетP1.Построить стабильные ключи группировки и дедупликации.
Остановить дальнейшие глобальные правила маршрутизации.
Передать результат в оркестрацию сервиса и обычный lifecycle.
В интерфейсе правило можно представить примерно так:
WHEN ALL ├── labels.environment equals production ├── labels.application equals checkout ├── labels.component equals database └── labels.customer_impacting is_true THEN ├── set_team → Payments ├── set_service → Checkout Database ├── set_severity → critical ├── set_priority → P1 ├── set_label → orchestrated=true └── set_grouping group_key → {{ labels.application }}: {{ labels.environment }} dedup_key → {{ labels.alertname }}: {{ labels.instance }} AFTER └── stop
stop здесь прекращает обработку следующих соседних правил этой оркестрации.
Событие не отбрасывается: оно продолжает обычный жизненный цикл с уже принятыми решениями.
Почему правила выполняются последовательно
Мы рассматривали вариант, при котором все правила получают один неизменяемый исходный контекст, а затем результаты как-то объединяются.
На бумаге это выглядит функционально и аккуратно. На практике быстро возникает вопрос: что делать, если два правила выбрали разные сервисы или установили разные значения priority?
В текущей модели правила выполняются сверху вниз, а действия внутри совпавшего правила — в указанном порядке. Последующие правила видят изменения предыдущих.
Например:
Правило 1: IF event.severity equals disaster THEN set_severity critical AFTER continue Правило 2: IF event.severity equals critical AND labels.customer_impacting is_true THEN set_priority P1
Второе правило уже увидит critical.
Это позволяет строить определение как понятный конвейер:
Нормализовать значения разных интеграций Добавить общие метки Выбрать владельца Выбрать priority и policies Настроить grouping и deduplication Принять решение suppress / pause / drop Поставить автоматизацию в очередь
Минус у подхода тоже есть: порядок становится частью логики.
Поэтому в execution trace сохраняются совпавшие условия, выполненные действия и значения до и после каждого изменения.
Условия: безопасные пути вместо произвольного кода
В условиях используются dotted paths к разрешённым частям контекста:
event.severity event.status labels.environment labels.application raw.alerts.0.labels.namespace variables.service route.id service.id team.id integration.source result.disposition
Движок читает JSON-объекты и индексы массивов, но не выполняет методы или произвольные выражения.
Основные операторы:
equals not_equals contains not_contains starts_with ends_with regex not_regex in not_in exists not_exists greater_than less_than greater_or_equal less_or_equal is_true is_false
Для логики доступны вложенные группы:
ALL— AND;ANY— OR;NONE— NOT.
Пример:
ALL ├── labels.environment equals production └── ANY ├── event.severity equals critical └── labels.priority equals p1
То есть:
production AND (critical OR p1)
Шаблоны тоже ограничены
Текстовые действия используют подстановки вида:
{{ labels.service | upper }}: {{ event.title | trim }}
Поддерживается небольшой набор детерминированных фильтров:
lower upper trim default replace truncate
Например:
{{ labels.cluster | default("unknown") | trim | lower }}
Если необязательное поле может отсутствовать, это следует явно обработать через default.
В противном случае ошибка шаблона попадёт в трассировку и будет обработана согласно on_failure действия.
У каждого действия есть одна из трёх стратегий ошибки:
Значение | Поведение |
|---|---|
| Записать ошибку и перейти к следующему действию |
| Остановить действия текущего правила |
| Остановить всю оркестрацию |
Для необязательного обогащения подходит continue.
Для выбора маршрута или разрушительного действия безопаснее остановить правило или всю оркестрацию, чем продолжать с частично изменённым событием.
Валидация выбранных сущностей
Недостаточно просто получить из правила числовой service_id. Во время выполнения IncidentRelay проверяет согласованность выбранных объектов.
Например, будут отклонены случаи, когда:
маршрут принадлежит другой группе;
route source равен
sentry, а источник события —alertmanager;выбранный сервис принадлежит другой команде, чем маршрут;
escalation policy или notification policy принадлежит другой команде;
выбранный объект был отключён или удалён после публикации версии.
Поведение после такой ошибки зависит от compatibility mode.
Два переключателя режима — и это не одно и то же
У определения есть runtime mode и compatibility mode. Поначалу их легко перепутать.
Runtime mode
Он отвечает на вопрос: влияет ли опубликованная версия на реальные события?
Режим | Что происходит |
|---|---|
| Production-события не вычисляются; доступны редактирование, validation и simulation |
| Опубликованная версия вычисляется и записывается, но её решения не меняют production |
| Допустимые решения применяются к реальной обработке |
Для shadow и active нужна опубликованная версия.
Compatibility mode
Он отвечает на другой вопрос: как оркестрация делит ответственность с существующим lifecycle?
Режим | Что происходит |
|---|---|
| Существующая логика остаётся главным источником решений |
| Явные решения оркестрации сохраняются, а старый lifecycle заполняет оставшиеся значения |
| Оркестрация становится главным источником маршрутизации; требуется валидный route |
Например, в hybrid:
Оркестрация явно установила priority=P1 → priority policy не заменяет это значение Оркестрация не выбрала notification policy → обычный механизм выбора policy продолжает работать
Если комбинация выбранных сущностей оказалась невалидной, hybrid может отклонить кандидата и продолжить legacy-обработку.
В режиме orchestration аналогичная ошибка блокирует дальнейшую обработку вместо fallback.
Для первого production-внедрения мы рекомендуем hybrid.
Отдельный крайний случай: конфигурация active + legacy не применяет решения оркестрации к production. Слово active здесь относится к runtime mode, но compatibility mode всё ещё оставляет старый lifecycle главным.
Название немного обманчивое, зато конфигурация безопасная. Иногда программное обеспечение шутит само, даже если разработчики не просили.
Simulator: проверить не только счастливый payload
Simulator вычисляет текущий draft изолированно.
Он не:
создаёт алерты;
отправляет уведомления;
вызывает webhooks;
меняет режим выполнения;
публикует версию;
изменяет production-данные.
Можно передать уже нормализованное событие или сырой payload поддерживаемой интеграции.
Во втором случае payload проходит через тот же зарегистрированный normalizer, который используется при production ingestion.
Для каждого важного правила стоит проверить минимум такие события:
Точное совпадение.
Почти совпадающее событие, которое не должно пройти.
Другое окружение.
Другой source.
Отсутствие необязательной метки.
Статус
resolved.Повторное событие, которое должно получить тот же
dedup_key.
Результат симуляции содержит:
исходное нормализованное событие;
совпавшие правила;
condition trace;
значения до и после каждого действия;
итоговый контекст;
выбранные сущности;
disposition;
разницу между draft и active version.
Успешная симуляция доказывает, что движок смог выполнить определение. Она не доказывает, что бизнес-решение правильное.
Зелёный HTTP 200 вполне способен сообщить, что вы очень успешно направили все production-алерты команде стажёров.
Grouping и deduplication — тоже часть маршрутизации
Правильный владелец не спасает ситуацию, если каждый повтор создаёт новый алерт.
Для группы однотипных событий можно задать:
group_key: {{ labels.alertname }}: {{ labels.environment }} dedup_key: {{ labels.alertname }}: {{ labels.instance }} window_seconds: 900
Так события с одним alert name в одном окружении попадут в общую группу, а каждый instance сохранит собственный дочерний алерт. Повторный сигнал обновит нужный child alert.
Ключи должны быть стабильными.
Временная метка в dedup_key формально допустима, но фактически означает «создавай новый алерт при каждом запросе». Очень удобно, если ваша цель — нагрузочно протестировать дежурного инженера.
suppress, pause и drop — три разных решения
Эти действия легко объединить в голове как «не уведомлять», но последствия отличаются принципиально.
Действие | Результат |
|---|---|
| Алерт создаётся и остаётся видимым, но уведомления и эскалация подавляются |
| Событие хранится как pending и активируется после задержки, если раньше не пришёл resolve |
| Алерт и группа вообще не создаются |
suppress подходит для событий, которые нужно сохранить для поиска или корреляции.
pause полезен для кратковременных сбоев. Например, можно подождать пять минут и отменить создание алерта, если за это время пришёл совпадающий resolve.
drop следует использовать только для сигналов без операционной ценности — например, точно определённого тестового heartbeat.
Пустое условие совпадает со всеми событиями. Поэтому catch-all вместе с drop, suppress, pause или обязательной маршрутизацией требует особенно осторожной проверки.
Catch-all с drop — очень эффективное средство против alert fatigue, примерно как демонтаж пожарной сигнализации против писка разряженной батарейки.
Асинхронные webhooks вместо произвольного кода
Если после совпадения правила нужно запросить диагностику, создать тикет или вызвать внутреннюю автоматизацию, используется переиспользуемое действие enqueue_webhook.
HTTP-запрос не выполняется внутри ingestion request. IncidentRelay ставит выполнение в очередь, а scheduler доставляет его асинхронно с timeout, retry и ограничениями безопасности.
Секретные заголовки хранятся зашифрованными и не возвращаются API. URL проверяются против SSRF, redirects повторно валидируются, а private network targets запрещаются или ограничиваются allowlist согласно конфигурации.
В simulation и shadow mode webhook не отправляется.
Иначе проверка нового правила могла бы создать несколько сотен вполне настоящих задач с заголовком TEST PLEASE IGNORE.
Что получилось в итоге
Global Orchestration дала нам единое место, где разнородные факты мониторинга превращаются в объяснимое операционное решение до начала paging:
интеграции могут сохранять собственные форматы payload;
общие правила нормализации не дублируются по сервисам;
ownership выбирается по содержимому события;
локальная логика остаётся в service orchestration;
каждое решение можно проверить в trace;
draft не влияет на production;
опубликованные версии неизменяемы;
simulation и shadow mode позволяют внедрять изменения без ставки на удачу.
IncidentRelay — open-source и self-hosted. Исходный код доступен на GitHub.
Подробное руководство по условиям, действиям, Simulator и shadow mode находится в документации Event Orchestration, а API — в отдельном руководстве.

