Три часа ночи, алерт, под в проде висит в статусе Pending, рядом второй ушёл в CrashLoopBackOff, а ещё один узел только что стал NotReady. Паника тут плохой советчик: kubernetes troubleshooting - это не угадайка, а строгое дерево решений. Симптом виден сразу в
Код: Выделить всё
kubectl get podsГлавный принцип отладки kubernetes: не лечи симптом, найди слой. В кластере событие проходит цепочку: API-сервер принял манифест -> scheduler выбрал узел -> kubelet на узле дёрнул containerd через CRI -> containerd вытянул образ и поднял контейнер -> CNI выдал поду адрес -> kube-proxy/Cilium прописали правила для Service. Сломаться может любое звено, и каждое звено оставляет след - в events, в logs или в conditions. Твоя задача - читать эти следы, а не перезапускать наугад.
Запомни три рабочих инструмента, которыми закрывается 95 процентов разборов.
Код: Выделить всё
kubectl describeКод: Выделить всё
kubectl logsКод: Выделить всё
--previousКод: Выделить всё
kubectl get eventsКод: Выделить всё
kubectl debug
Pod Pending: под назначить некуда
Pod pending почти всегда означает: scheduler не смог найти узел, который удовлетворяет требованиям пода. Первое и единственно правильное действие - смотреть события:
Код: Выделить всё
kubectl describe pod web-7d9f8 -n shopКод: Выделить всё
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m default-scheduler 0/5 nodes are available:
3 Insufficient cpu, 2 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }.Код: Выделить всё
requests.cpuВторая частая причина Pending - PVC не привязался. Под ждёт том, тома нет:
Код: Выделить всё
Warning FailedScheduling pod has unbound immediate PersistentVolumeClaimsКод: Выделить всё
kubectl get pvc -n shop
NAME STATUS VOLUME CAPACITY STORAGECLASS AGE
data-web Pending 4mКод: Выделить всё
storageClassNameКод: Выделить всё
kubectl describe pvc data-web -n shopТретья причина - сам ты затянул гайки affinity. Жёсткий
Код: Выделить всё
requiredDuringSchedulingIgnoredDuringExecutionImagePullBackOff: контейнер не из чего создать
Под не Pending (узел назначен), но контейнер не стартует, потому что не качается образ. В
Код: Выделить всё
get podsКод: Выделить всё
Warning Failed 12s kubelet Failed to pull image "cr.yandex/crp.../api:v1.4":
failed to resolve reference: unexpected status: 401 UnauthorizedКод: Выделить всё
kubectl create secret docker-registry regcred \
--docker-server=cr.yandex --docker-username=json_key \
--docker-password="$(cat key.json)" -n shopКод: Выделить всё
imagePullSecrets: [{name: regcred}]Код: Выделить всё
:latestCrashLoopBackOff: стартует и падает по кругу
crashloopbackoff - самый пугающий с виду статус, но самый понятный по логике: контейнер запустился, процесс завершился, kubelet перезапустил, тот снова упал, и теперь kubelet ждёт перед следующей попыткой (тот же экспоненциальный backoff). Сам статус ничего не говорит о причине - причина в логах. И ключевой нюанс: к моменту, когда ты смотришь, текущий контейнер может ещё стартовать, а упал предыдущий. Поэтому:
Код: Выделить всё
kubectl logs web-7d9f8 -n shop --previousКод: Выделить всё
--previousКод: Выделить всё
-p- Бизнес-ошибка: "could not connect to postgres: connection refused", "config file not found", "panic: missing env DB_PASSWORD". Конфиг, секрет, недоступная зависимость.
- Кривая команда: переопределил /
Код: Выделить всё
commandв манифесте, опечатался в пути к бинарю - контейнер мгновенно падает с exit 127 (command not found) или 126.Код: Выделить всё
args - Падение на пробах: приложение живо, но не проходит, и kubelet сам убивает контейнер. В Events увидишь "Liveness probe failed: ... connection refused". Частая причина - проба на порт, который приложение ещё не открыло (нужен
Код: Выделить всё
livenessProbeдля медленного старта), или слишком короткийКод: Выделить всё
startupProbe/Код: Выделить всё
initialDelaySeconds.Код: Выделить всё
timeoutSeconds - OOMKilled - о нём отдельно ниже.
Код: Выделить всё
kubectl get pod web-7d9f8 -n shop -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
{"exitCode":1,"reason":"Error","startedAt":"...","finishedAt":"..."}OOMKilled и код 137: память кончилась
Код 137 = 128 + 9, где 9 это SIGKILL. В Kubernetes это в подавляющем большинстве случаев OOM-killer ядра. Два сценария. Первый, частый: контейнер превысил собственный
Код: Выделить всё
resources.limits.memoryКод: Выделить всё
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 137Код: Выделить всё
kubectl top pod -n shop
NAME CPU(cores) MEMORY(bytes)
web-7d9f8 45m 498MiУзел NotReady: машина выпала из обоймы
node notready - это здоровье конкретной ноды, а не отдельного пода. Сначала общая картина и conditions:
Код: Выделить всё
kubectl get nodes
NAME STATUS ROLES AGE VERSION
worker-3 NotReady <none> 40d v1.31.4
kubectl describe node worker-3Код: Выделить всё
DiskPressure=TrueКод: Выделить всё
MemoryPressure=TrueКод: Выделить всё
systemctl status kubelet
journalctl -u kubelet -n 200 --no-pagerКод: Выделить всё
/etc/cni/net.d/Код: Выделить всё
/opt/cni/bin/Код: Выделить всё
/var/lib/containerdКод: Выделить всё
/var/lib/kubeletКод: Выделить всё
systemctl status containerd
df -h /var/lib/containerd /var/lib/kubelet
crictl ps # что реально крутится в CRI
crictl rmi --prune # подчистить мусорные образы, если забит imagefsКод: Выделить всё
containerLogMaxSizeКод: Выделить всё
containerLogMaxFilesКод: Выделить всё
systemctl status k3s-agentDNS не резолвит и Service без endpoints
Под живой, но "не видит" соседей - почти всегда это DNS или сервисная связность. Быстрая проверка изнутри пода:
Код: Выделить всё
kubectl exec -it web-7d9f8 -n shop -- nslookup postgres.shop.svc.cluster.localКод: Выделить всё
kubectl get pods -n kube-system -l k8s-app=kube-dnsКод: Выделить всё
ndots:5Код: Выделить всё
api.partner.ruКод: Выделить всё
api.partner.ru.Код: Выделить всё
dnsConfigТеперь Service без endpoints - топовая причина "сервис есть, а 503/connection refused". Проверяй цепочку:
Код: Выделить всё
kubectl get endpointslices -n shop -l kubernetes.io/service-name=postgres
NAME ADDRESSTYPE ENDPOINTS PORTS
postgres-abc12 IPv4 <none> 5432Код: Выделить всё
selectorКод: Выделить всё
labelsКод: Выделить всё
kubectl get svc postgres -n shop -o jsonpath='{.spec.selector}'
kubectl get pods -n shop --show-labelsКод: Выделить всё
kubectl get netpol -n shopКод: Выделить всё
cilium connectivity testГрабли и антипаттерны
- logs без --previous при CrashLoop - смотришь живой контейнер вместо упавшего и не видишь ошибку.
- Перезапуск как первая реакция. delete pod иногда "помогает" (под уехал на другой узел), но маскирует причину - завтра рванёт снова.
- Чтение симптома вместо Events. CrashLoopBackOff/ImagePullBackOff - это не диагноз, а класс. Диагноз всегда в Events и logs.
- Liveness вместо readiness. Повесил агрессивный livenessProbe на медленный старт - kubelet убивает приложение в цикле, и ты получаешь рукотворный CrashLoop. Для старта - startupProbe, для "не лей трафик пока" - readiness.
- Поды без limits памяти. Один такой под на узле способен утащить в OOM соседей и сделать ноду NotReady по MemoryPressure.
- requests == limits перепутаны со смыслом. Scheduler планирует по requests; поставил их в ноль - набьёшь узел и поймаешь node-level OOM.
Возьми любой dev-кластер (kind, minikube, k3s) и пройди руками:
- Создай Deployment с (сто ядер). Получи Pending, найди в
Код: Выделить всё
requests.cpu: "100"строку "Insufficient cpu". Снизь до 100m - под взлетит.Код: Выделить всё
describe pod - Укажи образ . Поймай ImagePullBackOff, найди "manifest unknown" в Events. Поправь тег.
Код: Выделить всё
nginx:nosuchtag - Запусти контейнер с . Получи CrashLoopBackOff, прочитай
Код: Выделить всё
command: ["sh","-c","echo bye; exit 1"], проверь exitCode через jsonpath.Код: Выделить всё
logs --previous - Поставь приложению, которое жрёт больше. Поймай Reason: OOMKilled и Exit Code 137 в describe.
Код: Выделить всё
resources.limits.memory: "16Mi" - Создай Service с селектором . Убедись, что EndpointSlice пустой. Поправь селектор - endpoints появятся.
Код: Выделить всё
app: wrong
- Под висит в Pending. Какая первая команда и какой раздел её вывода ты читаешь? Назови три типовые причины.
- Чем отличается от
Код: Выделить всё
kubectl logsи почему второй критичен при CrashLoopBackOff?Код: Выделить всё
kubectl logs --previous - Что означает Exit Code 137, чем container-level OOM отличается от node-level и почему память - несжимаемый ресурс?
- Service есть, а трафик идёт в 503. Как за две команды проверить, что проблема в селекторе или в readiness?
Отладка kubernetes сводится к дисциплине: симптом из
Код: Выделить всё
get podsКод: Выделить всё
get nodes