
Алерт приходит в чат без контекста. «Pod перезапускается», «очередь переполнена», «Job упал» — и дежурный начинает с одного и того же: открыть дашборд, найти Pod, полистать логи, вспомнить, что выкатывали накануне. Это первичное расследование почти всегда одинаково по форме, и именно его мы отдали агенту.
Мы внедрили HolmesGPT — open-source SRE-агента — в шести Kubernetes-кластерах: в собственном кластере Tuna и в пяти кластерах наших клиентов, которые у нас на DevOps-сопровождении. Клиентских проектов два: один живёт в российском облаке, другой — зарубежный, на двух облаках.
По ходу внедрения нам пришлось написать собственный инструмент — holmes-alert-bridge: готовой связки с Alertmanager в открытой версии HolmesGPT нет. Он принимает алерт, отсеивает лишнее, передаёт агенту вместе с контекстом и приносит разбор в чат; код мы открыли. Теперь через несколько минут после алерта рядом с ним лежит гипотеза: что происходит, почему и что проверить. Ниже — как это устроено, сколько стоит и чему мы научились за первый месяц.
Коротко
HolmesGPT — open-source SRE-агент из CNCF Sandbox: сам ходит в Kubernetes, метрики и логи и формулирует гипотезу о причине алерта.
Штатной push-интеграции с Alertmanager в открытой версии нет, поэтому мы написали и открыли holmes-alert-bridge.
Агент у нас только читает и только строит гипотезы. Чинить он ничего не может — мы не дали.
Разбор занимает 2–3 минуты и стоит несколько рублей или 10–30 центов, в зависимости от модели.
До внедрения проверьте свои логи на пароли, токены и персональные данные: всё, что прочитает агент, уйдёт провайдеру модели.
Зачем это нужно
Правило алерта знает только то, что написано в его выражении: метрика пересекла порог. Почему — оно не знает, а аннотация с советом «поднимите лимит» написана один раз и на все случаи. Дежурный получает сигнал и тратит первые минуты на сбор контекста, прежде чем начать думать.
HolmesGPT закрывает именно этот отрезок. Получив алерт, человек не начинает расследование с нуля: у него уже есть версия происходящего, список проверенного и два-три шага, чтобы версию подтвердить или опровергнуть. По свежим эпизодам от срабатывания алерта до готового отчёта в чате проходит около четырёх минут по медиане — вместе с задержкой группировки в Alertmanager.
Вот как это выглядит на одном реальном эпизоде в Tuna — подробно он разобран ниже, в кейсах. График построен в нашей Grafana по обычным метрикам: выражение из правила алерта, серия ALERTS и счётчики bridge.

Время |
Что произошло |
|---|---|
14:34:49 |
метрика пересекла порог, алерт перешёл в |
14:39:49 |
алерт сработал; через 10 секунд Alertmanager отправил сообщение в чат и копию в bridge |
14:39:59 |
Holmes начал расследование |
14:42 |
всплеск закончился, алерт погас |
14:44:07 |
разбор с гипотезой в чате — через 4 минуты 17 секунд после срабатывания алерта |
От начала проблемы до гипотезы прошло чуть больше девяти минут, и пять из них — ожидание самого правила. Алерт к этому моменту уже погас, но теперь понятно, что это было и нужно ли что-то делать.
Три решения, которые мы приняли сразу.
Только гипотезы, без автопочинки. Сам HolmesGPT умеет больше: если дать ему права на запись и описать действия в skills, он будет не только искать причину, но и устранять её. Мы этого сознательно не включаем: у агента права только на чтение, а его ответ помечен как гипотеза, а не диагноз.
Оценка качества — честно оценка. По нашему опыту гипотеза оказывается верной в 90–95 % случаев. Это именно оценка по ощущениям от дежурств, а не результат замера: систематически сверять разборы с реальными причинами инцидентов мы пока не начинали.
Каждый алерт стоит денег. Это неожиданно полезный побочный эффект. Когда у повторяющегося алерта появляется строчка расходов, его гораздо сильнее хочется починить системно, а не закрывать глазами в сотый раз.
Контуры получились разными, и это удобно для сравнения:
Tuna |
Проект в российском облаке |
Зарубежный проект на двух облаках |
|
|---|---|---|---|
Кластеров |
1 |
2 |
3 |
Модель |
DeepSeek V4.1 Flash |
DeepSeek V4.1 Flash |
Claude Sonnet 5 |
Как подключена |
напрямую у провайдера модели |
||
Куда приходит разбор |
Telegram |
Slack |
Slack |
Что такое HolmesGPT
HolmesGPT — агент для расследования проблем в облачной инфраструктуре. Лицензия Apache 2.0, проект создан в Robusta.dev и развивается при заметном участии Microsoft, с октября 2025 года входит в CNCF Sandbox. На GitHub у него около 3,5 тысячи звёзд, релизы выходят раз в одну-две недели — за последние четыре месяца их было тринадцать. Мы используем версии 0.41–0.42.
Внутри — классический агентный цикл: модель получает вопрос, решает, какой инструмент вызвать, читает результат, вызывает следующий — и так до ответа. На один алерт уходит два-три десятка вызовов инструментов, в запутанных случаях — под сотню. Из чего это собрано:
Toolsets — наборы инструментов: Kubernetes (ресурсы, логи Pod’ов, метрики), Prometheus, VictoriaLogs, ArgoCD, Grafana, проверки сетевой доступности и другие. Свой набор описывается YAML-файлом со скриптами, кодом на Python или подключается как MCP-сервер.
Skills — то, что раньше называлось runbooks: файл
SKILL.mdс именем и описанием. Агент видит только описания, а сам текст подгружает по необходимости. Здесь живёт знание о вашем проекте.Модель — любая, до которой дотягивается litellm: Anthropic, OpenAI, Azure, Bedrock, Gemini, Ollama, OpenRouter и любой OpenAI-совместимый API.
Способы запуска — CLI с интерактивной консолью, HTTP-сервер с Helm-чартом, оператор для Kubernetes (пока alpha), Python SDK.

Как мы его ставим
Основной сценарий — внутри Kubernetes, отдельная инсталляция на каждый кластер. Так не нужно строить сложную авторизацию: агент работает под ServiceAccount своего кластера с правами на чтение и ничего, кроме этого кластера, не видит. Запустить Holmes на выделенном сервере тоже можно — из CLI или docker-compose с kubeconfig, — но в кластере проще.
Минимальный values.yaml:
additionalEnvVars: - name: MODEL # ключ записи из modelList value: fast - name: OVERRIDE_MAX_CONTENT_SIZE # окно контекста, см. «Грабли» value: "400000" - name: OPENROUTER_API_KEY valueFrom: secretKeyRef: name: holmes-llm key: OPENROUTER_API_KEY modelList: fast: model: openrouter/deepseek/deepseek-v4.1-flash api_key: "{{ env.OPENROUTER_API_KEY }}" temperature: 0.5 extra_body: provider: zdr: true # см. «Данные и безопасность» toolsets: kubernetes/core: enabled: true kubernetes/logs: enabled: true prometheus/metrics: enabled: true # + config с адресом Prometheus victorialogs: enabled: true # + config с адресом и доступом skills: enabled: true # всё, что умеет писать или ходить наружу, выключаем явно bash: enabled: false internet: enabled: false robusta: enabled: false enableTelemetry: false
helm repo add robusta https://robusta-charts.storage.googleapis.com helm upgrade --install holmes robusta/holmes -n holmes --create-namespace -f values.yaml
По ресурсам это скромно: Holmes у нас занимает около полугигабайта памяти, всплески дают инструменты вроде kubectl на больших списках.
Не только алерты
Агента можно спрашивать и руками — это удобно, когда алерта нет, а вопрос есть. Через CLI (holmes ask "...") или прямо у инсталляции в кластере:
kubectl -n holmes port-forward svc/holmes-holmes 8080:80 curl -s localhost:8080/api/chat -H 'Content-Type: application/json' \ -d '{"ask": "Почему Pod checkout в namespace shop перезапускается?"}' | jq -r .analysis
В консоли это выглядит так:

Мост к Alertmanager
Здесь нас ждал сюрприз. В открытой версии HolmesGPT умеет только забирать алерты: команда holmes investigate alertmanager разово читает активные алерты и разбирает их. Приёмника вебхуков в HTTP-сервере нет, у оператора триггер по алертам пока в планах, а автоматический разбор в продукте Robusta идёт через их SaaS-платформу.
Нам же нужно было простое: Alertmanager шлёт вебхук, через пару минут в том же чате появляется разбор. Так появился holmes-alert-bridge — небольшой сервис на Go под лицензией Apache 2.0.


Почти всё, что делает bridge, — это защита бюджета и нервов:
Сразу отвечает 200. Holmes думает минуты, Alertmanager столько не ждёт. Расследование идёт в фоне.
Allowlist по имени алерта. Пустой список — сервис откажется стартовать: расследовать всё подряд означает счёт от провайдера за каждый флапающий алерт.
Дедупликация по эпизоду. Эпизод — это имя алерта, namespace и время начала. Повторные доставки того же эпизода игнорируются. Для упавших CronJob’ов ключом служит имя CronJob’а, иначе каждый новый запуск был бы новым эпизодом.
Троттлинг. По умолчанию не больше двух расследований в час и десяти в сутки. Это не тюнинг производительности, а потолок расходов.
Один воркер. Расследования идут строго по очереди, реплика у сервиса всегда одна.
Таймаут с разрывом стрима. Ответ агента читается потоком, поэтому через 15 минут bridge рвёт соединение, и Holmes останавливается на границе текущего шага, а не дорешивает задачу в фоне за ваш счёт.
Состояние в базе. Отметки дедупликации и троттлинга переживают рестарт: SQLite по умолчанию, PostgreSQL или MySQL — если база уже есть.
Доставка. Slack, Telegram, почта, произвольный вебхук. Markdown от агента переводится в разметку канала, длинный разбор не обрезается, а разбивается на части. Под разбором — длительность, стоимость, модель и ссылка на полную запись в VictoriaLogs.
Насколько это отсекает: в Tuna за первый месяц Alertmanager доставил в bridge 645 алертов, а до агента дошёл 41 — остальное сняли дедупликация, allowlist и троттлинг. В других контурах картина та же: больше 90 % доставок расследования не запускают.
Готовый отчёт выглядит так — это та же картинка, что в начале статьи:

Установка
Нужен работающий HolmesGPT в кластере и список алертов, которые имеет смысл расследовать.
helm repo add holmes-alert-bridge \ https://gitlab.com/api/v4/projects/yuccastream%2Fholmes-alert-bridge/packages/helm/stable helm upgrade --install holmes-alert-bridge holmes-alert-bridge/holmes-alert-bridge \ -n holmes -f values.yaml
config: alertAllowlist: - KubernetesPodCrashLooping - KubernetesJobFailed - InstanceDown holmesUrl: http://holmes-holmes.holmes.svc:80 holmesModel: fast # ключ из modelList Holmes promptLang: ru clusterName: prod environment: production costCurrency: USD maxPerHour: 2 maxPerDay: 10 telegram: chatId: "-1001234567890" victoriaLogs: uiUrl: https://logs.example.com streamFilter: '{app="holmes-alert-bridge",cluster="prod"}' existingSecret: holmes-alert-bridge-secrets # TELEGRAM_BOT_TOKEN и остальные
На стороне Alertmanager — получатель и маршрут с continue: true, чтобы разбор дополнял обычное оповещение, а не заменял его:
receivers: - name: holmes webhook_configs: - url: http://holmes-alert-bridge.holmes.svc:8080/webhook send_resolved: false route: routes: - receiver: holmes matchers: - severity =~ "warning|critical" continue: true
Если в вашем дереве маршрутов нет явного catch-all, continue может отнять доставку у алертов, которые раньше падали в корневой получатель. Тогда проще добавить bridge вторым webhook_configs к уже существующим получателям — у нас так сделано в четырёх кластерах из шести, а отбор целиком лежит на allowlist.
Остальное — каналы, секреты, свои шаблоны промптов и сообщений — описано в INSTALL.md.
Модели и бюджет
Как выбирали
Пилот начался в проекте в российском облаке, и первый же прогон — на glm-5.2 — отрезвил: на один вопрос ушло больше миллиона входных токенов, а расследование обошлось в десятки рублей. Агент делает десятки ходов, и каждый ход заново пересылает модели всю накопленную историю.
Тогда мы устроили отбор: восемь моделей, 52 прогона в три волны, включая задачу с заранее известным ответом. Смотрели в первую очередь на недорогие модели: мониторинг не должен стоить дороже того, за чем он следит. Пару моделей классом выше добавили для сравнения.
Модель |
Прогонов |
Падений |
Задача с известным ответом |
Цена прогона |
|---|---|---|---|---|
|
6 |
1 |
0 из 1 |
≈ 3 ₽ |
|
4 |
0 |
0,5 из 2 |
≈ 6 ₽ |
|
16 |
0 |
4 из 4 |
≈ 16 ₽ |
|
6 |
1 |
0 из 1 |
≈ 24 ₽ |
|
4 |
1 |
1 из 1 |
≈ 41 ₽ |
|
4 |
0 |
0 из 2 |
≈ 42 ₽ |
|
6 |
0 |
2 из 2 |
≈ 54 ₽ |
|
6 |
0 |
2 из 2 |
≈ 77 ₽ |
Цена прогона — оценка сверху, без учёта кэша: с кэшем разбор обходится в разы дешевле. Половина балла — частично верный ответ. Выборка небольшая: от 4 до 16 прогонов на модель.
Что из этого вышло:
По одному прогону сравнивать нельзя. Разница в цене, которая на первом прогоне выглядела как порядок, на полусотне сжалась до нескольких раз: разброс внутри одной модели оказался больше разницы между моделями.
Дешёвая модель — не значит дешёвый разбор. Половина кандидатов провалила задачу с известным ответом, причём одинаково: уверенно сообщила, что нужного компонента в кластере нет. Это был управляемый автоскейлер, которого среди Pod’ов действительно не видно.
Победила
deepseek-v4-flash: 16 прогонов без единого падения и верный ответ во всех четырёх попытках. Она не самая дешёвая в списке, но обе модели дешевле неё задачу с известным ответом не решили.
В продакшене первая версия продержалась недолго. В одном контуре примерно каждый пятый разбор не укладывался в 15 минут. В другом модель зацикливалась на повторных вызовах инструментов и уходила в таймаут, а её версию к тому же сняли с поддержки у провайдера. В середине сентября мы переехали на DeepSeek V4.1 Flash. По прайсу она в разы дороже, средний разбор тоже подорожал — зато разборы стали заканчиваться, и заканчиваться быстро.
Оговорка: чистого сравнения «до и после» у нас нет. В те же дни мы поправили инструкции для инструментов и обновили bridge, и вклад каждого изменения по отдельности не измеряли.
Что получилось
Tuna |
Проект в российском облаке |
Зарубежный проект |
|
|---|---|---|---|
Модель |
DeepSeek V4.1 Flash |
DeepSeek V4.1 Flash |
Claude Sonnet 5 |
Успешных разборов |
22 из 22 |
92,9 % |
15 из 15 |
Длительность, медиана |
2–3 минуты |
2–3 минуты |
около минуты |
Цена разбора |
около 10 центов |
несколько рублей |
25–30 центов |
Окно — 30 дней до 6 октября 2026 года; успешность считаем после стабилизации — смены модели и выхода bridge v0.11. Рублёвые суммы — оценка: цены модели в конфигурации у нас расходились с прайсом роутера.
Счёт за месяц — единицы долларов на проект: около четырёх долларов в Tuna по биллингу провайдера, четыре-пять в зарубежном проекте на три кластера и меньше тысячи рублей на два кластера в российском облаке. В Tuna мы сверили оценку стоимости, которую bridge берёт из ответа Holmes, с биллингом ключа — сошлось до цента.
Почему хватает дешёвой быстрой модели
Разбор алерта — это около миллиона токенов, из которых сама модель генерирует пару десятков тысяч. Всё остальное — накопленная история, которую агент пересылает на каждом ходу. Отсюда три свойства модели, которые важнее цены за миллион токенов по прайсу:
Дешёвое чтение кэша. По нашей оценке, из кэша читается около 90 % входных токенов. Это оценка обратным счётом, а не цифра из биллинга, но порядок понятен: экономика разбора держится на кэше.
Большое окно контекста. При окне в 200 тысяч токенов Holmes начинал сжимать историю уже около 126 тысяч, после сжатия заново запрашивал собранное — и разбор становился дольше и дороже. С окном в 400 тысяч сжатия прекратились.
Надёжный вызов инструментов. Модель, которая время от времени пишет вызов инструмента текстом или ломает JSON аргументов, обходится дороже любой «умной».
Где стоит модель подороже: в зарубежном проекте с первого дня работает Claude Sonnet 5, подключённая напрямую у провайдера. Сравнения моделей там мы не устраивали. Алертов в этом проекте мало — большинство гаснет раньше, чем Alertmanager успевает их отправить, — поэтому в абсолютных цифрах дорогая модель обходится в те же несколько долларов в месяц.
Два практических совета:
Ставьте месячный лимит на ключ провайдера. Троттлинг bridge защищает только путь от Alertmanager; ручные вопросы и другие клиенты агента идут мимо него.
Сверяйте цены. Если модель описана в
modelListсо своимиinput_cost_per_tokenиoutput_cost_per_token, стоимость в отчёте считается по ним. У нас эти числа расходились с прайсом роутера, и суммы в чате оказывались неверными.
Данные и безопасность
Сначала посмотрите, что лежит в ваших логах
При внешнем API всё, что прочитает агент, уходит провайдеру модели: строки логов, манифесты, события, метки метрик. До внедрения проверьте свои приложения и логи на пароли, токены и персональные данные. Агент не отличает секрет от обычной строки: содержимое лога попадает в запрос к модели как есть.
Что мы советуем сделать до первого алерта.
1. Аудит логов. У агента есть доступ к логам Pod’ов всего кластера. Если приложение пишет в лог токен авторизации или адрес почты клиента, это надо чинить в приложении — и сделать это до подключения агента, а не после.
2. Zero Data Retention — в двух местах. ZDR означает, что провайдер не хранит ваши запросы. В OpenRouter его стоит включать и настройкой аккаунта, и параметром запроса provider.zdr: true (в примере выше он уже есть). Настройку аккаунта не видно из конфигурации кластера, а параметр запроса зависит от того, донесла ли его цепочка библиотек, — вместе они страхуют друг друга. Проверяется это живым запросом: при включённом ZDR обращение к провайдеру вне ZDR-списка возвращает ошибку ZDR violation.
3. Роутер, у которого ZDR есть на деле. Годится любой роутер с API, но лучше тот, чьи провайдеры действительно дают нулевое хранение. OpenRouter публикует живой список таких endpoint’ов, и на него стоит посмотреть до выбора модели: одна и та же модель у разных хостеров может быть как в списке, так и вне его.
4. Права только на чтение. В чарте HolmesGPT по умолчанию включены наборы bash и internet — выключайте их явно. Secret’ы чарт агенту читать не даёт, но запрет на изменения должен обеспечивать RBAC, а не фраза в промпте.
5. Правило в skill’е — не техническое ограничение. Мы пишем агенту «работай с агрегатами, не цитируй персональные идентификаторы», и он в целом слушается. Но это текст, а не барьер, и полагаться на него как на защиту нельзя.
6. Закройте API самого Holmes. По умолчанию /api/chat доступен без аутентификации любому Pod’у кластера, а вместе с ним — права агента на чтение и ваш бюджет у провайдера. Варианты — NetworkPolicy (проверьте, что ваш CNI их исполняет) или ключ HOLMES_API_KEY.
Теперь честно про нас. В Tuna стоит OpenRouter с ZDR, включённым в обоих местах, — проверено живыми запросами. В проекте в российском облаке запросы идут через ИИ-роутер Selectel: отдельной настройки ZDR там нет, и мы опираемся на заявленную провайдером политику обработки запросов. В зарубежном проекте модель подключена напрямую у её провайдера: политика хранения там — свойство договора и настроек организации, из конфигурации кластера её не видно, и это отдельный пункт в нашем чек-листе.
VictoriaLogs
Логи — главный источник данных для агента: на них приходится больше всего вызовов инструментов. Мы рекомендуем VictoriaLogs: она быстрая, а её HTTP API достаточно прост, чтобы им одинаково легко пользовались и скрипты, и агенты. В HolmesGPT для неё есть встроенный toolset.
Мы специально переехали на VictoriaLogs — два проекта с Loki, один с Graylog — именно ради того, чтобы логи стали доступны программно: и нашим рабочим агентам, и Holmes. В итоге хранилище играет для агента сразу три роли:
источник логов приложений;
собственная память: skill ищет прошлые разборы в логах bridge;
адрес ссылки «полный разбор» под сообщением в чате.
Вот запись, на которую ведёт ссылка «полный разбор» под сообщением из кейса про очередь TCP. Служебные поля коллектора убраны, текст разбора обрезан:
{ "_time": "2026-10-03T10:44:06.793Z", "_msg": "investigation done", "cluster": "yucca", "alertname": "TCPListenQueueOverflow", "episode": "TCPListenQueueOverflow//2026-10-03T10:39:49Z", "starts_at": "2026-10-03T10:39:49Z", "duration": "4m7s", "cost": "0.0542736192", "cost_currency": "USD", "prompt_tokens": "1119866", "completion_tokens": "20957", "total_tokens": "1140823", "analysis": "I found a skill named **tuna-network-attacks** и использовал его для разбора…" }
Разборы лежат в логах обычными полями, поэтому бюджет считается одним запросом. Например, сколько стоил каждый алерт за месяц:
_time:30d {app="holmes-alert-bridge"} "investigation done" | stats by (alertname) count() n, sum(cost) total, avg(cost) avg | sort by (total) desc
Одной установки toolset’а мало — агенту нужно описать схему полей. Имена зависят от коллектора: у vector namespace лежит в kubernetes.pod_namespace, у fluent-bit — в kubernetes.namespace_name, и модель этого не угадает. Наши инструкции к toolset’у логов по смыслу такие:
Log fields (collector: vector): app, cluster, kubernetes.pod_namespace, kubernetes.pod_name, level, _msg A bare "phrase" is searched in _msg only. To filter by a field write field:="value". Pass `start` only. Never pass `end`, null or "now". One failed call does not mean the storage is down: fix the query and retry once. Do not name a root cause from a tail of 50 lines. Count first: ... | stats by (level, app) count() n
Промпты и skills
Знание о проекте у нас разложено по четырём слоям.
Рамки расследования задаёт системный промпт запроса — он встроен в bridge, есть на русском и английском. Смысл: ты расследуешь алерт без человека в диалоге; только чтение; держись namespace алерта; твой вывод — гипотеза; ответ уйдёт одним сообщением в чат — вердикт, наиболее вероятная причина, два-три шага проверки.
Паспорт кластера — skill, который агент читает первым. Это самый полезный текст во всей настройке. Обезличенный пример по структуре наших:
--- name: prod-cluster description: How the production cluster is built, what is normal here and what is not. Read this FIRST for any investigation in this cluster. --- ## What this cluster is Managed Kubernetes, production of an online store. Node pools: `apps` (stateless services, autoscaled) and `infra` (monitoring, ingress, ArgoCD). ## Where the workloads live - `shop` — API and web frontend, the only namespace that serves customers. - `jobs` — CronJobs: exports, reports, cleanup. A single failed run is rarely urgent. - `monitoring`, `ingress`, `argocd` — infrastructure. ## Databases are outside the cluster PostgreSQL and Redis are managed services. You cannot kubectl into them: use their metrics and `tcp_check`. ## ArgoCD owns the infrastructure A manual change will be reverted. Never suggest `kubectl edit`. ## What you may read, and what you may not Application logs may contain customer data. Work with counts and aggregates, never quote e-mails, phone numbers or order contents. ## What is normal here - Nodes of the `apps` pool are replaced every night: short NotReady and evictions between 02:00 and 03:00 UTC are expected. - `jobs/report-*` retries up to three times. Only the third failure matters. ## How to fill tool parameters Time range: pass `start` only. Log fields: see the logs toolset instructions. ## Blaming an external provider is a claim you can check Before writing "the payment gateway is down", run `tcp_check` against it and compare the error rate with the previous day. ## What always deserves escalation Anything in `shop` that lasts longer than 10 minutes; any sign of data loss.
Skills под типы алертов и сервисы. Скелет один и тот же:
--- name: edge-triage description: Investigate EdgeDown and EdgeHighErrorRate. The edge proxies run on plain hosts OUTSIDE Kubernetes, so kubectl is useless here. --- ## What the edge is # слой → где живёт → systemd-unit → job метрик ## How the alert reaches you # выражение правила, что значат метки ## Workflow # 4–6 шагов с готовыми запросами LogsQL и PromQL ## Synthesize # что обязано быть в ответе, ссылки на дашборды
Подсказка в самом правиле. Аннотации алерта попадают в вопрос агенту целиком. Поэтому короткую инструкцию можно положить прямо в правило отдельной аннотацией, которую шаблон сообщения в чате не выводит:
- alert: KubernetesJobFailed annotations: summary: "Job {{ $labels.job_name }} failed" ai: >- Jobs in the `jobs` namespace are retried by the scheduler. Check whether the next run succeeded before calling it an incident.
Что мы поняли, пока это писали:
Тексты skills — на английском, ответ — на русском. Это сочетание у нас работает без нареканий.
Инструмент, не названный в skill’е, почти не вызывается. Мы подключили агенту ArgoCD, Grafana и TCP-проверки — и он ими практически не пользовался. Поэтому в паспорте кластера стоит держать раздел «инструменты, о которых ты забываешь», а в skills — называть нужный инструмент прямо в шагах.
Правило надо класть туда, где модель его точно прочтёт. Запрет передавать
end: "now"стоял в инструкциях toolset’а логов, а модель всё равно его писала — десятки упавших вызовов. Помогло только общее правило в паспорте кластера; теперь оно встроено и в системный промпт bridge.Сломанные toolset’ы надо выключать явно. Встроенные наборы в статусе
failedвсё равно попадают в системный промпт, и агент пытается ими пользоваться.«Виноват внешний сервис» — это проверяемое утверждение. Пока у агента не было инструмента сетевой проверки и требования проверять самому, разборы недоступности партнёрского API заканчивались советом «сделайте curl».
Skill, который месяц ничего не находил
Самая поучительная наша ошибка. У агента есть skill «не разбирали ли мы это уже»: перед расследованием он ищет в логах bridge прошлые разборы того же алерта. Идея хорошая, и в Tuna агент обращался к этому skill’у чаще, чем к любому другому.
В двух контурах из трёх он месяц искал не по тому полю. Запрос в skill’е искал имя алерта голой фразой, а голая фраза в LogsQL ищется только в тексте сообщения — где лежит ровно investigation done и больше ничего:
{app="holmes-alert-bridge"} "investigation done" "KubernetesJobFailed" → 0 записей {app="holmes-alert-bridge"} "investigation done" alertname:="KubernetesJobFailed" → 73 записи
Агент выполнил сотни таких запросов и каждый раз честно писал «прошлых разборов нет» — в том числе про эпизод, который за месяц был разобран девятнадцать раз. Никакой ошибки при этом не возникало: пустой ответ — тоже ответ. В третьем контуре запрос был написан с фильтром по полю, и там память работала: агент ссылался на свои прошлые выводы и даже поправлял их.
Исправление — одна строка. Мораль шире: skill для агента надо тестировать как код. Каждый запрос из него стоит хотя бы раз выполнить руками и убедиться, что он что-то находит.
Кейсы
Очередь TCP переполнена, но sysctl тут ни при чём
На одном из серверов Tuna сработал TCPListenQueueOverflow: ядро отбрасывает входящие соединения, потому что очередь слушающего сокета переполнена. В описании алерта зашит стандартный совет — поднять net.core.somaxconn или backlog в приложении.


Holmes за четыре минуты и пять центов выяснил другое:
Переполнение есть только на одном хосте, за предыдущие семь дней — ноль. Счётчик SYN cookies почти не вырос, значит, это не SYN-флуд.
Слушатель, который перестал принимать соединения, —
sshd. В его журнале нашлись записи о началеMaxStartups throttlingи об отброшенных соединениях — около двух тысяч за эпизод.Источник — два адреса и порядка 180 попыток входа с несуществующими логинами за 15 минут.
Ресурсы хоста в норме, остальные сервисы на нём и соседние хосты не задеты.
К моменту разбора алерт уже погас, порты отвечают.
Вердикт: кратковременная потеря TCP-соединений из-за флуда SSH-подключений. Очередь переполнилась, потому что sshd упёрся в собственное ограничение MaxStartups, а не потому, что хосту не хватило ресурсов; net.core.somaxconn здесь ни при чём. И отдельное наблюдение: fail2ban такой поток не отсекает, потому что соединения рвутся до этапа аутентификации.
Совет из описания алерта увёл бы дежурного в сторону. А на скриншоте заодно видны две наши болячки из этой статьи: фраза «прошлых разборов этого алерта нет» — тот самый сломанный skill — и служебная преамбула агента в начале сообщения.
VirusTotal: шум или сигнал
У нас есть открытый инструмент, который следит за тем, как наши IP-адреса, публикуемые домены и хэш-суммы сборок выглядят в базах антивирусов и спам-листах. Задача — отреагировать раньше, чем пользователь получит предупреждение «ваш софт вредоносный».
Срабатывания у него бывают двух сортов. Чаще всего это шум: один-два малоизвестных движка пометили адрес туннельного сервера. Изредка — настоящий сигнал. Теперь каждое срабатывание дополняется вердиктом Holmes: отдельный skill объясняет ему, какие движки считать шумом и какие сочетания признаков — поводом звать человека.

В этом примере агент сначала признал, что срабатывание по адресу — шум движков малого охвата. Но заметил в отчёте новый файл с заметным числом детектов, трафик которого идёт на туннельный поддомен, — и эскалировал: через туннель, вероятно, раздаётся вредоносный файл, нужен человек. Заодно проверил, что прошлых вердиктов по этому файлу нет, и что туннель сейчас не используется, — то есть владельца надо искать по учётной записи.
Этот путь идёт к Holmes напрямую, мимо bridge, — и это ещё одна причина держать лимит на ключе провайдера.
Ещё два коротких
Агент разобрал собственное падение. После обновления новый Pod Holmes ушёл в CrashLoop, и алерт об этом достался старому Pod’у. Тот нашёл отказ в доступе к новому пути в хранилище секретов, сравнил окружение двух ревизий и отметил, что сервис при этом не лежит, потому что старый Pod жив. Полторы минуты, десять центов.
Один алерт — две разные причины. В проекте в российском облаке агент связал нехватку памяти на прерываемом узле с плановой заменой соседнего узла и нашёл Pod, который потреблял заметно больше запрошенного. Через два дня на том же алерте он назвал другую причину — совпавшие расписания CronJob’ов — и явно написал, чем этот эпизод отличается от предыдущего.
Грабли
Висящий алерт разбирается заново каждые сутки. Окно дедупликации по умолчанию — 24 часа, и если алерт горит дольше, он честно разбирается ещё раз. В одном контуре на такие повторы уходила почти половина бюджета, причём половину разборов в проде давали несколько хронически падающих CronJob’ов. Лечится с двух сторон: увеличить окно (dedupWindow) до нескольких суток и наконец починить то, что горит неделями, — счёт за разборы очень мотивирует.
Служебные вставки агента в сообщениях. Holmes оставляет в ответе заготовки под встраивание графиков в интерфейс Robusta: блоки вида << {"type": "promql", ...} >> и картинки со схемой tool-image://. В чате это выглядит как мусор, и встречалось оно больше чем в половине разборов. Начиная с v0.11.1 bridge вырезает их перед отправкой. Преамбулу «I found a skill named…» в начале ответа убрать сложнее: это формат самого Holmes при работе со skills, правило в системном промпте на неё не действует.
Модель ломает аргументы инструментов. Время от времени модель отдаёт аргументы вызова не объектом, а строкой с JSON внутри. Holmes на этом падает с ошибкой валидации, и разбор теряется целиком — в одном контуре это была самая частая причина неудачных разборов. Bridge в таких случаях повторяет разбор сразу, если позволяет бюджет, а с v0.11.1 пишет в лог, когда повтор не состоялся. В конце сентября эти падения прекратились сами, без изменений с нашей стороны.
Алерты на сам bridge. Месяц у нас не было ни одного правила на метрики bridge: о затроттленных алертах и таймаутах можно было узнать только из логов. Минимум:
- alert: HolmesBridgeInvestigationsFailing expr: increase(holmes_bridge_investigations_total{status!="ok"}[1h]) > 0 - alert: HolmesBridgeThrottling expr: increase(holmes_bridge_alerts_throttled_total[1h]) > 0
В allowlist самого bridge эти алерты добавлять не надо.
Цены в modelList ломают окно контекста. Стоит вписать модели свои цены — и Holmes 0.41 перестаёт понимать размер её окна и падает на первом же вызове инструмента. Обходится переменной OVERRIDE_MAX_CONTENT_SIZE. В 0.42 падения уже нет, но окно по умолчанию — 200 тысяч, и для длинных разборов его всё равно стоит задать явно.
Инструменты падают по памяти. kubectl на больших списках упирался не в лимит Pod’а, а в лимит памяти на один вызов инструмента. Он задаётся отдельно — TOOL_MEMORY_LIMIT_MB.
Парные правила дают два разбора. Если в allowlist стоят и общее правило, и его специализированный двойник, один инцидент разбирается дважды с разницей в секунды. Оставляйте одно из пары.
«Успешный» разбор без единого запроса. На старой модели дважды случилось так: агент прочитал пару skills, не сделал ни одного запроса к данным и уверенно написал, что у него нет доступа к метрикам. Формально разбор успешен, стоит он доли цента. Аномально дешёвый разбор — повод посмотреть на него глазами.
И одна находка со знаком плюс: group_wait — бесплатный фильтр. В зарубежном проекте Alertmanager ждёт три минуты перед первой отправкой, и из десятков эпизодов в проде до агента доехали единицы — всё, что живёт минуту-две, гаснет раньше.
Что дальше
Чего мы пока не измерили — и не хотим делать вид, что измерили:
качество гипотез — 90–95 % остаются оценкой, пока мы не разметим выборку разборов против реальных причин инцидентов;
эффект для людей — насколько сократилось время разбора инцидента.
Что хочется сделать в bridge:
метрику потраченной суммы, чтобы бюджет было видно в Grafana рядом с остальными счётчиками;
аккуратнее обходиться с висящими алертами — растущий интервал повторного разбора вместо фиксированного окна;
ключ API для запросов к Holmes;
ссылку на разбор не только в VictoriaLogs, но и в Loki, Elasticsearch и других хранилищах;
новые каналы доставки — Mattermost, Microsoft Teams, Discord.
Чего в bridge не будет: параллельных расследований, правил маршрутизации (это работа Alertmanager) и автоматической починки.
Код, чарт и документация — в репозитории holmes-alert-bridge. Если попробуете у себя — расскажите, что получилось: issue и merge request’ы приветствуются.