Окно Slack: алерт KubernetesJobFailed от Alertmanager и под ним разбор Holmes с вердиктом и шагами проверки
Это пример алерта и его разбора Holmes в Slack.

Алерт приходит в чат без контекста. «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.

График из Grafana: всплеск отброшенных соединений, состояние алерта pending и firing, период работы Holmes и момент появления гипотезы в чате
Сверху — выражение из правила алерта и его порог (пунктир). Снизу — состояние алерта, работа 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

ИИ-роутер 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.

Архитектура HolmesGPT: алерты и вопросы на входе, модель и runbooks сверху, данные наблюдаемости снизу, анализ причин на выходе
Схема из README HolmesGPT (github.com/HolmesGPT/holmesgpt), лицензия Apache 2.0.

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

Основной сценарий — внутри 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

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

Консольная сессия HolmesGPT: вызовы инструментов kubectl и итоговый ответ агента
Запись из документации HolmesGPT (holmesgpt.dev): агент вызывает инструменты и формулирует ответ.

Мост к Alertmanager

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

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

Схема: путь алерта от Alertmanager через holmes-alert-bridge к HolmesGPT и обратно в чат
Путь алерта: обычное оповещение уходит в чат как раньше, копия — в 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 с вердиктом и шагами проверки
Это пример сообщения

Установка

Нужен работающий 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 ₽

Цена прогона — оценка сверху, без учёта кэша: с кэшем разбор обходится в разы дешевле. Половина балла — частично верный ответ. Выборка небольшая: от 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 в приложении.

Telegram: алерт TCPListenQueueOverflow с советом поднять net.core.somaxconn и сообщение о том, что он погас
Алерт в Telegram: сработал в 14:39 и через три минуты погас.
Telegram: разбор Holmes по алерту TCPListenQueueOverflow — пять проверенных шагов, вердикт и что проверить
Разбор 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: шум или сигнал

У нас есть открытый инструмент, который следит за тем, как наши IP-адреса, публикуемые домены и хэш-суммы сборок выглядят в базах антивирусов и спам-листах. Задача — отреагировать раньше, чем пользователь получит предупреждение «ваш софт вредоносный».

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

Сообщение о срабатывании VirusTotal с вердиктом Holmes
Это пример сообщения

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

Этот путь идёт к 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’ы приветствуются.

Комментарии (0)