← Kubernetes: разбор поломок
6 мин · просто · Урок 1 из 12

ImagePullBackOff

Почему Pod не может скачать образ, где в describe лежит настоящий текст ошибки и что с этим делают.

Самая частая поломка в Kubernetes выглядит скучно: Pod создан, но контейнер не стартует, а в статусе висит ImagePullBackOff.

NAME   READY  STATUS             RESTARTS  AGE
web    0/1    ImagePullBackOff   0         90s

Kubernetes тут ни в чём не виноват. Он честно попросил образ и не получил его. Разбор ниже: что происходит под капотом, где искать текст ошибки и почему в девяти случаях из десяти виновата опечатка.

Как kubelet тянет образ

kubelet образ есть на ноде? pull runtime containerd, CRI-O manifest и слои registry вне кластера старт контейнер запущен
Ломается звено в середине, а видно это как статус у Pod.

kubelet - агент Kubernetes на каждой ноде, который запускает контейнеры. Получив spec Pod с конкретным image, он делает несколько вещей по порядку:

  1. Проверяет, есть ли уже образ локально на ноде.
  2. Если нет, дёргает container runtime (containerd, CRI-O) с запросом на pull.
  3. Runtime идёт в registry: парсит имя, резолвит host, шлёт HTTP запросы на manifest и слои.
  4. Если получил всё, монтирует и запускает контейнер.

На любом шаге может сломаться. Каждая ошибка даёт свой статус.

Семейство Image-ошибок

СтатусЧто значитТипичная причина
ErrImagePullпервая попытка pull провалиласьопечатка, нет авторизации, нет интернета, образ снят с registry
ImagePullBackOffkubelet делает паузы между повторными попыткамито же самое, что выше, просто прошло несколько секунд
InvalidImageNameимя образа не парситсяпробелы, спецсимволы, тег без двоеточия
RegistryUnavailableregistry не отвечаетсетевая проблема, DNS, registry упал

Между ErrImagePull и ImagePullBackOff концептуальной разницы нет. Это одна и та же ошибка, просто kubelet перешёл в режим экспоненциальной задержки: 10s, 20s, 40s, 80s, до 5 минут между попытками. Поэтому Pod, простоявший в ImagePullBackOff десять минут, не «завис» - он просто ждёт следующей попытки.

Что показывает диагностика

kubectl describe pod web

В выводе важны три места.

Containers → Image: то, что реально просили затянуть.

Containers:
  app:
    Image:    nginxinc/nginx-unpriviledged:1.25
    State:    Waiting
      Reason: ImagePullBackOff

Events: что говорит runtime. Тут прямой текст ошибки.

Events:
  Type     Reason   Message
  Normal   Pulling  Pulling image "nginxinc/nginx-unpriviledged:1.25"
  Warning  Failed   Failed to pull image "...": rpc error: not found
  Warning  Failed   Error: ErrImagePull
  Normal   BackOff  Back-off pulling image "..."
  Warning  Failed   Error: ImagePullBackOff

Дальше текст ошибки читается почти как справочник:

Для приватного registry нужен imagePullSecret, привязанный к Pod или к ServiceAccount. Без него ошибка та же самая, отличается только текст: unauthorized.

Если нужен только хвост событий без полного describe, помогает выборка по объекту:

kubectl get events --field-selector involvedObject.name=web --sort-by='.lastTimestamp'

Типичные опечатки

Проверить гипотезу быстро: попробовать тот же образ вручную через docker pull или crane manifest. Если runtime отвечает not found, имя действительно неверное, и дело не в кластере.

imagePullPolicy и почему он влияет на дебаг

У контейнера есть поле imagePullPolicy. По умолчанию:

Always означает, что kubelet будет тянуть образ при каждом старте контейнера. Если registry падает, Pod ломается, даже когда образ на ноде уже лежит.

IfNotPresent тянет, только если на ноде нет такого тега. Это безопаснее, но при первом запуске всё равно идёт в registry.

Never означает «считай, что образ уже есть локально». Если его нет, статус будет ErrImageNeverPull. Такой режим используют в локальных кластерах (kind, minikube), куда образ загружают отдельной командой.

Как чинят

Поле spec.containers[*].image у Pod изменяемое, так что имя можно поправить на месте:

kubectl set image pod/web app=nginxinc/nginx-unprivileged:1.25

Или руками через редактор:

kubectl edit pod web

После изменения kubelet сразу дёргает pull заново, и через 5-10 секунд Pod становится Running.

У Deployment правят шаблон, а не Pod: kubectl set image deploy/... создаёт новый ReplicaSet, и Pod пересоздаются с исправленным образом.

Когда Pod уже давно висит в ImagePullBackOff с длинным таймером backoff, часто быстрее снести его и применить исправленный манифест:

kubectl delete pod web --grace-period=0 --force
kubectl apply -f web.yaml   # тот же манифест, но с правильным image

Так старт гарантированно идёт с чистого состояния, без зависших таймеров от прошлых попыток.

Как не допустить

Имя образа удобно проверять до применения манифеста: docker pull или crane manifest дают ответ за секунду. В CI тот же эффект даёт шаг, который пуллит образ перед деплоем.

Плавающие теги вроде :latest вместе с imagePullPolicy: Always делают деплой зависимым от доступности registry в момент каждого рестарта. Конкретный тег и IfNotPresent убирают эту зависимость.

Домашка

Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.

./run.sh start debug-image     # развернуть стартовое состояние
./run.sh verify debug-image    # проверить текущий шаг

Коротко

Прошли материал до конца? Отметьте урок пройденным.