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 3m40sRESTARTS 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 Unauthorized | Secret kubernetes.io/dockerconfigjson и imagePullSecrets |
| Секрет не найден | событие FailedToRetrieveImagePullSecret | создать Secret в namespace пода, проверить имя |
| Лимит Docker Hub | 429 Too Many Requests, toomanyrequests | аутентификация, зеркало или свой реестр |
| Сеть или DNS с ноды | no such host, i/o timeout | DNS ноды, прокси, правила 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 — лимиты скачивания для анонимных и аутентифицированных пользователей.