Практическое применение самовосстанавливающегося Kubernetes: три сбоя, сигналы SigNoz и одобренные способы устранения проблем.
Самовосстанавливающийся Kubernetes с OpenTelemetry и SigNoz: цикл сбоев, принудительное завершение из-за нехватки памяти и резкий скачок токенов на Kind, что показывает каждый сигнал, и одобренные решения с использованием ИИ.
Источники: Self-Healing Kubernetes: Wiring OpenTelemetry, SigNoz, and a Groq-Powered Remediation Agent (PART 7)
В демо-версии есть несколько движущихся компонентов. Локальный кластер Kind хостит SigNoz для отслеживания трейсов и логов, а также Python-приложение, у которого три конечных точки намеренно не работают. Агент для устранения сбоев проверяет наличие аномалий и запрашивает у Groq диагноз, а панель управления на React позволяет оператору одобрить или отклонить предложенное решение перед тем, как что-либо будет запущено. Как только всё это создано, интересно наблюдать за его работой. В ходе этого теста каждый сбой имитируется в реальном времени с использованием всей стек-архитектуры, анализируются записи SigNoz по каждому инциденту, и указывается, что необходимо изменить, прежде чем кто-либо сможет доверять этой конфигурации за пределами ноутбука.
Подтверждение работоспособности стека
Перед тем, как будет имитирован любой сбой, должны работать пять компонентов. Первая проверка заключается в том, чтобы все поды находились в рабочем состоянии в обоих пространствах имен.
# 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
Недавние сообщения об отсутствии ошибок в выводе означают, что агент работает нормально. Контейнер, который не находится в состоянии «Running», следует сначала проверить с помощью команды 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
After the last warning comes silence, with no exception, no stack trace and no graceful shutdown message, because the kernel's OOM killer gives the process no chance to log. A run of climbing warnings followed by nothing is the pattern worth recognizing.
The cluster confirms the cause from its side.
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.
В SigNoz. Именно в таких скрытых сбоях проявляется ценность стека инструментов для наблюдаемости. График задержек p95, который в обычных условиях работы остается на уровне около 1,5 секунды, резко возрастает при выполнении дорогостоящих запросов; при этом изменение имеет резкий характер, а не постепенное снижение, поскольку каждый запрос достигает максимального лимита. Сортировка трейсов по времени выполнения выдвигает такие запросы вверх — их время выполнения составляет от 8 до 15 секунд.
При отображении детального трейса в графике выполнения видно, что почти весь времени занимает обработка одного подзадания — выходной HTTP-запрос к api.groq.com. Сам период обработки эндпоинта крайне короткий, поскольку обработчик FastAPI практически ничего не делает, а основное время уходит на обмен данными с большой языковой моделью. Длина ответа в этом периоде значительно превышает длину ответа при обычном вызове функции 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. В текущей схеме кто-то должен постоянно наблюдать панель управления, чтобы узнать о появлении карточки. Вызов webhook в Slack внутри механизма обработки инцидентов на стороне бэкенда решает эту проблему.
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
Модель не должна иметь возможности применять исправления к любому пространству имён или среде развертывания, находящимся вне списка, который ей был предоставлен.
Проекты, которые стоит изучить далее
Демо-версия была создана с нуля, чтобы показать, как взаимосвязаны отдельные компоненты, тогда как в реальных условиях разработка будет основана на существующих проектах.
- KubeStellar помогает, когда рабочая нагрузка распространяется по нескольким кластерам. Он берёт на себя задачи управления, которые усложняют устранение проблем в разных кластерах, например, определение того, в каком кластере произошла проблема, прежде чем будет предложено какое-либо решение.
- OpenTelemetry обеспечивает гораздо более глубокую функциональность, чем требовалось для демо-версии. Устройство сбора данных содержит процессоры и экспортеры, которые могут автоматически добавлять метаданные Kubernetes к элементам отслеживания, поэтому атрибуты ресурсов не нужно вручную указывать в манифестах приложений.
Что включает в себя вся структура
Готовая система представляет собой кластер Kind из трех узлов с реалистичной топологией планирования многократных узлов, а SigNoz развернут с помощью Helm; он обрабатывает трейсы, метрики и логи через OpenTelemetry Collector. Сервис на Python FastAPI автоматически оснащается инструментами при запуске, благодаря чему каждый HTTP-запрос и вызов LLM превращается в трейс, а каждая строка лога коррелируется без необходимости использования дополнительного кода инструментации в приложении.
Три неисправных конечных точки вызывают три наиболее распространенные проблемы в Kubernetes: сбой процесса, исчерпание памяти и ухудшение показателей стоимости и задержек. Агент для устранения проблем проверяет SigNoz каждые 30 секунд, применяет проверки пороговых значений, объединяет сигналы в структурированный запрос, запрашивает у большой языковой модели диагноз и создает карточку инцидента. Бэкенд на FastAPI принимает эти карточки, проверяет предложенные решения по списку разрешенных действий и выполняет одобренные команды с помощью kubectl. Панель управления на React предоставляет возможности для одобрения и отклонения действий, индикатор уровня уверенности модели, баннер для сомнительных диагнозов и журнал всех выполненных операций.
Все работает локально с использованием API бесплатного тарифа, и это не стоит ничего. Код достаточно компактен — около 600 строк на Python, одна HTML-страница и несколько YAML-манифестов, — поэтому каждый его элемент можно понять полностью.
Связанные материалы
- Самовосстанавливающаяся команда многих агентов в LangGraph: архитектор, тестер, отладчик — Создайте автомат в состояниях LangGraph на Python, в котором архитектор, разработчик, среда тестирования pytest, отладчик и рецензент сотрудничают друг с другом и повторяют процесс до тех пор, пока тесты не пройдут.
- Создание агента ReAct в LangChain: исполнители, рабочие площадки и их ограничения — Создайте агента, использующего инструменты, посмотрите, как агент и AgentExecutor делят работу, и узнайте, почему LangGraph лучше справляется с управлением потоком, циклами и состоянием.