# Minus20 [English](README.md) | **Русский** **Минус двадцать минут неизвестности.** Алерт сообщает, *что* сломалось, но никогда не говорит *почему*. И вот в 03:00 дежурный двадцать минут вручную собирает контекст и гадает. Minus20 убирает эти минуты из каждого инцидента в Kubernetes. Он реагирует на вебхук Alertmanager, собирает то же, что собрал бы инженер (события Kubernetes, логи подов, Prometheus, Loki, CloudWatch), и присылает в чат опровержимую гипотезу о первопричине вместе с доказательствами. Ответ приходит меньше чем за минуту с облачной моделью и примерно за две с локальной. Работает внутри вашего кластера; с локальной моделью наружу не уходит ничего, а с облачной уходит только замаскированный контекст. Он никогда не воздействует на ваш кластер. Он рекомендует, а решает инженер. **Попробовать без установки:** отправьте боту вывод `kubectl describe pod` или `kubectl logs`, и он ответит тем же сообщением, которое установленный продукт присылает после алерта. У владельцев любого бота Minus20 эта возможность уже есть. Публичный демо-бот работает 24/7 на небольшом сервере; открыть его для всех пока мешает облачная модель, на которой он работает (см. пункт про бесплатные тарифы облачных моделей в разделе «Известные ограничения»). --- ## Что вы получаете Каждый инцидент приходит в Slack или Telegram, и в сообщении есть: - **Вероятная причина** и уровень уверенности в ней - **Доказательства**, на которых она держится, чтобы за секунды проверить ход рассуждений - **Самое дешёвое наблюдение, способное её опровергнуть.** Выдвинуть гипотезу легко, а ночь спасает умение быстро её отбросить - **Радиус поражения** (blast radius) оценивается отдельно, чтобы редкая, но катастрофическая причина никогда не терялась на фоне частой - **Следующие шаги**, а также какой бэкенд дал ответ, сколько времени занял анализ и сколько он стоил А если этого мало, просто спросите. Агент в том же чате продолжит расследование с помощью read-only инструментов. Он помнит, что на самом деле стало причиной предыдущего похожего инцидента в *вашем* кластере (причину вы сообщаете ему командой `/wrong `), а по команде `/report` пишет еженедельный обзор инцидентов. Каждый вердикт заодно становится тестом: бенчмарк прогоняет ваши же инциденты через текущую модель и оценивает её по тому, что, по вашим словам, произошло на самом деле. Уже работаете с агентом в терминале? Те же инструменты и та же память доступны в виде [MCP-сервера](deploy/README.md#use-it-from-your-own-agent-mcp): Gemini CLI или Claude Code могут спросить «такое уже было?» и получить ответ, который инженер на самом деле дал в прошлый раз. --- ## Быстрый старт ### Вариант А: любой кластер Kubernetes, одна команда Helm-чарт и образы публикуются в GitHub Container Registry при каждом пуше в `main`, поэтому клонировать и собирать ничего не нужно. Настройки по умолчанию работают офлайн, так что API-ключ тоже не нужен. ```bash helm upgrade --install m20 oci://ghcr.io/maxuver/charts/minus20 -n minus20 --create-namespace kubectl -n minus20 rollout status deploy/m20-analyzer-worker ``` Если репозиторий склонирован, вместо OCI-ссылки можно указать `deploy/minus20`. Нет кластера под рукой? `kind create cluster --config kind/cluster.yaml` поднимет его за минуту. Самый быстрый путь к первой гипотезе: добавьте `--set demo.chaos.enabled=true`. Сразу после установки чарт сломает один под в своём namespace от имени отдельного ServiceAccount, а ServiceAccount продукта останется read-only. Замер на чистом сервере 2 vCPU / 4 ГБ: 35 с от `helm install` до первого проанализированного алерта с офлайн-заглушкой вместо модели, включая загрузку образов. Настоящая модель добавляет своё время на ответ: секунды у облачной модели, одну-две минуты у 7B-модели на CPU. Или сломайте что-нибудь сами и посмотрите, как он отработает: ```bash kubectl -n minus20 run billing-api --image=busybox --command -- \ sh -c "echo 'ERROR could not connect to postgres:5432'; sleep 2; exit 1" kubectl -n minus20 logs -f deploy/m20-analyzer-worker ``` Полное руководство по развёртыванию, включая стек мониторинга и полностью автоматический цикл, в котором пайплайн запускает сам Alertmanager: [`deploy/README.md`](deploy/README.md). Пошаговый сценарий для первого знакомства или записи скринкаста: [`docs/DEMO.md`](docs/DEMO.md). ### Вариант Б: docker compose, без кластера ```bash docker compose up --build curl -X POST http://localhost:8080/webhook/alertmanager \ -H "Content-Type: application/json" \ -d @services/ingest-api/tests/fixtures/crashloop.json ``` ### Совсем без кластера: прогон записанных сценариев Шесть записанных сценариев с внедрёнными сбоями (fault injection) прогоняются через настоящий пайплайн, а команда выводит время до первой гипотезы и стоимость в пересчёте на алерт: ```bash cd services/analyzer-worker pip install -r requirements-dev.txt python -m app.replay ``` --- ## Что понадобится с вашей стороны | Что | Зачем | Обязательно? | |---|---|---| | Кластер Kubernetes с Alertmanager | алерты, которые нужно разбирать | да | | Slack Incoming Webhook **или** токен Telegram-бота | доставка | да | | URL Prometheus | контекст по метрикам | необязательно | | URL Loki | контекст по логам | необязательно | | Лог-группа CloudWatch + имя кластера | логи и метрики на EKS без Loki/Prometheus | необязательно | | LLM-бэкенд | сама гипотеза | см. ниже | **LLM выбираете вы, и один из вариантов бесплатный.** Поднимите локальную модель через Ollama, и данные не выйдут за пределы вашей сети: ни API-ключа, ни платы за каждый алерт. Любой OpenAI-совместимый провайдер (DeepSeek, Groq, Together, OpenRouter, vLLM) или Anthropic подключается через тот же интерфейс. Бэкенд выбирается одним значением в конфиге, код менять не нужно. ```bash helm upgrade --install m20 deploy/minus20 -n minus20 \ --set config.llmProvider=ollama \ --set config.ollamaUrl=http://host.docker.internal:11434 \ --set config.collectors='k8s-events\,prometheus\,loki' \ --set config.notifier=slack ``` --- ## Принципы проектирования 1. **ИИ лишь надстройка, а не зависимость.** Приём алертов отделён от анализа очередью Redis Stream ограниченной длины. У анализа жёсткий таймаут и дневной лимит бюджета, а сырой алерт доставляется, даже когда модель недоступна. Выключите Minus20, и вы вернётесь ровно к тому, что было до него. ([ADR-0003](docs/adr/0003-graceful-degradation.md)) 2. **Логи не уходят наружу без вашего разрешения.** Маскирование (redaction), у которого нет выключателя, выполняется перед каждым вызовом модели. Оно скрывает адреса e-mail, адреса IPv4 и IPv6, bearer-токены, JWT, ключи AWS, токены Telegram-ботов, похожие на секреты пары `key=value`, номера платёжных карт (с проверкой по алгоритму Луна), IBAN (с проверкой контрольной суммы mod 97) и международные номера телефонов. Имена людей, физические адреса (улица, дом) и персональные данные в произвольном тексте не распознаются. С полностью локальным бэкендом данные вообще не покидают вашу сеть (zero egress). ([ADR-0002](docs/adr/0002-llm-privacy-and-pluggable-backends.md)) 3. **Стоимость известна заранее, без сюрпризов.** Детерминированный пайплайн делает ровно один структурированный вызов LLM на алерт, поэтому цена известна ещё до того, как вы его включите. ([ADR-0001](docs/adr/0001-event-driven-pipeline-over-agent-loop.md)) 4. **Он не может ничего изменить в продакшене.** У модели нет ни одного инструмента с правом записи, все интеграции используют read-only учётные данные, а вывод модели никогда не исполняется. Поэтому prompt injection, спрятанная в строке лога, обернётся неверным советом в чате, а не действием в вашем кластере. 5. **Гипотеза приходит вместе со способом её опровергнуть.** ([ADR-0004](docs/adr/0004-hypothesis-evidence-and-blast-radius.md)) 6. **Рефлекс и обдумывание разнесены по разным процессам.** На пути алерта выполняется один ограниченный вызов, и этот путь никак не зависит от агента. Агент запускается, только когда его спрашивает человек; у него закрытый набор read-only инструментов и память об инцидентах этого кластера. ([ADR-0005](docs/adr/0005-reflex-and-deliberate-agent.md)) --- ## Статус | Область | Состояние | |---|---| | `ingest-api`: вебхук Alertmanager → Redis Streams | ✅ | | `analyzer-worker`: коллекторы, маскирование, бюджет, дедупликация, группировка алерт-штормов, корреляция разных алертов (три алерта в 03:00, одна пересмотренная гипотеза), плавная деградация (graceful degradation) | ✅ ([ADR-0006](docs/adr/0006-cross-alert-correlation.md)) | | Коллекторы: события Kubernetes и логи подов, Prometheus, Loki, CloudWatch (Container Insights) | ✅ | | LLM-бэкенды: локальный Ollama, любой OpenAI-совместимый API (DeepSeek, Mistral, Gemini, Groq, vLLM…), Anthropic, офлайн-заглушка; сначала локальная модель, облачная в качестве запасного уровня | ✅ | | Доставка: Slack, Telegram | ✅ | | История инцидентов: Postgres, включая замаскированный контекст, на основе которого строилась каждая гипотеза (audit trail) | ✅ | | Read-only веб-интерфейс для истории инцидентов | ✅ | | Агент в Telegram: read-only инструменты, память о прошлых инцидентах (pgvector), `/report` (по запросу и раз в неделю по расписанию), распознавание скриншотов локальной vision-моделью, ответ на вставленный вывод `kubectl` как на алерт | ✅ ([ADR-0005](docs/adr/0005-reflex-and-deliberate-agent.md)) | | Демо без присмотра: CronJob от имени собственного ServiceAccount с правом записи по расписанию ломает под и отправляет алерт в путь рефлекса; сам продукт остаётся read-only | ✅ `demo.chaos.enabled` | | MCP-сервер: те же read-only инструменты для Gemini CLI, Claude Code, Cursor | ✅ | | Helm-чарт с RBAC по принципу наименьших привилегий, проверен end-to-end на kind | ✅ | | Сценарии с внедрёнными сбоями (fault injection) + replay-бенчмарк; набор для оценки (eval) пополняется реальными инцидентами с вердиктом инженера (`replay --from-store`) | ✅ [результаты](docs/BENCHMARKS.md) | | CI: линт, тесты, сборка образов + сканирование CVE, helm lint, SAST, сканирование зависимостей, поиск секретов по всей истории, подписанный provenance + SBOM для каждого образа | ✅ | | Демо 24/7: одноузловой k3s на VPS 2 vCPU / 4 ГБ, SSH только по ключу, снаружи доступен только порт 22 | ✅ холодный `helm install` из GHCR → первый проанализированный алерт за 35 с на модели-заглушке, занято 1,3 ГБ RAM (2026-09-27) | | Terraform для AWS EKS | ✅ два реальных прогона (2026-09-13/14): чарт из GHCR, Postgres на EBS, MCP, реальные инциденты, кластер удалён в той же сессии, около 0,13 $ за прогон по прайс-листу ([подтверждение](docs/EKS-RUN.md)) | ### Известные ограничения Говорим о них прямо, потому что вы всё равно на них наткнётесь: - **У веб-интерфейса нет аутентификации.** Он доступен только для чтения, а его Service намеренно имеет тип ClusterIP: заходите через `kubectl port-forward` и не выставляйте его наружу. - **Нет мультитенантности.** Одна команда, один кластер. - **Точность зависит от модели, и это измерено.** С локальной 7B-моделью: 6/6 на сценариях, где сигнал виден прямо, и 6/10 на сценариях, специально построенных так, чтобы направить по ложному следу (три из них специфичны для AWS). С современной облачной моделью в том же пайплайне: 6/6 на простом наборе и 6 из 7 исходных сложных сценариев, которые ей отправили (на седьмой её бесплатный тариф так и не ответил; три сценария для AWS на облачной модели ещё не прогонялись), примерно за 7 с. Методика и результаты по каждому сценарию: [docs/BENCHMARKS.md](docs/BENCHMARKS.md). На реальных инцидентах в продакшене пока ничего не измерялось, а сложные сценарии содержат сигналы, которые живые коллекторы сегодня не собирают (события узлов, состояние зависимостей), поэтому установленный Minus20 может показать результат ниже. - **Только Kubernetes.** Других источников алертов пока нет. - **На бесплатные тарифы облачных моделей опираться нельзя.** В сентябре 2026 года на одном из них закончилась дневная квота; второй требовал активировать тарифный план, но консоль провайдера так и не загрузилась; третий отказал в доступе из нашего региона. Если модель не ответила, алерт всё равно приходит (это покрыто тестами); для реальной работы берите локальную модель или платный ключ. - **На CPU агент работает медленно.** С локальной 7B-моделью вопрос, требующий нескольких шагов, занимает минуты (замер: 8 минут на четыре вызова инструментов); облачная модель отвечает за секунды. Путь рефлекса это не затрагивает. --- ## Структура репозитория ``` services/ ingest-api/ FastAPI webhook receiver → Redis Streams analyzer-worker/ collectors, redaction, LLM backends, delivery, replay app/agent/ the Telegram agent: tool loop, memory, report (same image) scenarios/ recorded fault-injection scenarios web-ui/ read-only incident history (FastAPI + Jinja2) deploy/ minus20/ Helm chart (services, RBAC, Postgres) infra/terraform/ AWS VPC + EKS (applied once for real, see docs/EKS-RUN.md) kind/ local cluster and monitoring stack config docs/ ARCHITECTURE.md the map: two processes, ports and adapters, what never leaves DEMO.md 25-minute walkthrough from an empty cluster to a hypothesis in chat VISION.md why this project exists, in depth adr/ architecture decision records ``` ## Разработка Если вы работаете здесь с ИИ-агентом для написания кода или вносите изменения сами, сначала прочитайте [AGENTS.md](AGENTS.md). Там перечислены инварианты, которые не должно нарушать ни одно изменение (сырой алерт приходит всегда, маскирование перед каждым вызовом модели, read-only, утверждения только с цифрами). Там же описано, как проверяются изменения. ```bash cd services/analyzer-worker pip install -r requirements-dev.txt ruff check app tests && pytest ``` ### Локальная разработка на kind Соберите образ с тегом `:dev`, загрузите его в kind и передайте чарту: ```bash docker build services/analyzer-worker -t minus20/analyzer-worker:dev kind load docker-image minus20/analyzer-worker:dev --name minus20 helm upgrade --install m20 deploy/minus20 -n minus20 --set images.analyzer=minus20/analyzer-worker:dev ``` ## Лицензия [GNU AGPL-3.0](LICENSE). Copyright (c) 2026 Maxim Patsyuk. Можно свободно использовать, изменять и разворачивать у себя, в том числе внутри компании. Если вы запускаете изменённую версию как сетевой сервис, AGPL обязывает опубликовать эти изменения. **Есть коммерческая лицензия.** Многие организации не принимают AGPL, и это разумная позиция. Правообладатель может выдать отдельную коммерческую лицензию на других условиях. Напишите, если вам это нужно. **Что остаётся открытым.** Всё, что нужно инженеру, чтобы запустить Minus20 на одном кластере, распространяется и будет распространяться под AGPL: приём алертов, анализ, коллекторы, маскирование, все LLM-бэкенды, доставка, история, агент и его память, чарт. Функции, ценность которых проявляется на уровне организации (коннекторы к базам знаний, отчёты по SLO и error budget, мультикластер, SSO), могут предлагаться отдельно. Мы рады вкладу в проект на условиях, описанных в [CONTRIBUTING.md](CONTRIBUTING.md). Релизы, вышедшие до 2026-09-09, остаются под лицензией MIT, под которой были опубликованы; смена лицензии действует с этой даты.