README.ru.md
Rama main · réplica de lectura del repositorio original.
# 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 <id> <cause>`), а по команде `/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, под которой были
опубликованы; смена лицензии действует с этой даты.