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

7 октября 2026 г. · 28 мин. чтения

![Окно Slack: алерт KubernetesJobFailed от Alertmanager и под ним разбор Holmes с вердиктом и шагами проверки](/blog/img/2026-10-07-holmesgpt-aiops/slack-window.png)

Это пример алерта и его разбора Holmes в Slack.

Алерт приходит в чат без контекста. «Pod перезапускается», «очередь переполнена», «Job упал» — и дежурный начинает с одного и того же: открыть дашборд, найти Pod, полистать логи, вспомнить, что выкатывали накануне. Это первичное расследование почти всегда одинаково по форме, и именно его мы отдали агенту.

Мы внедрили [HolmesGPT](https://github.com/HolmesGPT/holmesgpt) — open-source SRE-агента — в шести Kubernetes-кластерах: в собственном кластере Tuna и в пяти кластерах наших клиентов, которые у нас на DevOps-сопровождении. Клиентских проектов два: один живёт в российском облаке, другой — зарубежный, на двух облаках.

По ходу внедрения нам пришлось написать собственный инструмент — [holmes-alert-bridge](https://gitlab.com/yuccastream/holmes-alert-bridge): готовой связки с Alertmanager в открытой версии HolmesGPT нет. Он принимает алерт, отсеивает лишнее, передаёт агенту вместе с контекстом и приносит разбор в чат; код мы открыли. Теперь через несколько минут после алерта рядом с ним лежит гипотеза: что происходит, почему и что проверить. Ниже — как это устроено, сколько стоит и чему мы научились за первый месяц.

Коротко

* HolmesGPT — open-source SRE-агент из CNCF Sandbox: сам ходит в Kubernetes, метрики и логи и формулирует гипотезу о причине алерта.
* Штатной push-интеграции с Alertmanager в открытой версии нет, поэтому мы написали и открыли [holmes-alert-bridge](https://gitlab.com/yuccastream/holmes-alert-bridge).
* Агент у нас только читает и только строит гипотезы. Чинить он ничего не может — мы не дали.
* Разбор занимает 2–3 минуты и стоит несколько рублей или 10–30 центов, в зависимости от модели.
* До внедрения проверьте свои логи на пароли, токены и персональные данные: всё, что прочитает агент, уйдёт провайдеру модели.

## Зачем это нужно

Правило алерта знает только то, что написано в его выражении: метрика пересекла порог. Почему — оно не знает, а аннотация с советом «поднимите лимит» написана один раз и на все случаи. Дежурный получает сигнал и тратит первые минуты на сбор контекста, прежде чем начать думать.

HolmesGPT закрывает именно этот отрезок. Получив алерт, человек не начинает расследование с нуля: у него уже есть версия происходящего, список проверенного и два-три шага, чтобы версию подтвердить или опровергнуть. По свежим эпизодам от срабатывания алерта до готового отчёта в чате проходит около четырёх минут по медиане — вместе с задержкой группировки в Alertmanager.

Вот как это выглядит на одном реальном эпизоде в Tuna — подробно он разобран ниже, в [кейсах](#tcp-listen-queue). График построен в нашей Grafana по обычным метрикам: выражение из правила алерта, серия `ALERTS` и счётчики bridge.

![График из Grafana: всплеск отброшенных соединений, состояние алерта pending и firing, период работы Holmes и момент появления гипотезы в чате](/blog/img/2026-10-07-holmesgpt-aiops/alert-timeline.png)

Сверху — выражение из правила алерта и его порог (пунктир). Снизу — состояние алерта, работа Holmes и то, что в этот момент видит дежурный в чате.

| Время    | Что произошло                                                                          |
| -------- | -------------------------------------------------------------------------------------- |
| 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](https://openrouter.ai/) | [ИИ-роутер Selectel](https://promo.selectel.ru/ai-router) | напрямую у провайдера модели      |
| Куда приходит разбор | Telegram                             | Slack                                                     | Slack                             |

## Что такое HolmesGPT

[HolmesGPT](https://holmesgpt.dev) — агент для расследования проблем в облачной инфраструктуре. Лицензия 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.

![Архитектура HolmesGPT: алерты и вопросы на входе, модель и runbooks сверху, данные наблюдаемости снизу, анализ причин на выходе](/blog/img/2026-10-07-holmesgpt-aiops/holmesgpt-architecture.png)

Схема из [README HolmesGPT](https://github.com/HolmesGPT/holmesgpt), лицензия Apache 2.0.

### Как мы его ставим

Основной сценарий — внутри Kubernetes, **отдельная инсталляция на каждый кластер**. Так не нужно строить сложную авторизацию: агент работает под ServiceAccount своего кластера с правами на чтение и ничего, кроме этого кластера, не видит. Запустить Holmes на выделенном сервере тоже можно — из CLI или docker-compose с kubeconfig, — но в кластере проще.

Минимальный `values.yaml`:

```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

```

```bash
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 "..."`) или прямо у инсталляции в кластере:

```bash
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

```

В консоли это выглядит так:

![Консольная сессия HolmesGPT: вызовы инструментов kubectl и итоговый ответ агента](/blog/img/2026-10-07-holmesgpt-aiops/holmesgpt-cli-demo.gif)

Запись из [документации HolmesGPT](https://holmesgpt.dev): агент вызывает инструменты и формулирует ответ.

## Мост к Alertmanager

Здесь нас ждал сюрприз. В открытой версии HolmesGPT умеет только *забирать* алерты: команда `holmes investigate alertmanager` разово читает активные алерты и разбирает их. Приёмника вебхуков в HTTP-сервере нет, у оператора триггер по алертам пока в планах, а автоматический разбор в продукте Robusta идёт через их SaaS-платформу.

Нам же нужно было простое: Alertmanager шлёт вебхук, через пару минут в том же чате появляется разбор. Так появился [holmes-alert-bridge](https://gitlab.com/yuccastream/holmes-alert-bridge) — небольшой сервис на Go под лицензией Apache 2.0.

![Логотип holmes-alert-bridge](/blog/img/2026-10-07-holmesgpt-aiops/holmes-alert-bridge-logo.svg)

Путь алерта: обычное оповещение уходит в чат как раньше, копия — в bridge, а оттуда, если пройдёт фильтры, к агенту.

Почти всё, что делает 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 % доставок расследования не запускают.

Готовый отчёт выглядит так — это та же картинка, что в начале статьи:

![Окно Slack: алерт KubernetesJobFailed от Alertmanager и под ним разбор Holmes с вердиктом и шагами проверки](/blog/img/2026-10-07-holmesgpt-aiops/slack-window.png)

Это пример сообщения

### Установка

Нужен работающий HolmesGPT в кластере и список алертов, которые имеет смысл расследовать.

```bash
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

```

```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`, чтобы разбор дополнял обычное оповещение, а не заменял его:

```yaml
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](https://gitlab.com/yuccastream/holmes-alert-bridge/-/blob/master/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 ₽       |

Цена прогона — оценка сверху, без учёта кэша: с кэшем разбор обходится в разы дешевле. Половина балла — частично верный ответ. Выборка небольшая: от 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 публикует [живой список](https://openrouter.ai/api/v1/endpoints/zdr) таких 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](https://docs.victoriametrics.com/victorialogs/): она быстрая, а её HTTP API достаточно прост, чтобы им одинаково легко пользовались и скрипты, и агенты. В HolmesGPT для неё есть встроенный toolset.

Мы специально переехали на VictoriaLogs — два проекта с Loki, один с Graylog — именно ради того, чтобы логи стали доступны программно: и нашим рабочим агентам, и Holmes. В итоге хранилище играет для агента сразу три роли:

* источник логов приложений;
* собственная память: skill ищет прошлые разборы в логах bridge;
* адрес ссылки «полный разбор» под сообщением в чате.

Вот запись, на которую ведёт ссылка «полный разбор» под сообщением из [кейса про очередь TCP](#tcp-listen-queue). Служебные поля коллектора убраны, текст разбора обрезан:

```json
{
  "_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** и использовал его для разбора…"
}

```

Разборы лежат в логах обычными полями, поэтому бюджет считается одним запросом. Например, сколько стоил каждый алерт за месяц:

```text
_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'у логов по смыслу такие:

```text
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, который агент читает первым. Это самый полезный текст во всей настройке. Обезличенный пример по структуре наших:

```markdown
---
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 под типы алертов и сервисы.** Скелет один и тот же:

```markdown
---
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                 # что обязано быть в ответе, ссылки на дашборды

```

**Подсказка в самом правиле.** Аннотации алерта попадают в вопрос агенту целиком. Поэтому короткую инструкцию можно положить прямо в правило отдельной аннотацией, которую шаблон сообщения в чате не выводит:

```yaml
- 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` и больше ничего:

```text
{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 в приложении.

![Telegram: алер�т TCPListenQueueOverflow с советом поднять net.core.somaxconn и сообщение о том, что он погас](/blog/img/2026-10-07-holmesgpt-aiops/telegram-alert.png)

Алерт в Telegram: сработал в 14:39 и через три минуты погас.

![Telegram: разбор Holmes по алерту TCPListenQueueOverflow — пять проверенных шагов, вердикт и что проверить](/blog/img/2026-10-07-holmesgpt-aiops/telegram-holmes-report.png)

Разбор Holmes пришёл в 14:44 — в тот же чат, под алертом.

Holmes за четыре минуты и пять центов выяснил другое:

1. Переполнение есть только на одном хосте, за предыдущие семь дней — ноль. Счётчик SYN cookies почти не вырос, значит, это не SYN-флуд.
2. Слушатель, который перестал принимать соединения, — `sshd`. В его журнале нашлись записи о начале `MaxStartups throttling` и об отброшенных соединениях — около двух тысяч за эпизод.
3. Источник — два адреса и порядка 180 попыток входа с несуществующими логинами за 15 минут.
4. Ресурсы хоста в норме, остальные сервисы на нём и соседние хосты не задеты.
5. К моменту разбора алерт уже погас, порты отвечают.

Вердикт: кратковременная потеря TCP-соединений из-за флуда SSH-подключений. Очередь переполнилась, потому что `sshd` упёрся в собственное ограничение `MaxStartups`, а не потому, что хосту не хватило ресурсов; `net.core.somaxconn` здесь ни при чём. И отдельное наблюдение: fail2ban такой поток не отсекает, потому что соединения рвутся до этапа аутентификации.

Совет из описания алерта увёл бы дежурного в сторону. А на скриншоте заодно видны две наши болячки из этой статьи: фраза «прошлых разборов этого алерта нет» — тот самый сломанный skill — и служебная преамбула агента в начале сообщения.

### VirusTotal: шум или сигнал

У нас есть [открытый инструмент](https://tuna.am/blog/vt-telegram-bot.md), который следит за тем, как наши IP-адреса, публикуемые домены и хэш-суммы сборок выглядят в базах антивирусов и спам-листах. Задача — отреагировать раньше, чем пользователь получит предупреждение «ваш софт вредоносный».

Срабатывания у него бывают двух сортов. Чаще всего это шум: один-два малоизвестных движка пометили адрес туннельного сервера. Изредка — настоящий сигнал. Теперь каждое срабатывание дополняется вердиктом Holmes: отдельный skill объясняет ему, какие движки считать шумом и какие сочетания признаков — поводом звать человека.

![Сообщение о срабатывании VirusTotal с вердиктом Holmes](/blog/img/2026-10-07-holmesgpt-aiops/telegram-virustotal-mockup.png)

Это пример сообщения

В этом примере агент сначала признал, что срабатывание по адресу — шум движков малого охвата. Но заметил в отчёте новый файл с заметным числом детектов, трафик которого идёт на туннельный поддомен, — и эскалировал: через туннель, вероятно, раздаётся вредоносный файл, нужен человек. Заодно проверил, что прошлых вердиктов по этому файлу нет, и что туннель сейчас не используется, — то есть владельца надо искать по учётной записи.

Этот путь идёт к 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: о затроттленных алертах и таймаутах можно было узнать только из логов. Минимум:

```yaml
- 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](https://gitlab.com/yuccastream/holmes-alert-bridge). Если попробуете у себя — расскажите, что получилось: issue и merge request'ы приветствуются.

## Оставьте отзыв

Если вам нравится пользоваться Tuna, или наоборот вы недовольны чем либо, то пожалуйста [оставьте отзыв](https://my.tuna.am/?show_feedback=1).

![](/blog/img/2024-08-22/feedback.gif)

## Помощь

Мы ценим наших пользователей и детально изучаем все обращения, если у вас возникли проблемы с tuna – обязательно свяжитесь с нами одним из способов:

* [Электронная почта](mailto:info@tuna.am)
* [Telegram сообщество](https://telegram.me/+Kgbtt0_gJwI5ZmEy)

**Теги:**

* [aiops](https://tuna.am/blog/tags/aiops)
* [kubernetes](https://tuna.am/blog/tags/kubernetes)
* [open-source](https://tuna.am/blog/tags/open-source)
