Окна обслуживания#
Окна обслуживания определяют планируемые периоды, когда IncidentRelay должен изменить обработку совпадающих алертов. Окно может быть нацелено на группу, команду, сервис или маршрут и может либо подавлять уведомления, либо подавлять создание инцидентов, либо создавать инцидент обслуживания, либо приостанавливать эскалации.
Понятия#
Окно обслуживания сопоставляется с входящими алертами после маршрутизации и разрешения сервиса. Это означает, что маршрут, команда и сервис уже известны до того, как IncidentRelay решает, применяется ли окно обслуживания.
Окна обслуживания хранятся отдельно от инцидентов, но информация о сопоставлении прикрепляется к созданной группе алертов и дочерним алертам через поля обслуживания. В модели данных IncidentRelay AlertGroup является моделью хранения инцидентов.
Область действия#
Каждое окно обслуживания должно иметь хотя бы одну область действия.
Поддерживаемые типы областей:
| Область | Описание |
|---|---|
group | Применяется ко всем командам, сервисам, маршрутам и алертам в рамках группы. |
team | Применяется к выбранной команде. |
service | Применяется к выбранному сервису. |
route | Применяется к выбранному маршруту алертов. |
Если одному и тому же алерту соответствует несколько активных окон, репозиторий возвращает первым самое новое совпадающее активное окно, упорядоченное по starts_at, а затем по id.
Виды поведения#
Поведение обслуживания управляет тем, что происходит, когда совпадающий алерт приходит во время активного окна.
| Поведение | Инцидент создан | Статус инцидента | Уведомления | Эскалация | maintenance_suppressed |
|---|---|---|---|---|---|
suppress_notifications | Да | Статус входящего алерта | Подавлены | Приостановлена | true |
suppress_incident | Нет | Неприменимо | Неприменимо | Неприменимо | Неприменимо |
create_maintenance_incident | Да | maintenance | Не подавляются флагом обслуживания | Обычная, если не настроено иное | false |
pause_escalation_only | Да | Статус входящего алерта | Обычные | Приостановлена | false |
suppress_notifications#
Создаёт группу алертов и дочерний алерт, прикрепляет окно обслуживания и приостанавливает весь жизненный цикл уведомлений, пока окно активно. Сюда входят:
- первое уведомление;
- обновления группы в очереди;
- напоминания и повторные уведомления;
- отложенная доставка на уровне пользователя;
- последующие шаги эскалации и уведомления об эскалации.
Уже доставленные уведомления остаются в истории и не отзываются. Пропущенные напоминания и шаги эскалации не воспроизводятся после обслуживания.
Когда период обслуживания заканчивается, для всё ещё активной группы алертов планируется одно обычное уведомление или обновление. Счётчик напоминаний сбрасывается, а отсчёт эскалации по текущему правилу возобновляется с момента завершения обслуживания.
Используйте это, когда алерты должны оставаться видимыми в IncidentRelay, но ответственных не следует оповещать во время планируемых работ.
suppress_incident#
Не создаёт новую группу алертов для нового совпадающего алерта.
Если существующий алерт или группа уже существуют, поток дедупликации/обновления может продолжаться. Это позволяет избежать нарушения существующего поведения жизненного цикла инцидента, одновременно предотвращая создание новых инцидентов во время планового обслуживания.
Используйте это, когда совпадающие алерты ожидаемы и не должны отображаться как инциденты.
create_maintenance_incident#
Создаёт инцидент со статусом maintenance и прикрепляет окно обслуживания.
Это не устанавливает maintenance_suppressed=true. Флаг maintenance_suppressed означает только подавление уведомлений, вызванное suppress_notifications.
Используйте это, когда алерты обслуживания должны явно отслеживаться как инциденты.
pause_escalation_only#
Создаёт группу алертов и дочерний алерт, прикрепляет окно обслуживания и устанавливает следующее время эскалации в None.
Используйте это, когда инцидент должен быть видимым и уведомления по-прежнему могут отправляться, но эскалация не должна продвигаться, пока окно обслуживания активно.
Поведение часового пояса#
starts_at и ends_at — это значения даты и времени по «настенным часам» в выбранном часовом поясе окна обслуживания. Они не должны преобразовываться в UTC фронтендом перед отправкой в API.
Пример полезной нагрузки для окна обслуживания по Москве:
{
"starts_at": "2026-06-07T07:52:00",
"ends_at": "2026-06-07T11:37:00",
"timezone": "Europe/Moscow"
}
IncidentRelay интерпретирует это как с 07:52 до 11:37 в Europe/Moscow, а не в UTC.
Эффективный статус вычисляется динамически на основе:
starts_atends_attimezonerrulestatusenableddeleted
Сохранённый status может оставаться scheduled, в то время как API возвращает эффективный status как active или finished в зависимости от текущего времени.
Эффективный статус#
API возвращает как сохранённый, так и эффективный статус:
{
"status": "active",
"stored_status": "scheduled"
}
status — это эффективный статус, и он должен использоваться в UI.
stored_status — это значение из базы данных, полезное для отладки или аудита.
Правила расчёта статуса:
| Условие | Эффективный статус |
|---|---|
status = cancelled | cancelled |
enabled = false | Не считается активным для сопоставления |
Текущее локальное время раньше starts_at | scheduled |
Текущее локальное время между starts_at и ends_at | active |
Текущее локальное время после ends_at | finished |
| У RRULE есть текущее вхождение | active |
| У RRULE есть будущее вхождение | scheduled |
| У RRULE нет будущего вхождения | finished |
Повторяющиеся окна#
Окна обслуживания поддерживают опциональные строки RFC5545 RRULE в поле rrule.
RRULE хранится без префикса RRULE:. Если API получает префикс, он нормализует его перед сохранением.
Примеры:
| Повторение | RRULE |
|---|---|
| Ежедневно, 3 вхождения | FREQ=DAILY;COUNT=3 |
| Еженедельно, 5 вхождений | FREQ=WEEKLY;COUNT=5 |
| Ежемесячно, 2 вхождения | FREQ=MONTHLY;COUNT=2 |
| Каждый понедельник | FREQ=WEEKLY;BYDAY=MO |
Первое вхождение начинается в starts_at. Каждое вхождение использует одинаковую длительность, вычисленную как ends_at - starts_at.
Для повторяющихся окон API может возвращать текущее или следующее вхождение:
{
"occurrence": {
"status": "active",
"starts_at": "2026-06-07T07:00:00",
"ends_at": "2026-06-07T08:00:00",
"timezone": "Europe/Moscow",
"recurring": true
}
}
UI должен отображать времена вхождений для повторяющихся окон, а не только базовые starts_at и ends_at.
Сопоставление алертов и инцидентов#
Сопоставление обслуживания происходит в upsert_alert() после того, как разрешены маршрут, команда, сервис и ротация.
Решение о сопоставлении основано на:
- id группы
- id команды
- id сервиса
- id маршрута
- текущем времени в часовом поясе окна обслуживания
- полях enabled/deleted/status
Для сервисов и маршрутов active_maintenance является динамическим. Если сервис или маршрут в данный момент находится на обслуживании, сериализатор может вернуть активное обслуживание, даже если во время окна не было создано ни одного алерта.
Для алертов и инцидентов active_maintenance является прикреплённым состоянием. Существующие группы алертов не должны помечаться как обслуживание задним числом только потому, что позже стало активным новое окно обслуживания команды/сервиса/маршрута.
Правильное поведение:
| Объект | Источник active_maintenance |
|---|---|
| Сервис | Динамический поиск по текущему активному окну обслуживания |
| Маршрут | Динамический поиск по текущему активному окну обслуживания |
| Группа алертов / инцидент | Прикреплённый maintenance_window_id в группе алертов |
| Дочерний алерт | Прикреплённый maintenance_window_id в алерте |
API#
Список окон обслуживания#
GET /api/maintenance-windows?include_finished=1
Ответ:
{
"items": [
{
"id": 12,
"group_id": 1,
"group_name": "Platform",
"team_id": 2,
"team_name": "SRE",
"name": "Payments deploy",
"description": "Planned deployment",
"status": "active",
"stored_status": "scheduled",
"behavior": "suppress_notifications",
"timezone": "Europe/Moscow",
"rrule": null,
"starts_at": "2026-06-07T07:52:00",
"ends_at": "2026-06-07T11:37:00",
"occurrence": {
"status": "active",
"starts_at": "2026-06-07T07:52:00",
"ends_at": "2026-06-07T11:37:00",
"timezone": "Europe/Moscow",
"recurring": false
},
"enabled": true,
"deleted": false,
"scopes": [
{
"id": 33,
"scope_type": "service",
"service_id": 10,
"service_name": "payments-api"
}
]
}
]
}
Создание окна обслуживания#
POST /api/maintenance-windows
Content-Type: application/json
{
"name": "Payments deploy",
"description": "Planned deployment for payments-api",
"behavior": "suppress_notifications",
"timezone": "Europe/Moscow",
"starts_at": "2026-06-07T07:52:00",
"ends_at": "2026-06-07T11:37:00",
"enabled": true,
"rrule": null,
"scopes": [
{
"scope_type": "service",
"service_id": 10
}
]
}
Обновление окна обслуживания#
PUT /api/maintenance-windows/{window_id}
Content-Type: application/json
Поддерживаются частичные обновления. Если starts_at, ends_at, timezone, behavior или rrule опущены, текущие значения сохраняются.
Когда scopes опущен, существующие области действия сохраняются.
{
"name": "Updated deploy",
"description": "Deployment postponed"
}
Чтобы заменить области действия, отправьте scopes явно:
{
"scopes": [
{
"scope_type": "team",
"team_id": 2
}
]
}
Отмена окна обслуживания#
POST /api/maintenance-windows/{window_id}/cancel
Content-Type: application/json
{
"reason": "Deployment postponed"
}
Отмена устанавливает:
status = cancelled
enabled = false
deleted = false
Удаление окна обслуживания#
DELETE /api/maintenance-windows/{window_id}
Удаление является мягким удалением и устанавливает:
deleted = true
enabled = false
RBAC#
Окна обслуживания используют разрешения на запись для выбранной области действия.
| Область | Требуемое разрешение |
|---|---|
group | Доступ на запись к группе |
team | Доступ на запись к команде |
service | Доступ на запись к команде сервиса |
route | Доступ на запись к команде маршрута |
При обновлении окна:
- Если
scopesпередан, разрешения проверяются относительно новых областей действия. - Если
scopesопущен, разрешения проверяются относительно существующего владельца окна или существующих областей действия.
Журнал аудита#
Успешные действия записи создают записи аудита:
| Действие | Действие аудита |
|---|---|
| Создание | maintenance_window.create |
| Обновление | maintenance_window.update |
| Отмена | maintenance_window.cancel |
| Удаление | maintenance_window.delete |
Данные аудита включают финальное состояние окна обслуживания и полезную нагрузку запроса.
Поведение UI#
Страница окон обслуживания должна показывать:
- сводные плитки
- представление списком/таблицей
- бейдж статуса
- поведение
- значение повторения
- текущее или следующее вхождение
- начало/окончание в выбранном часовом поясе
- панель деталей
- модальное окно создания/редактирования
Выбор часового пояса должен повторно использовать общий помощник AppTimezones, используемый слоями ротации.
Фронтенд должен отправлять значения datetime-local как обычные строки по «настенным часам» через AppTimezones.normalizeDatetimeLocal(). Он не должен вызывать new Date(value).toISOString() для окон обслуживания.
Бейджи обслуживания#
Сервисы и маршруты могут динамически показывать бейджи активного обслуживания.
Группы алертов и инциденты должны показывать бейджи только тогда, когда обслуживание было прикреплено на момент обработки алерта.
Текст бейджа должен включать:
- имя окна обслуживания
- метку поведения
- время текущего или следующего вхождения
- часовой пояс
Пример подсказки:
Payments deploy · Suppress notifications · 07/06 07:52 — 07/06 11:37 Europe/Moscow
Покрытие тестами#
Тесты окон обслуживания должны покрывать:
- создание/обновление/отмену/удаление
- записи аудита
- RBAC для областей service/team/route/group
- статус активности с учётом часового пояса
- повторяющиеся окна RRULE
- расчёт вхождений
- динамические бейджи активного обслуживания сервисов и маршрутов
- прикреплённые бейджи обслуживания групп алертов
- отсутствие пометки старых групп алертов задним числом
- все виды поведения обслуживания
Матрица поведения, которую нужно держать покрытой:
suppress_notifications:
incident is created
maintenance_suppressed = true
notification queue is cleared
reminders and user-level delayed deliveries are suppressed
escalation is paused
after maintenance, one normal notification/update is scheduled
missed reminders and escalation steps are not replayed
suppress_incident:
new incident is not created
upsert_alert returns (None, False)
create_maintenance_incident:
incident is created
status = maintenance
maintenance_suppressed = false
pause_escalation_only:
incident is created
next_escalation_at = None
maintenance_suppressed = false
Ретроактивная повторная проверка#
У каждого окна обслуживания есть две независимые настройки жизненного цикла:
- Apply to existing unresolved alerts по умолчанию отключена. Если её включить, активное окно немедленно начинает влиять на совпадающие неразрешённые группы алертов, которые существовали до начала периода обслуживания. Настройка недоступна для Suppress incident, поскольку уже созданную группу нельзя ретроактивно сделать несуществующей.
- Reactivate affected alerts when this maintenance ends по умолчанию включена. Если её отключить, применённый эффект сохранится после завершения, отключения или отмены окна. Чтобы снять сохранённые эффекты, включите настройку позднее и сохраните окно. Удаление окна, исключение группы из его области действия или переход к несовместимому виду поведения всегда снимает эффекты, которые больше не относятся к окну.
Явные изменения через API согласуются немедленно. Задание планировщика обрабатывает запланированные начала, истечение и повторяющиеся периоды. Обработка идемпотентна, а перекрывающиеся окна отслеживаются независимо. Группа возобновляет работу только после прекращения последнего активного или сохранённого эффекта. Пропущенные напоминания и шаги эскалации не воспроизводятся.