Самовыліковы Kubernetes у дзеянні: тры збою, сігналы SigNoz і затверджаныя способы выліковання.
Автонаправлены Kubernetes з OpenTelemetry і SigNoz: цыкл збою, завершэнне працы праз нехватку памяці і рэзкы адлік токенава на Kind, што показвае кожны сігнал, і затверджаныя AI-рэшэнні.
Крыніцы: Self-Healing Kubernetes: Wiring OpenTelemetry, SigNoz, and a Groq-Powered Remediation Agent (PART 7)
У дэмаверы ёсць калькі рухомых элементаў. Локальны кластэр Kind выкананы як хост для SigNoz, які адрабатвае даны пра трэсы і лагі, а таксама для Python-дапытку, чыя тры канцэнтры спецыяльна зламаны. Агент для усунення нештаўносцей перакантролюе наявнасць анамалій і запытае Groq пра дыягназ. Дашборд на базе React дазволяе аператару затвердзіць або адхіліць запропанаваны спосаб усунення прычыны нештаўносці, прычым нічога не запускаецца. Калі все гэта створана, цікавае — спаглядаць як яна працуе. Гэты тэст запускае кожную нештаўнасць у рэальным часе, аналізуе, што фіксуе SigNoz па кожнам інцыдэнце, і паказвае, што трэба змяніць, перш чым хтось зможа паверыць у цю сістэму паza межамі ноутбука.
Паказваць, што сістэма працуе
Перш чым будзе запускана якая-лібо нештаўнасць, павінны працаваць пяць элементаў. Першая перакантролька — чы ўсе пады ў обох просторах імен справныя.
# All pods should show Running
kubectl get pods -n observability
kubectl get pods -n apps
Для працы порт-форвардаў для SigNoz, службы стварэння аптату і панелі керування кожныя з іх патрабуеяць свой сабэйчык у тэрмінале, а пасляпэўныя каманды ў гэтым блаке адстаюць ад логаў агента.
# Port-forwards (run each in its own tab if not already open)
# Tab A: kubectl port-forward -n observability svc/signoz 8080:8080
# Tab B: kubectl port-forward -n apps svc/summarizer 8000:80
# Tab C: kubectl port-forward -n apps svc/approval-dashboard 9000:9000
# Tab D: kubectl port-forward -n apps svc/approval-frontend-svc 3000:3000# Confirm the agent is polling
kubectl logs -n apps deployment/remediation-agent --tail=5
Няўзабавныя сігналы пра стан «все гаразд» у тым выходным тэксце значаюць, што агент здаровы. Пад, які не працуе, трэба спачатку пераглянуць за дапамою каманды kubectl describe і вылагодзіць. Залічвайце два вікна браузера: SigNoz на порту 8080 і панель керування на порту 3000.
Сцэнарый першы: CrashLoopBackOff
Установка зменнай абявлення KILL_ME на развёртванні summarizer, а пасля запуск ўпрабовкі на яго точку краху прыводзіць да таго, што працэс замірае ў момент апрацоўкі запіту.
kubectl set env deployment/summarizer -n apps KILL_ME=true
curl -s http://localhost:8000/crash
Званнець curl можа не вернуць нічога або застаўціцца, адтолькі як працэс завершаецца пад час свайго выканання. Назіранне за кантэйнерамі паказвае, як падрахоўвальнік перзапускаў расце, а статус перыходзіць між Running і CrashLoopBackOff; два-тры цыклы вістачае для гэтага.
kubectl get pods -n apps -w
У SigNoz. У аптэкстазе службы падзбірніка паказана стопа пакашанняя, якая стрымліваецца да 100% як толькі падзе падзець, тады як стопа запыткаў падыходзіць да нуля, калі падзець не працуе межы перзапускамі. Трафік, потым нічога, потым знову трафік — гэта візуальны знак цыклу падзецяў. Розгортанне працоўнае падае інакш, з чыстым пераходам і без рывка пакашанняя, а раздзеленне сеткі таксама выглядае інакш, калі стопа пакашанняя расце, а падзець застаецца ў стане «Running».
Карточка «Логі», фільтраваная на падзбірнік, містыць крытычную лінію, яюя выдалае прыкладка непразь яе згіну.
CRITICAL summarizer — KILL_ME is true, exiting process
У тым логе ўказаны ідэнтыфікатор трэйса запытку, якая яго запусціла, таму адна клікання ведае з рэкорда лога да самага трэйса і да конкрэтнай HTTP-запыткі, якая завершыла працэс.
Агент рэагуе. Чэрэз 30 секунд пасля першага збою запускаецца цыкл паллінгу. Агент бачыць рэштку памылак 1,0, якая перавышае прагу 0,10, агрэгуее сігналы і вызывае Groq. Логі агента паказваюць гэтую последовальнасць.
kubectl logs -n apps deployment/remediation-agent --tail=20 -f
WARNING agent — crash loop signal: error_rate=1.00
INFO agent — anomaly detected: crash_loop — calling Groq for diagnosis
INFO agent — diagnosis: type=crash_loop severity=critical confidence=0.91
fix=kubectl set env deployment/summarizer -n apps KILL_ME-
INFO incident_poster — incident posted: id=<uuid> type=crash_loop
Апраўленне. Панель керування паказвае картку прыблізна за дзесяць секунд, а запрошаны спосаб выправлення ёсць правым, таму аператар нажымае «Апраўці». Сервіс у спіну пераканальваецца, што команда пачынаецца з kubectl set env і не містыць заблокаваных тэрмінаў, а пасля выканальвае яе. Картка апдэйтаваецца, каб падтвердзіць апраўленне і змяну сераўіснай асяроддзя.
✓ Approved by operator
deployment.apps/summarizer env updated
У раздзеле абляцыяў наступны запуск адбываецца успешна, цыкл збою завершаецца, а парадакт бягаў SigNoz вяртаецца да нуля. Перад наступным сцэнарыем быстрая перапрацоўка падтверджае, што зменная больш не існуе.
# The approve action already removed KILL_ME, but confirm:
kubectl get deployment summarizer -n apps -o jsonpath='{.spec.template.spec.containers[0].env}' | jq
KILL_ME не павінен з’являцца ў выходным тэксце.
Сцэнарый два: OOMKill
Цікл запытоў POST да канцэнтрацыі выліву паступова збільшае об’ём памяці.
for i in {1..30}; do
curl -s -X POST http://localhost:8000/leak | jq -c '{leaked_mb,chunks}'
sleep 0.5
done
У працэ падаў кантыент памяці, і прыблізна на 25-й раз пад час адбывання цыклу пад быў знішчаны, пасля чаго прыемнік curl пачаў вяртаць памылкі з’яўлення з’язку.
У SigNoz. Часама ступень памылак не вырастае паступова. Знішчэнне пада з-за нехапакі памяці выглядае раптовым, і прыемнік ніколі не вяртае адпаведзі пра памылку. У той час частота запытак розкаляцеўваецца: спачатку сільна зменшыцца, пасля чаго наступіць перэрва, а потым зноў павышыцца, калі пад зноў запускаецца.
У логах знаходзіцца справжня інфармацыя. Пасля фільтраўвання для аптаматызаванага падсумавання, перэрве прадхаджаюць апавешчэнні пра зростаючыя размеры бака.
WARNING summarizer — leak endpoint hit, bucket now holds 10 MB
WARNING summarizer — leak endpoint hit, bucket now holds 20 MB
...
WARNING summarizer — leak endpoint hit, bucket now holds 240 MB
Пасля пасляпяшнага адзвешчэння настае тыша, без жадных асаблівоў, без лога пра апыт выканання і без паведамлення пра гракціязны завершэнне, таму што механізм OOM killer ядра не дае процэсу можлівасці запісаць лог. Патэрн, калі спачатку выдаюцца адзвешчэння пра проблемы, а пасля — нічога, ўважаецца важлівым для адгукання.
Кластэр па сваю частку паўтарае падтверджэнне прычыны.
kubectl describe pod -n apps -l app=summarizer | grep -A 5 "Last State"
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Код выхідныя 137 — гэта узнавальны знак ядра: SIGKILL (сігнал 9) плюс 128. Ён разлічваў забіўцю з-за нехваткі памяці ад збою прыкладнення, якое завершаецца з кодам 1, а таксама ад невдачы перагляду стану працы, якая таксама дае 137, але з іншым рэчэннем прычыны ў рэзультатах аналізу.
Агент рэагуе. Яго перагляд нехваткі памяці шукае ў індэксе логаў SigNoz рэчэнне “OOMKilled” за апошнія дзесяць хвілін. Адна знаходка спакоюе ў адзіны пакет з прыкладамі логаў і паданням пра перзапуск пада, і пасля чаго даходзіць да дыягназы.
type=oom_kill severity=critical confidence=0.88
fix=kubectl set resources deployment/summarizer -n apps \
--containers=summarizer --limits=memory=512Mi
Практычным рашэннем яўнае падвоўкі ліміту памяці. эта трымкавая мера ўзяцца за прыемную, калі яна дае час на выкананне практычных паследніх праблемаў у прыкладнай програме. Пасля затверджэння бэкенд застосоўвае ліміт у 512 Мб, Kubernetes адбывае поступовы перзапуск, і падмножынка знову працюе з достатнім прыемнам.
Сама вытока памяці застаецца ў кодзе. Змена толькі паднімае максімальны ліміт, а справжняі інцыдэнт завершыцца практычнымі паследнімі праблемаў у кодзе, якія перыядычна усунуць выток памяці. Задача агента — зупініць выток, а асалоджанне прычыны застаецца задачэм для чалавека.
Трэці сцэнарый: затрымка і рэзкы адхіл токенаў
Короткі цыкл вызоў да дорогага канцэнтрацыі выклікае трэціяе абыкновеннае падзея.
for i in {1..5}; do
curl -s -X POST http://localhost:8000/expensive \
| jq '{output_tokens, length_chars}'
sleep 2
done
Нічыта не зламваецца і нічыта не занікае, і пад застаецца ў стане Ранінг. Адпаведзі займаюць ад 8 да 15 секунд кожная, а колькасць токэнаў прыбліжваецца да максімальнага ліміту 2,048 max_tokens.
У SigNoz. Самэльга глухая неудача — гэта тады, калі стак паветранаследжэння прадае сваю цэннасць. Лінія п95 лятэнсу, якая ў нормальных умовах працы застаецца на роўні адпаведна 1,5 секунд, падымаецца, калі выкананы дорогія вызывы, і ўзор змяны ёй ёсць рэзкі, а не паступовы, адтуды што кожны вызыв досягае максімальнага ліміту. Сортаванне трэсаў па трываласці ставяе гэтыя вызывы на верх, дзе яны займаюць ад 8 да 15 секунд кожны.
Адзіёўка спадзейнага трагу ў графіку палачоў паказвае практычна весь час, які прыналежыць адзінаму падзею-спану, як выходны HTTP-запыт да api.groq.com. Спан для самага апрацоўшчыка канцэнтраў ёсць слабкі, таму што апрацоўшчык FastAPI практычна нічога не робіць, а перадача дадзенняў межы LLM займае большую частку часу. Дужыня контэнту адпаведзення спану ў разы большая, чым у звычнай вызову функцыі summarize, пры чым колькасць токенав, якія выходзяць, паказваецца ў байтах. Равная лінія, падых, знова равная лінія, калі заканчываецца цыкл — гэта характэрыстыка неканфігураванага канцэнтра, а не адказкі інфраструктуры.
Агент рэагуе. Перакананне на запазджэнне паўтарае практыкаванне p95 па маршрутах. Дорогі маршрут даходзіць да прыблізна 11 000 мс, тады як порог становіць 5 000 мс, і агент агрупавае этыя даны.
{
"check": "latency_spike",
"spikes": { "/expensive": 11240.0 },
"p95_ms_by_route": {
"/summarize": 1820.0,
"/expensive": 11240.0,
"/healthz": 4.0
}
}
Groq чытае яго і дае тачны дыягназ.
type=token_spike severity=high confidence=0.85
root_cause: The /expensive endpoint has no reasonable max_tokens budget
and a prompt that invites long outputs, causing p95 latency
to spike to 11s versus 1.8s on the normal /summarize path.
fix=kubectl set env deployment/summarizer -n apps MAX_TOKENS_EXPENSIVE=256
Практычна паследованая встановліць зменную сераўіса, яую можа прачытаць прыклад, каб обмежыць квоту токенав на тым канцэнтры. Пасля падтрымкі гэтая зменная встановліваецца. У рэальнай разгортцы прыклад таксама будзе прачытаць MAX_TOKENS_EXPENSIVE з сераўіса. Агент пропануе змяну налаштавання, а не змяну коду, што ўзгодна з мерамі паследованай на рэвэлі kubectl.
Тры неудачы за адны перыяд часу
Калі всі тры сцэнарыі запускаюцца адна за іншай, аптаксіўванне за два гадзіны паказвае тры разныя схемы.
- Цыкл зламаў выражаецца як зубчастая форма у частоце адказоў, якая падымаецца да 100%, падымаецца да нуля і знову падымаецца, пры чым таксама спостыраюцца перэрывы ў працэсе апрацоўкі запытоў.
- Зупінка процэса з-за нехваткі памяці выражаецца як рэзкы спад у частоце запытоў, без падыскання частоты адказоў, таму што процэс так і не мог узвратыць памылку.
На аднай панелі керування паказваюцца тры спосабы выканання бядаў з трыма відзначальныміся формамі, без якіх-лібо спецыяльных правілаў апавешчэння і без ручнага аналізу логаў. Гэта ўласна перавага супарабоцавання трэсаў, метрык і логаў.
Змены, якія трэба здзейсніць для прыменення ў працоўным сераўеры
Дэман стаўіцца на ноутбуке і ігноруе тыя аспекты, якія ёсць важлівымі ў іншых сцэнарыях.
Заменіце храненне інформацыі пра інцыдэты ў памяці. Спіс інцыдэтаў ўтвараецца як слоўнік на Python унутрь падсістэмы-бэкенду, таму пасля перзапуску вяртаецца нуль інформацыі. Выкарыстоўванне SQLite праз SQLAlchemy — гэта змена, якая патрабуе толькі адной лініі коду і не трэбуе дадатковых служб, а Redis падходзіць, калі панель керування павінна масштабавацца на калькі падсістэм-рэплік. Фармат API застаецца тым самым, а змінюецца толькі спосаб хранення даных.
Дадзіце паведамленні ў Slack. У ныяму стане кожны раз, калі павінна быць створана карточка, хтось трэба старацца за панэллю керування, каб гэта з’явілася. Выклік Slack webhook у прыемніке інцыдэтаў у бэкэндзе рашае гэтыя проблемы.
import httpx
SLACK_WEBHOOK = os.environ.get("SLACK_WEBHOOK_URL")def notify_slack(card: dict):
if not SLACK_WEBHOOK:
return
d = card["diagnosis"]
httpx.post(SLACK_WEBHOOK, json={
"text": (
f"*{d['severity'].upper()} — {d['incident_type']}* on `{card['service']}`\n"
f"Root cause: {d['root_cause']}\n"
f"Proposed fix: `{d['proposed_fix']}`\n"
f"Confidence: {int(d['confidence']*100)}%\n"
f"<http://your-dashboard-url|Review on dashboard>"
)
})
Выкліканне спавярнікавальнага прыстрою ў канцы прыймальніка означае, што кожны новы інцыдэнт адправляе сігнал у канал дзеяння, і апраўнік больш не павінен сочыць за вікнам браузера.
Правільна обработка кранцыяў кластэра. Бэкэнд выкананы праз kubectl, што ўсё гаразд, калі сераўерная асяроддзе яго налаштавляе. У рэальным кластэрэ бэкэнд-паду патрэбны ServiceAccount з адпаведнымі правамі RBAC, а таксама Python-кліент Kubernetes, налаштаваны для вікорыстання кранцыяў унутры кластэра.
from kubernetes import client, config
config.load_incluster_config()
apps_v1 = client.AppsV1Api()
core_v1 = client.CoreV1Api()
Кожны тып запуску, які быў затверджаны, стае безпосерэднім вызовам SDK, а не рэчымай у шэле. Хоць такі падход болей дэталізаваны, ён забезпечвае структураванае кераванне памылкамі, логаванне дзеянняў на серверэ API і не створае рызыку втрымкі шэлу.
Згружыце спіс дазволеных запускаў. Дэман прымае будзь-які запуск каманды kubectl set env. У працоўным сераўеры яго трэба обмежыць толькі названымі развёртаннямі і прасторамі імен.
ALLOWED_TARGETS = {
"deployment/summarizer -n apps",
"deployment/worker -n apps",
}
def validate_command(cmd: str) -> bool:
if not any(cmd.startswith(p) for p in ALLOWED_PREFIXES):
return False
if any(term in cmd for term in BLOCKED_TERMS):
return False
if not any(target in cmd for target in ALLOWED_TARGETS):
return False
return True
Модэль не павінна магчымаць прыняцця захода ў жадны простор імен чы аплыванне паza спіс, які ёй быў даўнан.
Праекты, якія варта выявіць далей
Дэман быў створаны з нуля, каб паказаць, як усе элементы з’яўляюцца ў сувязі, а для практычнага вікорыстання будуць аднойчы прыкладзаны існуючыя праекты.
- KubeStellar дапамагае, калі навантажэння распадаецца між кількома кластэрамі. Ён берэ на сябе працу кантрольнага плану, якая ускладнюе усуненне проблем у розных кластэрах, напрыклад, выявленне таго, у якім кластэры знаходзіцца інцыдэнт, прычым перш чым будзе запропанавана якая-небудзь парадка.
- OpenTelemetry працюе набагато глыбэй, чым было неабходна для дэмана. Колектар распаўляецца на працэсары і экспортары, якія можуць аўтаматычна дадаць метаданы Kubernetes да спанаў, таму атрыбуты рысакоў не павінны дадавацца вручную ў маніфестах прыложэння.
Што саме містыцца ў ціх рэштках
Готавыя сістэма — гэта кластэр Kind з трох вузламі з рэалістычной топалогіяй планавання для калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе калькуляцыі на базе каль
Тры ўражаныя канцэнтры выклікаюць тры найбольш распашчастыя аберанціі Kubernetes: зупінку процэса, выкарыстанне всіх рэсурсаў памяці і пагіршэнне каштоўнасці і затрымкі. Агент для усунення проблем прагледвае даныя з SigNoz кожныя 30 секунд, аплікуюць перакананні на адпаведнасць праграмам, агрупавае сігналы ў структураваны запіт, прасіць штучны інтэлект пра дыягназу і публікуе картку з інформацыяй пра інцыдэнт. Бэкэнд FastAPI прымоць такія карткі, пераканаецца, чы рэшэнціі, запропанаваныя для вылечэння, адпавядаюць спіску дазволенага, і запускае затверджаныя каманды за дапамою kubectl. Панель керування на базе React даўае можлівасць затверджваць чы адхіляць рэшэнціі, паказвае індыкатор доверыя да модэлю, банер для сумнівных дыягназаў і лог кожнай выконанай дзеянні.
Усё працуе локальна на API безкоштовнага тарыфа і не каштуе нічога. Код дастаткова маленькі — адносна 600 ліній на Python, адна HTML-стороніца і калькі маніфестаў у фармаце YAML — таму кожны ўжо частака можа быць зрозумелы з початку до канца.
Спаднія матэрыялы
- Самаўздрабнюючыя каманды з калькольнікаў у LangGraph: архітэктар, тэставальнік, дэбаггер — Створыце машыну станоў у LangGraph на Python, у якой архітэктар, распрацоўшчык, сандбокс pytest, дэбаггер і рэвізор саавтарску працуюць і павтараюць працэс, пакуль тэсты не пройдуць.
- Стварэнне агента ReAct у LangChain: экзэкутары, зоны працы і ўсія іх меры — Створыце агента ReAct, які выкарыстоўвае інструменты, пазнакоміцеся з тым, як агент і AgentExecutor дзелюць працу, і дазнаецеся, чаму LangGraph лепш справляецца з кераваннем прабегам, цыкламі і станамі.