kai.kzLab

ImagePullBackOff и ErrImagePull: почему Kubernetes не может скачать образ и как это исправить

Команда KAI Lab · · 8 мин чтения

ImagePullBackOff значит, что kubelet не смог скачать образ контейнера и теперь повторяет попытки с растущей паузой, до 5 минут между ними; ErrImagePull — статус самой неудачной попытки, который видно до перехода в режим ожидания. Главные причины — опечатка в имени или теге образа, приватный реестр без imagePullSecrets и лимиты реестра, например Docker Hub.

Контейнер в таком поде ещё не создан: под назначен на ноду и ждёт образ. Поэтому логов приложения нет, а вся информация — в событиях пода. Ниже — как их читать, чем отличаются причины и что делать после исправления.

Что означают ErrImagePull и ImagePullBackOff

Когда kubelet запускает контейнер, он просит container runtime (обычно containerd) скачать образ. Если попытка не удалась, контейнер остаётся в состоянии Waiting с причиной ErrImagePull. Перед следующей попыткой kubelet выжидает паузу, и на это время причина меняется на ImagePullBackOff. Документация Kubernetes описывает это так: пауза растёт с каждой попыткой, пока не упрётся во встроенный предел в 300 секунд.

Пытаться kubelet будет бесконечно. Сам под при этом в фазе Pending: контейнеры ещё не созданы.

Рядом живут ещё две похожие причины:

  • ErrImageNeverPull — у контейнера imagePullPolicy: Never, а образа на ноде нет. kubelet даже не пытается его скачать и пишет Container image "…" is not present with pull policy of Never.
  • InvalidImageName — строка образа не разбирается как ссылка на образ: пробел внутри строки, заглавные буквы в имени репозитория, два двоеточия подряд.

Как выглядит ImagePullBackOff

$ kubectl get pods -n shop
NAME                       READY   STATUS             RESTARTS   AGE
checkout-5c8d7b9f4-m2kqz   0/1     ImagePullBackOff   0          3m40s

RESTARTS 0 — важная деталь: контейнер ни разу не запускался. Если счётчик растёт, это уже не проблема образа, а падение приложения, и вам нужна статья про CrashLoopBackOff.

Пошаговая диагностика

Шаг 1. Прочитать событие Failed

$ kubectl describe pod checkout-5c8d7b9f4-m2kqz -n shop
...
Events:
  Type     Reason     Age                    From               Message
  ----     ------     ----                   ----               -------
  Normal   Scheduled  3m41s                  default-scheduler  Successfully assigned shop/checkout-5c8d7b9f4-m2kqz to worker-1
  Normal   Pulling    2m10s (x4 over 3m40s)  kubelet            Pulling image "registry.example.kz/shop/checkout:4.3.0"
  Warning  Failed     2m9s (x4 over 3m39s)   kubelet            Failed to pull image "registry.example.kz/shop/checkout:4.3.0": rpc error: code = NotFound desc = failed to pull and unpack image "registry.example.kz/shop/checkout:4.3.0": failed to resolve reference "registry.example.kz/shop/checkout:4.3.0": registry.example.kz/shop/checkout:4.3.0: not found
  Warning  Failed     2m9s (x4 over 3m39s)   kubelet            Error: ErrImagePull
  Normal   BackOff    95s (x6 over 3m38s)    kubelet            Back-off pulling image "registry.example.kz/shop/checkout:4.3.0"
  Warning  Failed     95s (x6 over 3m38s)    kubelet            Error: ImagePullBackOff

Главное — в первом событии Failed: после Failed to pull image идёт ответ runtime и реестра. Точная формулировка зависит от версии containerd, но ключевые слова одни и те же:

  • not found — такого образа или тега в реестре нет;
  • pull access denied, 401 Unauthorized, 403 Forbidden — реестр не пустил без учётных данных или с неверными;
  • 429 Too Many Requests и toomanyrequests — упёрлись в лимит реестра;
  • no such host, i/o timeout — нода не может достучаться до реестра;
  • x509: certificate signed by unknown authority — нода не доверяет сертификату реестра;
  • no match for platform in manifest — в образе нет варианта для архитектуры ноды.

В свежих версиях kubelet текст последней ошибки сохраняется и в статусе контейнера, его удобно доставать для нескольких подов сразу:

$ kubectl get pod checkout-5c8d7b9f4-m2kqz -n shop \
    -o jsonpath='{.status.containerStatuses[0].state.waiting.message}'

Шаг 2. Проверить имя и тег

$ kubectl get pod checkout-5c8d7b9f4-m2kqz -n shop -o jsonpath='{.spec.containers[*].image}'
registry.example.kz/shop/checkout:4.3.0

Правила имени из документации Kubernetes: без адреса реестра подразумевается Docker Hub, без тега — latest. Строка busybox означает docker.io/library/busybox:latest. Отсюда типовые ошибки: тег не опубликован, потому что сборка в CI упала; тег с префиксом v в реестре и без него в манифесте; забытый адрес приватного реестра, из-за которого образ ищут на Docker Hub; тег удалила политика очистки реестра.

Проверить, есть ли тег в реестре, можно с рабочей машины:

$ docker manifest inspect registry.example.kz/shop/checkout:4.3.0

Шаг 3. Скачать образ с самой ноды

Документация советует попробовать скачать образ вручную. Надёжнее делать это с той ноды, где висит под: так проверяются её DNS, прокси, сертификаты и сетевой доступ. На нодах с containerd для этого есть crictl:

$ sudo crictl pull registry.example.kz/shop/checkout:4.3.0
$ sudo crictl pull --creds 'ci-puller:ТОКЕН' registry.example.kz/shop/checkout:4.3.0

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

Шаг 4. Приватный реестр и imagePullSecrets

Для приватного реестра kubelet нужны учётные данные. Рекомендуемый способ — Secret типа kubernetes.io/dockerconfigjson и ссылка на него в поле imagePullSecrets:

$ kubectl create secret docker-registry regcred -n shop \
    --docker-server=registry.example.kz \
    --docker-username=ci-puller \
    --docker-password="$REGISTRY_TOKEN"
spec:
  imagePullSecrets:
  - name: regcred
  containers:
  - name: checkout
    image: registry.example.kz/shop/checkout:4.3.0

Чтобы не прописывать секрет в каждом манифесте, его добавляют в ServiceAccount, и новые поды получают его автоматически:

$ kubectl patch serviceaccount default -n shop -p '{"imagePullSecrets": [{"name": "regcred"}]}'

Что проверить, если секрет есть, а образ всё равно не качается:

  • Secret лежит в том же namespace, что и под. Секрет из default поду в shop не виден.
  • Имя совпадает. При опечатке в событиях будет FailedToRetrieveImagePullSecret с текстом Unable to retrieve some image pull secrets (<regcred>); attempting to pull the image may not succeed.
  • Адрес реестра в секрете совпадает с адресом в имени образа. Для Docker Hub документация указывает https://index.docker.io/v1/.
  • Токен не истёк. У облачных реестров токены бывают короткоживущими; для них Kubernetes предлагает kubelet credential provider, который получает учётные данные сам.

Шаг 5. Лимиты реестра

Если в событии 429 Too Many Requests и слово toomanyrequests, образ есть и доступ есть, но реестр отказал из-за лимита. Для Docker Hub это частая история: ноды кластера ходят в интернет через один NAT и расходуют общий анонимный лимит. Какие лимиты действуют сейчас, как проверить остаток и чем их обойти, мы подробно разобрали в статье toomanyrequests и лимиты Docker Hub.

Причины ImagePullBackOff: таблица

ПричинаКак узнатьКак исправить
Опечатка в имени или тегеnot found в событии Failedисправить image, проверить, что CI опубликовал тег
Тег удалён политикой очисткиnot found для старого тегане удалять теги, которые на проде; деплоить по digest
Нет учётных данныхpull access denied или 401 UnauthorizedSecret kubernetes.io/dockerconfigjson и imagePullSecrets
Секрет не найденсобытие FailedToRetrieveImagePullSecretсоздать Secret в namespace пода, проверить имя
Лимит Docker Hub429 Too Many Requests, toomanyrequestsаутентификация, зеркало или свой реестр
Сеть или DNS с нодыno such host, i/o timeoutDNS ноды, прокси, правила firewall
Недоверенный сертификатx509: certificate signed by unknown authorityдобавить CA реестра на ноды, в containerd — параметр ca в hosts.toml
Нет образа под архитектуруno match for platform in manifestсобрать multi-arch образ или выбрать ноды нужной архитектуры
Pull запрещён политикойErrImageNeverPullубрать imagePullPolicy: Never или заранее загрузить образ

Что делать после исправления

kubelet повторит попытку сам, но пауза может доходить до пяти минут. Чтобы не ждать, удалите под: контроллер создаст новый, и тот начнёт скачивание сразу. Если ошибка была в самом имени образа, исправьте его в Deployment:

$ kubectl set image deployment/checkout checkout=registry.example.kz/shop/checkout:4.3.1 -n shop
$ kubectl rollout status deployment/checkout -n shop

Если сломанный тег приехал с релизом, быстрее всего вернуть прошлую версию командой kubectl rollout undo deployment/checkout -n shop, а потом разбираться. При стратегии RollingUpdate контроллер не убирает старых подов больше, чем разрешает maxUnavailable, пока новые не готовы, поэтому ошибка в образе обычно выглядит как зависшая выкатка, а не как упавший сервис.

Поля imagePullSecrets у работающего пода не поменять. Если секрет забыли прописать в манифесте, исправьте Deployment: изменение шаблона создаст новые поды. А если ссылка на секрет была, но самого секрета не существовало, достаточно его создать: следующая попытка скачивания его подхватит.

Как не встречать ImagePullBackOff на проде

  • Деплойте по неизменяемым тегам или по digest (image@sha256:…). Документация Kubernetes не советует :latest в проде: сложно понять, какая версия работает, и сложно откатиться.
  • Проверяйте в пайплайне, что тег опубликован, до того как менять манифест.
  • Настройте политику очистки реестра так, чтобы она не трогала теги, которые сейчас на проде.
  • Держите базовые образы в своём реестре или за зеркалом, чтобы лимиты и сбои внешнего реестра не останавливали выкатки.

Если под висит в Pending и до скачивания образа дело не доходит, проблема не в образе, а в планировании: разбор в статье под в статусе Pending. Вопрос «чем ErrImagePull отличается от ImagePullBackOff» встречается и на собеседованиях по Kubernetes.

Частые вопросы

Чем ImagePullBackOff отличается от ErrImagePull? ErrImagePull — результат конкретной неудачной попытки скачать образ. ImagePullBackOff — ожидание перед следующей попыткой. Статус пода переключается между ними, а причина в обоих случаях одна и видна в событии Failed.

Сколько раз Kubernetes будет пытаться скачать образ? Бесконечно. Пауза между попытками растёт, пока не достигнет 300 секунд, и дальше остаётся такой. Под не перейдёт в ошибку сам: его нужно исправить или удалить.

Почему на одной ноде под запустился, а на другой нет? Скорее всего, у контейнера imagePullPolicy: IfNotPresent, и на первой ноде образ уже был в локальном кэше. Второй ноде нужно скачать образ, и тут всплывает реальная проблема: нет доступа, нет тега или лимит реестра.

Нужен ли imagePullSecrets для публичных образов? Нет, публичные образы скачиваются без учётных данных. Но у Docker Hub анонимный лимит считается по IP-адресу, а для аутентифицированных пользователей он выше, поэтому секрет с учётной записью Docker Hub помогает и для публичных образов.

Что делать, если образ собран на Mac с Apple Silicon, а ноды на x86? Образ, собранный только под linux/arm64, не запустится на ноде linux/amd64, и pull закончится ошибкой про платформу. Соберите образ под обе архитектуры, например docker buildx build --platform linux/amd64,linux/arm64, или под архитектуру нод.

Потренироваться на практике

В симуляторе DevOps KAI Lab в месяце про Kubernetes разбирают зависшие выкатки и поды, которые не стартуют. Консоль в браузере отдаёт реальные выводы kubectl, и причину нужно найти самому. Начать можно с бесплатного теста навыков DevOps.

Источники

  • Kubernetes. Images — ImagePullBackOff и предел в 300 секунд, правила имён образов, imagePullPolicy, приватные реестры, kubelet credential provider.
  • Kubernetes. Pull an Image from a Private Registry — kubectl create secret docker-registry, событие FailedToRetrieveImagePullSecret.
  • Kubernetes. Configure Service Accounts for Pods — imagePullSecrets в ServiceAccount.
  • Kubernetes. Debug Pods — что проверить, если под завис в ожидании образа.
  • Kubernetes. Debugging Kubernetes nodes with crictl — crictl pull на ноде.
  • containerd. Registry Configuration — hosts.toml, зеркала и сертификаты реестра.
  • Docker. Docker Hub usage and limits — лимиты скачивания для анонимных и аутентифицированных пользователей.

Читать дальше