
Привет, это снова Максим Королёв из Петрович-Тех — сервисный менеджер, который автоматизирует всё, до чего дотянутся руки. Сегодня расскажу, как бот для дежурств научился не ждать команды, а действовать первым.
Есть такое наблюдение, которое знакомо любому, кто хоть раз дежурил - закон Мерфи: самое неприятное случается в момент, когда ты отошёл за кофе. Именно он стал одним из главных аргументов в пользу проактивного бота, который не ждёт, пока ты нажмёшь кнопку, а сам пинает тебя о приближающихся работах и собирает участников для устранения инцидентов.
В первой статье «Дежурный» был одним из семейства Telegram-ботов для ITSM: сбой → Jira → канал, оформление за минуту вместо десяти. Во второй - когда Telegram падал в разгар аварии - я вынес логику в CORE и подключил MAX как второй мессенджер: одно ядро, два входа, паттерн reply_fn.
С тех пор бот перестал быть просто кнопкой «завести сбой», а постепенно превратился в SRE-помощника на смене. Теперь он автоматически заранее уведомляет о предстоящих регламентных работах, инициирует war room (чаты) для конкретного инцидента, по справочнику зовёт ответственных, заполняет поля в Jira и отдает статистику по сервисам - всё из мессенджера.
Дальше - о том, что я добавил, как это легло на архитектуру CORE и какие решения оказались рабочими, когда интеграций стало больше, чем кнопок в меню.
📖 Словарь - для тех, кто пропустил прошлые серии )
Дежурный - наш Telegram/MAX бот для управления инцидентами
CORE - ядро бизнес-логики на Python, не привязанное к конкретному мессенджеру
MAX - мессенджер, на который мы мигрировали с Telegram
FA-чат - (Failure Analysis) отдельный чат под конкретный инцидент, наша версия war room
reply_fn - паттерн абстракции ответа, позволяющий CORE не знать, в какой мессенджер он пишет
ITSM - управление IT-услугами; у нас реализовано через Jira
Откуда растут ноги
Мы автоматизировали дисциплину. Три реальных проблемы процесса, которые и стали точкой роста:
Проблема 1: сбои не оформлялись. До бота завести сбой в систему могло занять полчаса - или не заводили вообще: решали в рабочем чате и забывали. Теперь сбой попадает в Jira не позже чем через минуту, если его подтвердил мониторинг, или до десяти минут, если информация пришла из техподдержки. (Напомню, завести сам сбой в Jira занимало еще 10 минут до бота)
Проблема 2: поле «Время окончания сбоя». В старых тикетах поле TimeEndProblem зияло пустотой или, что еще хуже, содержало время, когда ответственный вспомнил о задаче, а не когда реально починили. MTTR можно было смело выбрасывать в мусорку. Теперь бот заполняет это поле сам.
Проблема 3: дежурный был привязан к рабочему столу. Пока ты за компом в офисе - всё ок. Но авария не спрашивает, в какое время ты за ПК. Теперь завести сбой, поднять FA-чат, позвать нужных людей и закрыть тикет можно прямо с телефона - из любой точки.
От реактивного бота к проактивному помощнику
Сначала всё было просто:
Сломалось → дежурный открыл бота → оформил сбой → пост в канал → все бегут в чат.
Потом появились новые требования:
Регламентные работы живут в Confluence, заполняют их разные люди — дежурный должен сам отловить изменение и вовремя сообщить.
Переход на MAX потребовал при параллельных сбоях создавать отдельные чаты под каждый инцидент, чтобы не смешивать всё в одну кашу.
Добавилась необходимость хранить историю устранения и смотреть быструю статистику прямо с телефона — без захода в BI-дашборд.
Бота я не переписывал. «Просто» расширил инфраструктурный слой вокруг того же core/: добавил новые services/*, фоновые воркеры в main.py и тонкий MAX-адаптер. Бизнес-логика осталась на месте - в core/creation.py и core/actions.py.
Управление регламентными работами
Календарь Confluence — ежедневная сводка в 10:00 и 18:00
Дежурному не нужно вручную мониторить Confluence в поисках новых работ. Бот раз в минуту сверяется с расписанием — на деле это легковесный запрос к API Confluence, который не создаёт нагрузки - и отправляет в MAX только те записи, которые пересекаются с текущим днем. Время дайджеста настраивается через CALENDAR_DIGEST_TIMES (по умолчанию 10:00 и 18:00) и BOT_TIMEZONE.
Пример сообщения:
📅 Регламентные работы на 01.06.2026
1. Обновление ЗУП 3.1
31.05.2026 22:00 — 01.06.2026 02:00 | ЗУП | Иванов И.
Информирование: 31.05.2026 18:00
Фильтр работает по пересечению времени проведения с текущим днём. Чтобы одна и та же запись не приходила дважды, состояние фиксируется в bot_state. На практике дайджест срабатывает 6–7 раз в неделю - только тогда, когда есть что сообщить.
«Информировать?» — вопрос от бота
Вместе с дайджестом бот смотрит на поле inform_at каждой записи и ровно в назначенное время спрашивает дежурного: «Отправить уведомление пользователям?». Одна кнопка - и сообщение уходит в нужные каналы. Если дежурный не ответил - бот напомнит ещё раз и залогирует пропуск.
Управление инцидентом (War Room)
Ротация FA-чатов
Telegram баловал нас темами внутри одного чата, а в MAX пришлось выкручиваться по-другому. Мы завели четыре чата, и бот, как опытный крупье, раскидывает инциденты по свободным «столам»:
# Переменные окружения MAX_ALARM_FA_CHAT_1_ID = "..." MAX_ALARM_FA_CHAT_2_ID = "..." MAX_ALARM_FA_CHAT_3_ID = "..." MAX_ALARM_FA_CHAT_4_ID = "..."
Если все четыре заняты — садимся за последний, но с предупреждением: «Ребята, что у вас там случилось? Четыре параллельных аварии — это перебор!»
Вызов ответственных и параллельная рассылка
Ответственных бот находит не вручную - они описаны в едином справочнике сервисов в Confluence. Для каждого сервиса там указаны ФИО, роль и ID в мессенджере. Когда открывается инцидент, бот вытягивает нужных людей из справочника и сразу пишет им в MAX по их ID - без участия дежурного.
Пример сообщения:
Мистер Петрович, добрый день.Вы назначены ответственным за данный сбой. Просьба оперативно подключиться к решению.
Мистер ТЕХ, добавляем вас для информации.В случае недоступности ответственного просьба назначить замену.
Два сообщения - и нужные люди уже в курсе. Справочник поддерживают сами команды: обновили владельца сервиса в Confluence - бот сразу начнёт звать правильного человека.
Пример параллельной рассылки при закрытии сбоя, архивация FA-чата, обновление петлокала, пост в канал и простановка времени в Jira идут параллельно. Каждая задача обернута в свой try/except, а список failed собирает имена того, что не сработало:
# Фрагмент из core/creation.py — параллельная отправка после регистрации сбоя: await asyncio.gather( _task_max_archive(), _task_petlocal(), _task_channels(), _task_jira_time_end() ) msg = f"🚨 Сбой {alarm_id} остановлен." if failed: msg += f"\n⚠️ Частичные сбои: {', '.join(failed)}." await reply_fn(msg)
Закрытие всегда завершается — даже если Jira или канал были недоступны. Дежурный видит итог одним сообщением и сразу понимает, что нужно перепроверить вручную.
При создании сбоя — небольшая асимметрия. Jira вызывается до параллельного блока: если она упала, бот честно сообщает об этом, но сбой всё равно регистрируется с локальным ID. FA-чат, канал и петлокал обрабатываются параллельно следом; ошибки там идут в лог, не прерывая основной флоу. Критичное сохранено — остальное восстанавливается.
Архивация в Jira
После закрытия инцидента бот автоматически переносит переписку из FA-чата в комментарий тикета Jira. Хронология устранения сохраняется без ручного копипаста.
Поведение при сбоях / Когда что‑то сломалось вокруг
Отказоустойчивость — тема, о которой легко говорить в архитектурных презентациях. Лучше покажу на конкретике: вот как бот ведёт себя, если внешние системы недоступны.
Общая философия одна: сбой регистрируется в любом случае. Jira упала — получаешь локальный ID и предупреждение. Confluence не отвечает в фоне — цикл продолжается с интервалом 10 секунд, в лог пишется warning. Нажал кнопку «Календарь» в неподходящий момент — видишь «🔴 Не удалось загрузить календарь» вместо пустого экрана.
Дежурный всегда знает, что сломалось и что с этим делать — бот не молчит и не делает вид, что всё хорошо.
Мониторинг и безопасность
«Дежурный» запущен в Docker с флагом --restart unless-stopped: упал процесс — контейнер поднимается сам. Логи пишутся в файлы и агрегируются отдельным веб-сервисом мониторинга. Если что-то пошло не так — это видно раньше, чем об этом сообщит дежурный.
Токены хранятся в переменных окружения, в репозиторий не попадают. Административные команды доступны только по белому списку user_id — для всех остальных они просто не существуют.
Аналитика без боли
Append-only лог
Все события бот пишет в alarm_history.jsonl — каждое событие это отдельная JSON-строка в конце файла. Ничего не удаляется, ничего не перезаписывается. Это даёт главное: статистику всегда можно пересчитать с нуля, не боясь расхождений с реальностью.
Пример записи:
{"kind":"alarm_created","alarm_id":"FA-1234","created_at":"2026-06-01T14:30:00",
"closed_at":null,"service":"WMS","jira_key":"FA-1234","issue":"Не открывается заказ"}
Файл живёт в Docker volume /app/data — бэкап и перенос между деплоями сводятся к одной команде. Ротации нет: объём лога за всё время работы остаётся скромным, а терять историю инцидентов ради экономии места не хочется.
Статистика «Другое» и быстрый доступ с телефона
Раньше категория «Другое» в отчетах была свалкой, из которой ничего нельзя было понять. Теперь бот предлагает уточнить тип при закрытии инцидента, и за несколько недель «Другое» сократилось с 40% до 12%. Вся статистика доступна прямо из мессенджера - без корпоративного VPN и BI-дашборда.
Финал миграции на MAX
Когда Telegram “падал” - а он падал именно в момент аварии (помним про закон Мёрфи) - дежурный оставался без инструмента. Архитектура с CORE и reply_fn позволила подключить MAX как основной транспорт без переписывания бизнес-логики.
Что дала архитектура
Один CORE — любой адаптер подключается без изменения логики.
Изоляция транспорта — падение мессенджера не роняет систему учёта инцидентов.
Тестируемость — бизнес-логику можно тестировать без поднятия реального бота.
Что поменялось по метрикам
За счет автоматизации оформления инцидентов и закрытия тикетов мы снизили среднее время восстановления MTTR примерно на 20%. При этом за счет более быстрого реагирования и вовлечения нужных людей тяжесть последствий инцидентов сократилась примерно на 30%.
Что дальше
Ротация четырёх FA-чатов работает — но это честный костыль. Главная боль проявляется, когда инцидент закрыт, чат заархивирован, а коллеги хотят еще раз вернуться к обсуждению постмортема или уточнить детали.
В Telegram эта проблема решалась темами внутри одного группового чата: каждый инцидент — отдельная тема, всё в одном месте, архивируй сколько угодно. Пока MAX такого функционала не предоставляет, ротация чатов остаётся единственным рабочим вариантом.
Если кто-то из команды MAX читает это - очень ждём темы в групповых чатах! Это устранит костыль и сделает историю инцидентов по-настоящему удобной. А пока, коллеги — как вы справляетесь с архивом обсуждений по сбоям в корпоративных мессенджерах без тем? Пишите в комментариях.
