Диагностика ресурсов Kubernetes

Профессиональная пошаговая диагностика и устранение неполадок ресурсов Kubernetes с готовыми kubectl-командами и исправлениями.

// промпт
Ты опытный администратор Kubernetes (SRE) с глубокими знаниями kube-scheduler, kubelet, CNI, CSI и control plane. Твоя задача — провести системную диагностику проблемного ресурса, поставить обоснованный диагноз и предложить проверяемое решение. Не пропускай шаги и не угадывай: если данных не хватает, явно укажи, какие команды нужно выполнить. ## Контекст - **Тип ресурса:** {{tip_resursa}} - **Имя и namespace:** {{imia_i_namespace}} - **Симптомы / наблюдаемое поведение:** {{simptomy}} - **Версия Kubernetes:** {{versiia_kubernetes}} - **Окружение:** {{okruzenie}} Манифест и/или вывод диагностических команд: ``` {{manifesty_i_vyvod_komand}} ``` ## Что нужно сделать 1. **Инспекция и сбор данных** - Проанализируй статус, события (`Events`), `conditions` и причины перезапусков. - Сопоставь спецификацию ресурса с фактическим состоянием кластера. 2. **Постановка диагноза** - Определи корневую причину, а не симптом (например, `ImagePullBackOff`, `CrashLoopBackOff`, `Pending` из-за `resources.requests`, `OOMKilled`, проблемы с `readiness/liveness probe`, RBAC, сеть/DNS, PVC). - Кратко обоснуй, почему именно эта причина, опираясь на предоставленные данные. 3. **Команды для проверки** - Дай конкретные `kubectl` команды (`describe`, `logs --previous`, `get events --sort-by`, `top`) для подтверждения гипотезы. 4. **Решение и профилактика** - Пошаговая процедура исправления с исправленным фрагментом манифеста. - Применимые best practices и как предотвратить повторение. ## Формат ответа - **Диагноз:** одно-два предложения с корневой причиной. - **Обоснование:** маркированный список фактов из данных. - **Команды для подтверждения:** блок с `kubectl`. - **Исправление:** нумерованные шаги + блок с YAML. - **Профилактика:** короткий список. Если входных данных недостаточно для уверенного диагноза, перечисли недостающую информацию вместо догадок.
Заполните переменные

Пример запуска

Сгенерировано Claude Opus 5.5, 23 сентября 2026 г.

Значения переменных

Тип ресурса
Deployment (и связанные Pod'ы)
Имя и namespace
payment-api, namespace billing
Симптомы
Поды постоянно уходят в CrashLoopBackOff с интервалом ~2-5 минут, счётчик RESTARTS растёт (уже 47 за последние 3 часа). В момент падения в логах приложения (Java/Spring Boot) видна java.lang.OutOfMemoryError: Java heap space. При этом kubectl top показывает, что использование памяти подом резко подскакивает перед рестартом. Проблема началась сразу после вчерашнего релиза v2.14.0, в котором увеличили batch-размер обработки платёжных транзакций с 100 до 1000.
Версия kubernetes
v1.28.6
Окружение
Продакшен, self-managed кластер на bare-metal через kubeadm, 3 master-ноды + 8 worker-нод, Ubuntu 22.04, CNI Calico v3.27, CSI Longhorn v1.6, containerd 1.7
Манифесты и вывод команд
$ kubectl get pods -n billing -l app=payment-api NAME READY STATUS RESTARTS AGE payment-api-7f9d8c6b45-2kx9p 0/1 CrashLoopBackOff 47 (2m ago) 3h12m payment-api-7f9d8c6b45-9mvqt 0/1 CrashLoopBackOff 45 (3m ago) 3h12m payment-api-7f9d8c6b45-jr4wz 1/1 Running 43 (7m ago) 3h12m $ kubectl describe pod payment-api-7f9d8c6b45-2kx9p -n billing ... Containers: payment-api: Container ID: containerd://a1b2c3d4e5f6... Image: registry.internal.example.com/billing/payment-api:v2.14.0 Port: 8080/TCP State: Waiting Reason: CrashLoopBackOff Last State: Terminated Reason: OOMKilled Exit Code: 137 Started: Tue, 23 Sep 2026 10:41:02 +0300 Finished: Tue, 23 Sep 2026 10:43:47 +0300 Ready: False Restart Count: 47 Limits: cpu: 500m memory: 256Mi Requests: cpu: 250m memory: 256Mi Liveness: http-get http://:8080/actuator/health delay=30s timeout=1s period=10s #success=1 #failure=3 Readiness: http-get http://:8080/actuator/health delay=15s timeout=1s period=5s #success=1 #failure=3 Environment: JAVA_OPTS: -Xms256m -Xmx256m BATCH_SIZE: 1000 Conditions: Type Status Initialized True Ready False ContainersReady False PodScheduled True Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 3h12m default-scheduler Successfully assigned billing/payment-api-7f9d8c6b45-2kx9p to worker-node-04 Normal Pulled 3h12m kubelet Container image already present on machine Normal Created 3h12m kubelet Created container payment-api Normal Started 3h12m kubelet Started container payment-api Warning BackOff 28s (x39 over 3h10m) kubelet Back-off restarting failed container payment-api in pod payment-api-7f9d8c6b45-2kx9p_billing $ kubectl logs payment-api-7f9d8c6b45-2kx9p -n billing --previous --tail=30 2026-09-23 10:43:41.221 INFO 1 --- [io-8080-exec-9] c.e.billing.service.TxnBatchProcessor : Processing batch of 1000 transactions, batchId=88213 2026-09-23 10:43:42.884 WARN 1 --- [io-8080-exec-9] o.s.o.jpa.JpaSystemException : could not extract ResultSet 2026-09-23 10:43:43.019 ERROR 1 --- [io-8080-exec-9] c.e.billing.service.TxnBatchProcessor : Exception in batch processing java.lang.OutOfMemoryError: Java heap space at com.example.billing.service.TxnBatchProcessor.loadTransactionEntities(TxnBatchProcessor.java:142) at com.example.billing.service.TxnBatchProcessor.process(TxnBatchProcessor.java:88) at java.base/java.lang.Thread.run(Thread.java:840) 2026-09-23 10:43:47.331 INFO 1 --- [Thread-3] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to BROKEN $ kubectl top pod -n billing -l app=payment-api NAME CPU(cores) MEMORY(bytes) payment-api-7f9d8c6b45-2kx9p 480m 248Mi payment-api-7f9d8c6b45-9mvqt 475m 251Mi payment-api-7f9d8c6b45-jr4wz 310m 230Mi Фрагмент манифеста Deployment (текущая версия, spec.template.spec.containers[0]): - name: payment-api image: registry.internal.example.com/billing/payment-api:v2.14.0 ports: - containerPort: 8080 env: - name: JAVA_OPTS value: "-Xms256m -Xmx256m" - name: BATCH_SIZE value: "1000" resources: requests: cpu: 250m memory: 256Mi limits: cpu: 500m memory: 256Mi livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 15 periodSeconds: 5 failureThreshold: 3

Диагностика: payment-api (namespace billing)

Диагноз

Корневая причина — неверный расчёт памяти JVM, который вскрылся после увеличения batch-размера. -Xmx256m равен limits.memory: 256Mi, поэтому у JVM нет запаса под non-heap память. В v2.14.0 BATCH_SIZE вырос с 100 до 1000, и рабочий набор heap при загрузке JPA-сущностей (loadTransactionEntities) превысил допустимое. В результате происходят сразу два отказа:

  • внутри JVM: OutOfMemoryError: Java heap space;
  • на уровне cgroup: kernel OOM killer завершает контейнер (OOMKilled, exit 137).

Причина не в kubelet, планировщике, сети или хранилище. Это ошибка конфигурации ресурсов приложения, а триггер — изменение в коде и конфигурации релиза.

Обоснование

Тип завершения. Last State: Terminated, Reason: OOMKilled, Exit Code: 137 означает SIGKILL от OOM killer по лимиту memory cgroup контейнера. Liveness probe и само приложение контейнер не завершали.

Хронология одного цикла (под 2kx9p):

Время Событие
10:41:02 Старт контейнера
10:43:41 Processing batch of 1000 transactions, batchId=88213
10:43:42 JpaSystemException: could not extract ResultSet. Вероятно, OOM возник уже при материализации ResultSet
10:43:43 OutOfMemoryError: Java heap space в TxnBatchProcessor.loadTransactionEntities:142
10:43:47 Spring переводит LivenessState в BROKEN. В ту же секунду Finished: ядро убивает процесс

Время жизни контейнера около 2 мин 45 с, что совпадает с наблюдаемым интервалом 2–5 минут. Liveness probe (3 × 10 с) не успела бы сработать за 4 секунды. Значит, убил именно cgroup OOM.

Бюджет памяти не сходится даже теоретически. В 256Mi лимита нужно уместить:

  • heap до 256 MiB;
  • Metaspace (Spring Boot + Hibernate обычно 80–150 MiB);
  • CodeCache (20–50 MiB);
  • стеки потоков Tomcat (до 200 потоков);
  • служебные структуры GC, direct/NIO буферы, память glibc malloc.

Реальный RSS JVM с таким -Xmx составляет 400–500 MiB. Пока batch был 100, heap не заполнялся целиком и RSS оставался чуть ниже лимита. При batch 1000 heap заполняется, GC касается всех страниц, и RSS упирается в лимит.

Метрики. kubectl top показывает 248–251Mi при лимите 256Mi у падающих подов. Под jr4wz (230Mi, 43 рестарта) находится в том же цикле и просто пока не дошёл до своего batch.

Корреляция с релизом. Проблема началась сразу после v2.14.0 (BATCH_SIZE: 1000, в 10 раз больше). Стек указывает на загрузку сущностей всего batch в память одной выборкой.

Все реплики падают одинаково. Это типично для «ядовитого» batch: batch 88213 не фиксируется как обработанный, и после рестарта его подхватывает та же или соседняя реплика. Для платёжного сервиса нужно проверить идемпотентность, см. раздел о недостающих данных.

Сопутствующие проблемы. Они не являются корневой причиной, но усугубляют картину:

  • limits.cpu: 500m, фактическое потребление 475–480m, то есть CPU throttling. JVM видит ActiveProcessorCount=1. При 1 CPU и менее 1792 MB памяти JDK автоматически выбирает SerialGC, а не G1: GC-паузы длинные, старт медленный.
  • Liveness и readiness используют один эндпоинт /actuator/health с timeout=1s. При GC-паузах или недоступности БД это может вызывать ложные рестарты и каскадные отказы. startupProbe отсутствует.
  • QoS класс Burstable: memory request равен limit, но CPU request меньше limit.
  • Нет -XX:+ExitOnOutOfMemoryError. После heap OOM JVM остаётся в неопределённом состоянии, пока её не добьёт ядро или probe.

Исключено по данным: Pending/scheduling (PodScheduled=True), ImagePull (image already present), PVC, RBAC, CNI/DNS. Событий о проблемах с томами, сетью или eviction нет.

Команды для подтверждения

NS=billing; POD=payment-api-7f9d8c6b45-2kx9p

# 1. Причина завершения по всем подам (OOMKilled vs Error/liveness)
kubectl get pods -n $NS -l app=payment-api -o custom-columns=\
NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount,\
REASON:.status.containerStatuses[0].lastState.terminated.reason,\
EXIT:.status.containerStatuses[0].lastState.terminated.exitCode,\
NODE:.spec.nodeName

# 2. События по времени: нет ли Unhealthy (liveness), Evicted, MemoryPressure
kubectl get events -n $NS --sort-by=.lastTimestamp \
  --field-selector involvedObject.name=$POD
kubectl get events -n $NS --sort-by=.lastTimestamp | grep -Ei 'unhealthy|oom|evict|killing'

# 3. Логи предыдущего запуска: какой batch валит поды, повторяется ли batchId
kubectl logs $POD -n $NS --previous | grep -E 'batchId|OutOfMemory|LivenessState'
for p in $(kubectl get pods -n $NS -l app=payment-api -o name); do
  echo "== $p"; kubectl logs $p -n $NS --previous | grep -oE 'batchId=[0-9]+' | tail -3
done

# 4. Реально ли JVM применяет JAVA_OPTS и какие флаги выбраны
#    (выполнять в окне ~2 мин, пока контейнер жив)
kubectl exec -n $NS $POD -- sh -c 'cat /proc/1/cmdline | tr "\0" " "; echo'
kubectl exec -n $NS $POD -- jcmd 1 VM.flags        # MaxHeapSize, UseSerialGC/UseG1GC
kubectl exec -n $NS $POD -- jcmd 1 GC.heap_info
kubectl exec -n $NS $POD -- sh -c 'cat /sys/fs/cgroup/memory.max; cat /sys/fs/cgroup/memory.current; grep -E "anon|file " /sys/fs/cgroup/memory.stat'
kubectl exec -n $NS $POD -- sh -c 'grep -E "nr_throttled|throttled_usec" /sys/fs/cgroup/cpu.stat'

# 5. Подтверждение cgroup OOM на ноде (а не глобального OOM ноды)
kubectl debug node/worker-node-04 -it --image=ubuntu:22.04 -- \
  chroot /host sh -c 'dmesg -T | grep -iE "memory cgroup out of memory|oom-kill|Killed process" | tail -20'
kubectl describe node worker-node-04 | grep -A8 -E 'Conditions|Allocated resources'

# 6. Что изменилось в релизе и есть ли ограничения на увеличение ресурсов
kubectl rollout history deployment/payment-api -n $NS
kubectl rollout history deployment/payment-api -n $NS --revision=<N-1>   # сравнить env/resources
kubectl get limitrange,resourcequota,hpa,pdb -n $NS
kubectl describe resourcequota -n $NS

Ожидаемые результаты, подтверждающие диагноз:

  • REASON=OOMKilled, EXIT=137 у всех подов;
  • в dmesg есть Memory cgroup out of memory: Killed process ... (java);
  • memory.max = 268435456;
  • MaxHeapSize = 268435456 и UseSerialGC;
  • нет событий Unhealthy перед убийством.

Исправление

Этап A. Немедленная стабилизация (минуты)

  1. Снизить batch без пересборки образа. Это быстрее и безопаснее, чем rollout undo, потому что v2.14.0 мог включать миграции БД, несовместимые с v2.13:
    kubectl set env deployment/payment-api -n billing BATCH_SIZE=100
    kubectl rollout status deployment/payment-api -n billing
    
    Если v2.14.0 ломается и при BATCH_SIZE=100, и совместимость схемы БД подтверждена, откатить релиз: kubectl rollout undo deployment/payment-api -n billing.
  2. Проверить судьбу проблемного batch (88213 и соседних) в БД или очереди. Нет ли частично применённых или дублированных платёжных транзакций? Это обязательно до того, как сервис начнёт штатно обрабатывать поток.

Этап B. Правильный sizing памяти (в течение дня, через GitOps/CI, а не kubectl edit)

  1. Убрать жёсткий -Xmx. Размер heap задаётся от лимита контейнера через MaxRAMPercentage, чтобы оставить примерно 25–30% под non-heap.
  2. Поднять лимит памяти. Стартовое значение 1Gi: heap около 720 MiB плюс запас около 300 MiB под non-heap. Итоговое значение уточнить по GC-логам и NMT под реальной нагрузкой с batch 1000.
  3. Сделать память Guaranteed (request = limit). CPU request поднять, CPU limit увеличить до 2 или убрать по политике кластера. Задать ActiveProcessorCount=2 и явно включить G1, чтобы JVM не выбрала SerialGC.
  4. Добавить ExitOnOutOfMemoryError и heap dump. Heap dump для платёжного сервиса может содержать чувствительные данные: писать на защищённый том и ограничить доступ.
  5. Развести probes: liveness на /actuator/health/liveness, readiness на /actuator/health/readiness, плюс startupProbe.
  6. Перед применением проверить квоты и ёмкость:
    • ResourceQuota и LimitRange в billing должны разрешать 3 × 1Gi;
    • на worker-нодах должно хватать allocatable-ресурсов (kubectl describe node | grep -A8 Allocated);
    • есть ли HPA по памяти: процент утилизации пересчитается от нового request.
- name: payment-api
  image: registry.internal.example.com/billing/payment-api:v2.14.0
  ports:
    - containerPort: 8080
  env:
    - name: JAVA_OPTS
      value: >-
        -XX:MaxRAMPercentage=70.0
        -XX:InitialRAMPercentage=50.0
        -XX:+UseG1GC
        -XX:ActiveProcessorCount=2
        -XX:MaxMetaspaceSize=192m
        -XX:ReservedCodeCacheSize=64m
        -XX:MaxDirectMemorySize=64m
        -XX:+ExitOnOutOfMemoryError
        -XX:+HeapDumpOnOutOfMemoryError
        -XX:HeapDumpPath=/dumps
        -XX:NativeMemoryTracking=summary
        -Xlog:gc*:stdout:time,uptime,level,tags
    - name: BATCH_SIZE
      value: "100"          # вернуть к 1000 только после исправления кода (шаг 9) и нагрузочного теста
  resources:
    requests:
      cpu: "1"
      memory: 1Gi
    limits:
      cpu: "2"
      memory: 1Gi           # request == limit по памяти
  volumeMounts:
    - name: dumps
      mountPath: /dumps
  startupProbe:
    httpGet:
      path: /actuator/health/liveness
      port: 8080
    periodSeconds: 5
    failureThreshold: 36    # до 180 с на старт
  livenessProbe:
    httpGet:
      path: /actuator/health/liveness
      port: 8080
    periodSeconds: 10
    timeoutSeconds: 3
    failureThreshold: 3
  readinessProbe:
    httpGet:
      path: /actuator/health/readiness
      port: 8080
    periodSeconds: 5
    timeoutSeconds: 3
    failureThreshold: 3
# на уровне spec.template.spec:
# volumes:
#   - name: dumps
#     emptyDir:
#       sizeLimit: 2Gi      # или PVC Longhorn, если дампы нужны после рестарта

В application.yaml должно быть management.endpoint.health.probes.enabled: true. В Kubernetes Spring Boot 2.3+ включает это автоматически. Также убедитесь, что JAVA_OPTS реально подставляется в ENTRYPOINT (шаг 4 проверки). Если нет, используйте JAVA_TOOL_OPTIONS.

Этап C. Исправление в коде (корневая причина на уровне приложения)

  1. Переписать TxnBatchProcessor.loadTransactionEntities. Сейчас он материализует весь batch с графом сущностей в persistence context. Варианты:

    • обработка чанками по 100–200 в рамках batch 1000: flush() + clear() EntityManager после каждого чанка;
    • DTO-проекции вместо полных сущностей; отказ от EAGER-связей; JOIN FETCH только нужного;
    • hibernate.jdbc.fetch_size / Stream<> с @QueryHints(HINT_FETCH_SIZE) вместо getResultList();
    • hibernate.jdbc.batch_size для записи.
  2. Нагрузочный тест с BATCH_SIZE=1000 на стенде с теми же лимитами. Снять:

    • jcmd 1 GC.heap_info;
    • jcmd 1 VM.native_memory summary;
    • пиковый memory.current.

    Лимит = пиковый RSS + 20–25%.

  3. Поднимать BATCH_SIZE постепенно (100 → 250 → 500 → 1000) с наблюдением за метриками.

Профилактика

  • Правило sizing для JVM: никогда не ставить -Xmx ≥ limits.memory. Использовать MaxRAMPercentage 60–75%.
  • Параметры, влияющие на объём данных в памяти (batch, page size, cache size), считать изменением capacity: обязательный нагрузочный тест и ревью resources в том же PR.
  • Алерты:
    • container_memory_working_set_bytes / limit > 0.85;
    • kube_pod_container_status_last_terminated_reason{reason="OOMKilled"};
    • increase(kube_pod_container_status_restarts_total[15m]) > 2;
    • JVM-метрики jvm_memory_used_bytes{area="heap"} через Micrometer.
  • Progressive delivery: canary (Argo Rollouts/Flagger) с автоматическим откатом по рестартам и error rate. Все 3 реплики не должны получать новый релиз одновременно.
  • Идемпотентность batch-обработки платежей: фиксация прогресса по чанкам, DLQ или карантин для «ядовитого» batch после N неудач, чтобы один batch не валил все реплики.
  • Политика в namespace: LimitRange с разумными дефолтами; policy-проверка (Kyverno/Gatekeeper) на соответствие -Xmx и лимитов; раздельные liveness/readiness и наличие startupProbe для JVM-сервисов.
  • VPA в режиме Off (recommendation only) для payment-api, чтобы видеть реальные рекомендации по памяти и CPU.

Недостающие данные

Для полной уверенности не хватает следующего:

  1. Применяются ли JAVA_OPTS на деле (ENTRYPOINT образа, jcmd 1 VM.flags). Если нет, JVM берёт дефолтный heap в 25% от 256Mi, около 64 MiB. Тогда heap OOM ещё очевиднее, а расчёт MaxRAMPercentage нужно проверить на фактическом образе.
  2. Профиль памяти v2.13 при batch 100 (метрики за прошлую неделю). Он покажет, насколько близко к лимиту сервис работал до релиза.
  3. Heap dump или NMT на момент OOM. Это подтвердит, что память занимают именно сущности batch, а не утечка, например кеш в новом коде v2.14.0.
  4. Повторяется ли один и тот же batchId во всех падениях и состояние транзакций этого batch в БД. Это критично для целостности платёжных данных.
  5. Содержит ли v2.14.0 миграции схемы БД. От этого зависит, безопасен ли rollout undo.

Похожие промпты

IT и Администрирование

Ревью и рефакторинг PowerShell-скрипта

Разбирает существующий скрипт PowerShell: что сломается на реальных данных, где ошибки глушатся вместо обработки и что переписать в первую очередь, а что оставить.

IT и Администрирование

Скрипт PowerShell под задачу

Пишет скрипт PowerShell с обработкой ошибок, логированием и режимом пробного прогона — так, чтобы его можно было запустить на рабочей машине и не бояться последствий.

IT и Администрирование

Диагностика проблем VPN-подключения

Методично диагностирует проблемы VPN: соединение не устанавливается, скорость падает, утекает DNS — по одному шагу, с командами под вашу ОС.

IT и Администрирование

Помощник по настройке WireGuard и OpenVPN

Пошагово разворачивает собственный VPN-сервер на WireGuard или OpenVPN: готовые команды и конфиги, firewall, клиентские профили и проверка утечек.