lenec ru

← все посты

Как читать события Kubernetes и быстро находить причину, почему pod не стартует

10K

Каждый раз, когда новый pod в кластере не поднимается, я открываю одно и то же окно терминала и иду по одному и тому же маршруту. За восемь лет работы с k8s у меня накопился короткий чек-лист команд, который покрывает 90% случаев: образ не скачался, не хватило ресурсов, ошибка в манифесте, проблемы с правами, кривой liveness. Здесь я выложу его так, как сам бы хотел увидеть три года назад.

Версия — k8s 1.30, но всё применимо к 1.27+. Команды копируются и работают, никаких <your-cluster>-плейсхолдеров без объяснений.

Куда смотреть в первую очередь

Когда pod в статусе Pending, CrashLoopBackOff, ImagePullBackOff или просто бесконечно ContainerCreating, в 99% случаев причина уже написана в событиях. События — это объекты типа Event, которые kubelet, scheduler и controller-manager сами создают, когда что-то идёт не по плану.

Самый честный способ их посмотреть:

kubectl describe pod <pod-name> -n <namespace>

В выводе пролистай вниз, в самом конце будет секция Events:. Это таблица с колонками Type, Reason, Age, From, Message. Именно последние две колонки тебе и нужны: они говорят, кто пожаловался и на что.

Альтернативный вариант — через get events

Если pod уже умер и пересоздаётся под новым именем, describe покажет события только текущей итерации. Чтобы увидеть всё подряд, отсортированно по времени:

kubectl get events -n <namespace> --sort-by=.lastTimestamp

Я обычно фильтрую по типу Warning, чтобы не утонуть в шуме:

kubectl get events -n <namespace> \
  --field-selector type=Warning \
  --sort-by=.lastTimestamp

Сноска: события по умолчанию хранятся около часа (--event-ttl=1h у kube-apiserver). Если pod упал ночью, а ты пришёл утром — событий уже не будет, придётся идти в логи.

Типичные сценарии и что они означают

ImagePullBackOff и ErrImagePull

Самая частая ошибка в новых проектах. Сообщение в Events выглядит так:

Failed to pull image "registry.example.com/api:v1.2.3": rpc error:
  code = Unknown desc = failed to pull and unpack image: failed to resolve reference
  "registry.example.com/api:v1.2.3": pulling from host registry.example.com failed
  with status code [manifests v1.2.3]: 404 Not Found

Возможные причины: тэга реально нет в реджистри, опечатка в имени, нет imagePullSecrets для приватного реджистри, нода в private subnet и не видит реджистри.

Проверка вручную с самой ноды:

kubectl get nodes -o wide
ssh user@<node-ip>
sudo crictl pull registry.example.com/api:v1.2.3

Если crictl тоже не тянет — значит проблема не в k8s, а в сети или авторизации.

OOMKilled

Pod в статусе CrashLoopBackOff, в describe в секции про последний exit видно Reason: OOMKilled и Exit Code: 137. Это значит, контейнер съел больше памяти, чем указано в resources.limits.memory, и kernel его убил.

Что делать: либо повысить лимит, либо чинить утечку в коде. Для Go-приложений я ставлю GOMEMLIMIT на 90% от лимита pod, чтобы GC начал работать раньше OOMKilled. Java и .NET аналогично — указывайте лимиты в JVM/CLR, не надейтесь на k8s.

resources:
  requests:
    memory: 256Mi
    cpu: 100m
  limits:
    memory: 512Mi
    cpu: 500m
env:
  - name: GOMEMLIMIT
    value: "460MiB"

CreateContainerConfigError

Pod создан, но не стартует. В Events что-то про secret "db-creds" not found или configmap "app-config" not found. Значит, манифест ссылается на ресурс, которого нет в нэймспейсе.

kubectl get secrets,configmaps -n <namespace>

В CI это часто случается, когда секрет деплоится отдельным джобом, а pod стартует раньше. Лечится зависимостями в пайплайне или ArgoCD sync waves.

FailedScheduling

Pod в Pending, в Events:

0/5 nodes are available: 3 Insufficient memory, 2 node(s) had untolerated taint

scheduler честно пишет, по какой ноде что не сошлось. Считай: если у тебя три ноды без памяти и две с taint — добавляй ноды или меняй tolerations. Часто помогает посмотреть, кто съедает ресурсы:

kubectl top nodes
kubectl describe node <node-name> | grep -A 5 "Allocated resources"

Liveness probe failed

Pod стартует, через минуту падает. В Events:

Liveness probe failed: HTTP probe failed with statuscode: 503

Тут два варианта: приложение реально не отвечает, или liveness слишком жёсткий. Проверь сам:

kubectl exec -it <pod> -n <ns> -- sh
# внутри контейнера:
curl -v http://localhost:8080/healthz

Если эндпоинт отвечает руками, но liveness падает — увеличь initialDelaySeconds, periodSeconds или используй startupProbe для медленных стартов. Подробнее про probes — это отдельная тема, но 80% проблем решается тем, что startupProbe вообще задаётся.

Когда событий нет, а pod всё равно сломан

Бывает, события закончились (TTL), либо pod умирает где-то совсем рано. Тогда идём в логи:

kubectl logs <pod> -n <ns>
kubectl logs <pod> -n <ns> --previous   # логи прошлой жизни контейнера
kubectl logs <pod> -n <ns> -c <init-container>  # для init-контейнеров

Для логов прошлой жизни флаг --previous работает только если контейнер не пересоздавался полностью. Если pod был удалён и создан заново — previous уже не поможет, нужны централизованные логи (Loki, ELK, что у вас стоит).

Логи самого kubelet

Если совсем плохо и pod не запускается без событий, нужно лезть на ноду:

ssh user@<node-ip>
sudo journalctl -u kubelet -f
sudo journalctl -u containerd -f

В journalctl -u kubelet видно, например, как kubelet пытается смонтировать volume и валится с permission denied или mount failed. Из describe pod такие вещи иногда не видно сразу.

Полезные алиасы и команды на каждый день

В моём ~/.zshrc лежат вот эти алиасы — экономят минуты в день:

alias k='kubectl'
alias kgp='kubectl get pods -o wide'
alias kgpa='kubectl get pods -A -o wide'
alias kge='kubectl get events --sort-by=.lastTimestamp'
alias kdp='kubectl describe pod'
alias klf='kubectl logs -f --tail=100'

kbroken() {
  kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
}

Функция kbroken показывает все pods, которые не Running и не Succeeded — то есть всех текущих больных. Удобно по утрам пробежать глазами по кластеру.

Маршрут диагностики, который я повторяю всегда

Когда мне в чат пишут «у нас pod не работает», я делаю по шагам:

  1. kubectl get pod <pod> -n <ns> — текущее состояние, restart count, age.
  2. kubectl describe pod <pod> -n <ns> — секция Events, статус контейнеров, exit code последнего раза.
  3. kubectl logs <pod> -n <ns> --previous — логи прошлой попытки, если есть.
  4. kubectl get events -n <ns> --sort-by=.lastTimestamp — события всего нэймспейса за час.
  5. kubectl top pod <pod> -n <ns> — по памяти/CPU, если жив.
  6. Если ничего — journalctl -u kubelet на ноде.

На пунктах 1–3 закрывается процентов 70 кейсов, на 4–6 — ещё 25. Остаётся 5% случаев, когда у тебя сломан CNI, сетевые политики бьют конкретный pod или CSI не умеет смонтировать том. Это уже отдельный квест.

Что запомнить

Не угадывай причину. Открывай describe pod и читай Events — k8s сам пишет, что не так, нужно только не пропустить мимо глаз. Если события закончились — переходи на логи, логи прошлой жизни и логи kubelet. И держи под рукой kbroken-функцию: один взгляд утром экономит часы вечером.

Куда копать дальше: официальный kubectl cheat sheet, страница про debug pods. Там же есть про kubectl debug — отдельный инструмент для случаев, когда в контейнере вообще нет shell. Про debug я как-нибудь напишу отдельно.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.