🕵️ AIOps: наш опыт внедрения HolmesGPT на трёх проектах

Алерт приходит в чат без контекста. «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 | метрика пересекла порог, алерт перешёл в pending: у правила for: 5m |
| 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 |
| Как подключена | OpenRouter | ИИ-роутер Selectel | напрямую у провайдера модели |
| Куда приходит разбор | 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 прогона в три волны, включая задачу с заранее известным ответом. Смотрели в первую очередь на недорогие модели: мониторинг не должен стоить дороже того, за чем он следит. Пару моделей классом выше добавили для сравнения.
| Модель | Прогонов | Падений | Задача с известным ответом | Цена прогона |
|---|---|---|---|---|
qwen3.7-flash | 6 | 1 | 0 из 1 | ≈ 3 ₽ |
gpt-5.6-luna | 4 | 0 | 0,5 из 2 | ≈ 6 ₽ |
deepseek-v4-flash-0731 | 16 | 0 | 4 из 4 | ≈ 16 ₽ |
minimax-m3 | 6 | 1 | 0 из 1 | ≈ 24 ₽ |
gemini-3.7-flash | 4 | 1 | 1 из 1 | ≈ 41 ₽ |
longcat-2.0 | 4 | 0 | 0 из 2 | ≈ 42 ₽ |
glm-5.2 | 6 | 0 | 2 из 2 | ≈ 54 ₽ |
deepseek-v4-pro-0813 | 6 | 0 | 2 из 2 | ≈ 77 ₽ |
Что из этого вышло:
- По одному прогону сравнивать нельзя. Разница в цене, которая на первом прогоне выглядела как порядок, на полусотне сжалась до нескольких раз: разброс внутри одной модели оказался больше разницы между моделями.
- Дешёвая модель — не значит дешёвый разбор. Половина кандидатов провалила задачу с известным ответом, причём одинаково: уверенно сообщила, что нужного компонента в кластере нет. Это был управляемый автоскейлер, которого среди 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'ы приветствуются.
Leave feedback
If you enjoy using Tuna, or on the contrary you are not happy with something, please leave feedback.
Help
We value our users and carefully review every request. If you have any problems with tuna, please contact us in one of the following ways: