Как настроить omp для разбора алертов: issue вместо сессии, подписки вместо ключей
Алерт из VictoriaLogs становится issue в репозитории продукта, его разбирает roboomp на omp по подписке через auth-broker. Пять граблей по дороге, от алерта без имени проекта до 403 Request not allowed, и проба: меньше минуты от issue до ответа бота.
Авторы: slavb18
Утром мы с idevops настроили доставку алертов в сессию проекта. Через час задумались: а почему в Claude, а не в нашу систему omp?
Внятного ответа не нашлось. Задачу «слать ошибки из почты в сессию проекта» я прочитал как «в Claude-сессию с именем проекта» и так и написал в задании для idevops. Вариант с omp даже не рассматривали.
Сессию можно выключить
Claude-сессия проекта живёт в tmux на сервере разработки. Её перезапускают, иногда она просто не запущена, и алерт, пришедший в выключенную сессию, никто не прочитает.
И разбор остаётся в переписке. Через неделю не найти, кто смотрел инцидент и что решил.
Нужен был адресат, который работает всегда и оставляет след. Такой у нас уже был, просто стоял в стороне от алертов.
roboomp уже написали авторы omp
В репозитории oh-my-pi лежит python/robomp: GitHub-бот, которым авторы omp разбирают свои issue. В их CHANGELOG полно PR от @roboomp. На новое issue он ставит метки и действует по классу: баг воспроизводит, чинит в отдельном worktree и открывает PR с разделами Repro, Cause, Fix, Verification; на вопрос отвечает комментарием. На каждое issue заводится своя сессия omp --mode rpc, и ответы в issue её продолжают.
PAT бота живёт в отдельном контейнере gh-proxy, оркестратор ходит к нему с HMAC-подписью. Агент, который исполняет команды модели, токена не видит.
У нас roboomp поднят в чарте iomp на стенде: под из двух контейнеров, наружу через шлюз открыт один путь, POST /webhook/github. Схема целиком:
алерт VictoriaLogs → alert-relay → issue в iconicompany/<project> → вебхук → roboomp → omp → комментарий или PR
По дороге было пять граблей.
Грабли 1: у алерта нет имени проекта
Правило HighErrorRate считало ошибки так:
| stats by (namespace, container) count() as error_count
В пространстве iconicompany-production живут все модули кабинета, и контейнер у каждого называется app. По письму namespace=iconicompany-production container=app не понять, чей это всплеск и куда его нести.
Зато говорит имя пода. Vector и так пишет поле pod в каждую запись лога: iomp-58dcd966d5-8c6tr. Отрезаем хеш ReplicaSet и пятисимвольный суффикс, остаётся iomp. Идея выглядит так:
| extract_regexp "^(?P<project>.+?)(-[0-9a-f]{6,10})?-[0-9a-z]{5}quot; from pod
| stats by (namespace, project, container) count() as error_count
idevops добавил ветку для StatefulSet (<имя>-<N>) и прогнал выражение по шести часам логов. iconicompany-production/italent/app, 16 ошибок, имя проекта верное.
Грабли 2: платить за API не хотелось
roboomp из коробки ходит в модели через LLM-шлюз с API-ключами. Разбор каждого алерта на Opus по API стоит денег, а подписки у нас уже оплачены.
В omp для этого есть omp auth-broker. Это хранилище учётных данных: другим omp оно раздаёт токены доступа, а refresh-токены держит у себя и никому не отдаёт. На хосте запускаем:
omp auth-broker serve --bind=<адрес docker0>:9000
В под передаются две переменные, OMP_AUTH_BROKER_URL и OMP_AUTH_BROKER_TOKEN. Проверка: контейнер без единой учётной записи спрашивает, сколько будет 6×7, и отвечает «42». Значит, подписка доехала через брокер.
Грабли 3: 403 Request not allowed
Из docker на хосте брокер работал. Из пода в кластере Anthropic ответил:
{"type": "forbidden", "message": "Request not allowed"}
Через прокси платформы с выходом в США ответ был 403, напрямую с выходом из РФ тоже 403. Без токена тот же прокси возвращал 401: сеть доходит, отказывают именно подписке по адресу выхода. Docker на хосте ходил через sing-box, и этот выход Anthropic принимает.
Первая попытка idevops пустила через sing-box весь трафик подов. Сломались входящие соединения, упал прод iagent, правку откатили. Вторая пошла через connmark: в sing-box уходят только соединения, которые открыл сам под. После неё тот же запрос из пода вернул «56».
Грабли 4: бот не отвечает сам себе
roboomp пропускает события от собственного логина, иначе он отвечал бы на свои же комментарии. Если alert-relay откроет issue токеном бота, алерт так и останется неразобранным.
Поэтому issue заводит отдельный GitHub App iconicompany-issues, установленный на все репозитории организации. На его комментарии roboomp тоже не реагирует. Это правильно: «resolved» от релея разбирать не нужно.
Грабли 5: один алерт должен давать одно issue
HighErrorRate срабатывает каждую минуту, пока ошибок больше порога. Если заводить issue на каждое срабатывание, через час репозиторий превратится в свалку.
alert-relay кладёт в тело маркер <!-- alert-fp: … --> с fingerprint алерта. Если алерт сработал снова, пока issue открыто, релей пишет комментарий, а когда алерт прошёл, добавляет «resolved». Сам релей issue не закрывает: это делает PR с исправлением или человек.
Сырых строк лога в issue нет: только тип ошибки, счёт, образцы request_id и команда logs.sh --vl 'request_id:"…"'. Репозитории приватные, но персональным данным из логов в issue всё равно делать нечего.
Проба
idevops завёл пробный алерт, iomp#2. Хронология по логам roboomp:
05:21:19 issues.opened → вебхук 202 → dispatch
05:21:34 rpc_model_pick model=anthropic/claude-opus-5-5
05:22:12 rpc_done task=triage_issue
Разбор занял 38 секунд. Ответ бота: «Проба доставки получена… Алерт тестовый (severity: info), в коде iomp чинить нечего — помечено invalid». Всё верно, пробу закрыли.
Следующий настоящий всплеск в подах iomp-* придёт issue в репозиторий, и первым его прочитает бот.
Где граница
Подписки кончаются. За неделю Codex ответил usage_limit_reached, а Gemini исчерпал квоту со сбросом через 94 часа. Когда упрётся и Claude, бот останется без модели: roboomp повторит попытку трижды и сдастся, а алерт пролежит открытым issue до человека.
Бот не видит логов. У пода roboomp нет доступа к VictoriaLogs, он разбирает текст issue и код. Для всплеска ошибок в своём модуле этого часто хватает, а причину падения Postgres так не найти.
Брокер подписок слушает на docker0, и его видит любой контейнер и под на хосте. Защищает его только bearer-токен.
Автоматизация на потребительских подписках находится в серой зоне условий провайдеров. Если аккаунт заблокируют, встанет и бот, и ручная работа на той же подписке.
Наконец, roboomp отвечает на каждое новое issue в разрешённом репозитории, а не только на алерты. В iomp это нормально. В репозитории с живой очередью задач он будет мешать.
Алерт в выключенной сессии ничем не отличается от алерта, которого не было. Issue хотя бы висит открытым, пока его кто-нибудь не закроет.