О проекте
About the Project
Гибрид: детерминированный Knowledge Graph + LLM-пайплайн за policy-gate. Обогащение алертов, RCA и controlled remediation в Kubernetes. Спроектирован честным к своим слепым зонам — система помечает, чего она не знает, вместо того чтобы выдавать догадки за факты.
A hybrid: deterministic Knowledge Graph + an LLM pipeline behind a policy gate. Alert enrichment, RCA and controlled remediation for Kubernetes. Built to be honest about its blind spots — it marks what it doesn't know instead of passing guesses off as facts.
Безопасность прежде всего
Safety-first remediation
Главный страх «AI сам трогает прод» снят архитектурно: можно ли запускать kubectl-write считается детерминированно (8 risk-axes → policy-engine), а не берётся из ответа модели. Dry-run по умолчанию, approvals, anti-replay, savepoint-изоляция.
The core fear — “the AI touches prod on its own” — is removed by design: whether a kubectl write may run is computed deterministically (8 risk axes → policy engine), not taken from the model’s answer. Dry-run by default, approvals, anti-replay, savepoint isolation.
Граф, который знает, чего он не знает
A Knowledge Graph that knows when it doesn't know
Операционный граф из нескольких источников (топология, деплои, события, метрики, логи) с провенансом, отслеживанием свежести, проверками целостности и явными слепыми зонами. Поверх него — инциденты (один открытый на сервис), timeline по шести таблицам и blast radius с эпистемикой на каждом пути. Сейчас в проде: 17 938 узлов, 20 945 живых рёбер, 231 namespace.
A multi-source operational graph (topology, deploys, events, metrics, logs) with provenance, freshness tracking, integrity checks and explicit blind spots. On top of it — incidents (one open per service), a timeline across six tables and a blast radius with epistemic labels on every path. Live in production: 17,938 nodes, 20,945 live edges, 231 namespaces.
Честность по умолчанию
Honest by design
У каждого факта три исхода, а не два: found — нашли, absent — искали в живом источнике и не нашли, unknown — проверить было нечем. Упавший источник понижает «не нашли» до «не знаем», и критик никогда не опровергает гипотезу по unknown. Система не выдаёт «зелёное», когда у неё просто нет данных — редкий, но решающий для доверия признак.
Every fact has three outcomes, not two: found, absent (looked in a live source, nothing there) and unknown (could not check). A failed source demotes “not found” to “don’t know”, and the critic never refutes a hypothesis with an unknown. It won’t report “green” when it simply has no data — a rare but decisive trait for trust.
Как это работает
Конвейер инцидента — 6 ступеней. Честно: что уже live, а что включается с LLM-флагом.
1 · IngestliveAlertManager webhook → HMAC + anti-replay + dedup.
2 · Enrich (KG)liveГраф добавляет контекст: деплои (точные и ns-broadcast — как разные свидетельства), blast radius, pod-события, логи, ingress 5xx/p95. Алерт присоединяется к инциденту сервиса.
3 · Reason (LLM)за флагомГипотез-агенты строят версии из контекста; critic ранжирует и режет слабые.
4 · Respondlive +LLMОдин Discord-эмбед: факты + подсказка root-cause; с LLM — связный narrative.
5 · Remediateopt-inПредложить/применить kubectl-фикс за детерминированным policy-gate + approval. Перед записью — сверка uid цели с графом, после — проверка исхода через 5 и 15 минут.
6 · Self-watchlive11 self-health-чеков /30 мин, включая integrity-watchdog графа; результат — в VictoriaMetrics, алерты на протухшие и упавшие проверки. Источник, который ничего не увидел, heartbeat не пишет.
How it works
The incident pipeline — 6 stages. Honest about what's live today vs what the LLM flag turns on.
1 · IngestliveAlertManager webhook → HMAC + anti-replay + dedup.
2 · Enrich (KG)liveThe graph adds context: deploys (exact vs namespace-wide — different evidence), blast radius, pod events, logs, ingress 5xx/p95. The alert joins its service’s incident.
3 · Reason (LLM)flaggedHypothesis agents draft versions from the context; a critic ranks and prunes weak ones.
4 · Respondlive +LLMOne Discord embed: facts + a root-cause hint; with LLM, a coherent narrative.
5 · Remediateopt-inPropose/apply a kubectl fix behind a deterministic policy gate + approval. Before the write the target’s uid is checked against the graph; after it the outcome is re-verified at +5 and +15 min.
6 · Self-watchlive11 self-health checks /30 min, including a graph integrity watchdog; results exported to VictoriaMetrics with alerts on stale or failing checks. A source that saw nothing writes no heartbeat.
Как выглядит выводWhat the output looks like
Синтетический пример critical-эмбеда в Discord (данные вымышленные).A synthetic example of a critical Discord embed (fictional data).
🔴 Critical · checkout-service · prod
🎯 Likely cause
deploy #482 4 min before — p95 +180%, 5xx 0.6 rps at api.example.com/checkout
🌐 Endpoint health (ingress-derived)
5xx 0.6 rps · p95 920 ms
🗂 Incident
#128 · 3 alerts · opened 4 min ago · timeline: deploy → OOMKilled → alert
💥 Blast radius
3 svc → ◇ orders-service, ◇ cart-service, ≈ payments-gateway · 1 URL
❔ callers known from manifests/env only — list may be incomplete
🩺 Pod events
CrashLoopBackOff ×7 (OOMKilled)
🔧 Suggested action
kubectl rollout undo deploy/checkout-service [⚙️ Apply — dry-run ✓, risk: medium, target uid ✓]
Почему это безопасно для прода
Главный страх «AI сам трогает прод» снят архитектурно — решение принимает не модель:
- Детерминированный policy-gate: можно ли запускать
kubectl-write считается из структурного intent'а по 8 risk-axes, а не берётся из ответа LLM (его risk — лишь advisory).
- Dry-run по умолчанию + approval: реальный apply только после
--dry-run=server и явного подтверждения кнопкой (risk ≤ medium).
- TOCTOU-подпись intent + anti-replay + savepoint-изоляция + полный OTEL-аудит каждого действия.
- Default = advisory: из коробки copilot не зовёт
kubectl вообще — анализирует и пишет в Discord.
Why it's safe next to prod
The core fear — "the AI touches prod on its own" — is removed by design; the model doesn't make the call:
- Deterministic policy gate: whether a
kubectl write may run is computed from the structured intent across 8 risk axes — not taken from the LLM (its risk field is advisory only).
- Dry-run by default + approval: a real apply happens only after
--dry-run=server and an explicit button confirmation (risk ≤ medium).
- TOCTOU intent signature + anti-replay + savepoint isolation + full OTEL audit of every action.
- Default = advisory: out of the box the copilot never calls
kubectl — it analyzes and posts to Discord.
Проверено продом, а не демо-кластером
Реальные дефекты, найденные на живых данных. Каждый стал регрессионным тестом.
2 092 невалидных ребра за тик
k8s Service и его Deployment делили одну строку с ключом (namespace, name). Ребро между ними не могло существовать — вырождалось в петлю и отбрасывалось. В графе жило 3 ребра serves_traffic.
→ Явная онтология узлов + регрессионные тесты. Сейчас 4 234 ребра.
230 задач в очереди
Окно жизни было выставлено ровно у одной периодической задачи из 27. Дешёвые пробы копились — 94 минутных тика за полтора часа — и полностью вытеснили синк топологии: он планировался по расписанию и не выполнялся ни разу.
→ Протухание задач по интервалу.
4 200 upsert-ов в одной транзакции
Синк коммитил раз в тик, удерживая блокировку 13 минут. Миграция встала за ним в очередь — а очередь на лок в PostgreSQL блокирует всех, кто пришёл после неё. Зависли семь читателей, приложение стояло 6 минут.
→ Коммит батчами.
Метрика, которая солгала
Новый тип узла уронил orphan с 72,5% до 42% за ночь — без единой новой интеграции. Каждый «вылеченный» сервис получил ровно одно ребро: на самого себя.
→ Откатили метрику, а не схему. У каждой метрики качества теперь тест на то, чего она считать НЕ должна.
10 064 ложных деплоя за 40 минут
Первый watcher выкатов считал деплоем рост metadata.generation. Его крутит каждый тик HPA и каждый kubectl scale: 776 сервисов получили «недавний деплой», не меняя ни одного образа.
→ Критерий — смена образа или hash spec.template. Hotfix через 40 минут, записи вычищены.
25 000 поколений одного Deployment
rollout status висел на «spec update to be observed». Мёртвая ingress-заглушка без класса матчилась пятью контроллерами, её статус скакал между VIP, а Rancher каждый раз переписывал аннотацию на Deployment.
→ Заглушка снята; generation замер за минуту.
327 из 390 алертов — один шум
Первые 13 инцидентов в проде: 7 — KubeDeploymentGenerationMismatch, который Discord давно считал шумом. Без пометки таблица инцидентов стала бы таблицей одного алерта.
→ Инцидент наследует решение о шуме; настоящий алерт снимает флаг.
66 алертов на экспортер метрик
У KubeJobFailed лейбл service — kube-state-metrics, цель — в job_name. Месяц все они лежали на экспортере, хотя граф знал владельцев job'ов.
→ Владелец из графа, иначе имя CronJob; экспортер — никогда.
Built against production, not a demo cluster
Real defects found on live data. Each became a regression test.
2,092 invalid edges per tick
A k8s Service and its Deployment shared one row keyed by (namespace, name). The edge between them could not exist — it degenerated into a self-loop and was dropped. The graph held 3 serves_traffic edges.
→ Explicit node ontology + regression tests. Now 4,234 edges.
230 queued jobs
Exactly one periodic task out of 27 had an expiry window. Low-value probes piled up — 94 one-minute ticks over 90 minutes — and starved the topology sync completely: dispatched on schedule, never executed.
→ Task expiry sized to each interval.
4,200 upserts, one transaction
A sync committed once per tick, holding a table lock for 13 minutes. A migration queued behind it — and in PostgreSQL a queued DDL blocks every reader that arrives after it. Seven readers hung; the app stalled for 6 minutes.
→ Batched commits.
The metric that lied
A new node type made orphan drop from 72.5% to 42% overnight — without a single new integration. Every “cured” service had gained exactly one edge: to itself.
→ We reverted the metric, not the schema. Every quality metric now has a test asserting what it must NOT count.
10,064 false deploys in 40 minutes
The first rollout watcher treated metadata.generation growth as a deploy. HPA and every kubectl scale bump it: 776 services got a “recent deploy” without a single image change.
→ Criterion: image or spec.template hash change. Hotfix in 40 minutes, rows purged.
25,000 generations of one Deployment
rollout status hung on “spec update to be observed”. A dead classless ingress stub matched five controllers, its status flapped between VIPs and Rancher rewrote a Deployment annotation each time.
→ Stub removed; generation froze within a minute.
327 of 390 alerts — one noise
First 13 incidents in production: 7 were KubeDeploymentGenerationMismatch, long classified as noise in Discord. Unflagged, the incident table would have become a table of one alert.
→ The incident inherits the noise verdict; a real alert clears it.
66 alerts on the metrics exporter
KubeJobFailed carries service = kube-state-metrics and the target in job_name. For a month all of them landed on the exporter while the graph knew the jobs’ owners.
→ Owner from the graph, else the CronJob name; the exporter — never.
Почему всё держится на графеWhy the Knowledge Graph matters
| Обычный AI-SRE | SRE AI Copilot |
| Источник истины | модель и её контекст | операционный граф в Postgres |
| Evidence | подразумевается | явное, с провенансом источника |
| Неизвестное | обычно скрыто | полноправное состояние: «данных нет» ≠ «всё хорошо» |
| История | ограничена окном | хранится: деплои, события, аномалии |
| Инцидент | = один алерт | один открытый на сервис; флаппинг переоткрывает, а не плодит; timeline по шести таблицам |
| Результат действия | exit code kubectl | та же ли цель (uid), сработало ли, стало ли лучше — через 5 и 15 минут |
| Право на действие | решает модель или тул | детерминированный policy-gate, 8 risk-axes |
| Обратная связь | опциональна | непрерывная: результат возвращается в граф |
| Typical AI-SRE | SRE AI Copilot |
| Source of truth | the model and its context | an operational graph in Postgres |
| Evidence | implicit | explicit, with source provenance |
| Unknown state | usually hidden | first-class: “no data” ≠ “all good” |
| History | limited to a window | retained: deploys, events, anomalies |
| Incident | = one alert | one open per service; flapping re-opens instead of multiplying; timeline across six tables |
| Action outcome | kubectl exit code | same target (uid)? took effect? got better? — checked at +5 and +15 min |
| Authorization | model or tool decides | deterministic policy gate, 8 risk axes |
| Production feedback | optional | continuous: outcomes flow back into the graph |
3300+ tests
17 938 graph nodes
231 namespaces
3 outcomes per fact
8-axis policy gate
verified remediation