Опубликовано 30 июля 2026 г.
Почему оркестратор аварий лучше строить как граф с чекпойнтами
Почему не скрипт
Сценарий обработки аварии магистрального линка кажется линейным, пока не начнёшь его писать. Мониторинг заводит тикет, после чего нужно разобрать описание, подключиться к обоим концам линка, снять показатели оптики и конфигурацию интерфейса, сделать вывод, деактивировать порт, поставить задачу полевой службе и ждать. Ожидание может занять час или три дня, пока бригада доберётся до муфты.
Скрипт такого не переживёт. Он остановится при первом же деплое сервиса, и вместе с ним пропадёт информация о том, что порт уже деактивирован и к нему едет бригада.
Поэтому сценарий построен как граф состояний на LangGraph, а состояние графа хранится в чекпойнтах PostgreSQL. После рестарта сервиса контекст сохраняется, и процесс продолжает работу с того узла, на котором остановился.
Решение 1: LLM не подключается к устройствам
Дать модели доступ к CLI очень заманчиво, но делать этого не стоит. Обязанности в системе строго разделены:
- детерминированный код подключается по NETCONF/PyEZ, разбирает вывод и применяет конфигурацию;
- модель работает только с текстом и компактной JSON-выжимкой диагностики, по которой составляет заключение для инженера.
Все изменения в сети проходят через код, который можно прочитать и покрыть тестами. Модель влияет на формулировки и на выводы вроде «похоже на обрыв волокна», но commit она не выполняет.
У такого подхода есть приятный побочный эффект: выжимка занимает в разы меньше токенов, чем сырой вывод команд.
Решение 2: commit confirmed
Junos умеет сам откатывать конфигурацию:
# таймер автоотката ставится на самом устройстве
cu.commit(confirm=10, comment="incident <ticket>: deactivate port")
# ... инженер подтверждает в интерфейсе ...
cu.commit() # фиксация; без неё устройство откатится само
Это меняет поведение системы при сбоях. Если оркестратор упадёт, потеряет связь или ошибётся, порт вернётся в исходное состояние без нашего участия, потому что таймер работает на самом устройстве. Человек при этом остаётся в контуре, ведь подтверждает изменение именно он.
Решение 3: сначала dry-run
Первый этап проекта намеренно запущен без единого реального commit. Вместо применения конфигурации оркестратор вычисляет diff и записывает в лог, что собирался сделать:
WOULD COMMIT on <device-a>:
[edit interfaces xe-0/0/0]
+ inactive: gigether-options
Этот этап выглядит скучно, но пользы от него много. За время работы в режиме dry-run становится видно, где парсер спотыкается о нестандартные описания тикетов, где диагностика приходит к ложному выводу и каких пар концов линка не хватает в инвентаре. Всё это лучше узнать из лога, чем из инцидента.
Что пока не решено
Формат описания тикета не зафиксирован контрактом. Его формирует шаблон системы мониторинга, и этот шаблон меняется. Парсер справляется со всеми встреченными вариантами, а если список интерфейсов оказывается пустым, останавливается и зовёт человека. Этот принцип, пожалуй, главный для всей системы: автоматика имеет право отказаться от работы.