Когда в Kubernetes внезапно появляются ImagePullBackOff и ErrImagePull, первая мысль — «упал registry» или «сломались креды». Но в реальных кластерах (dev/test, CI, автоскейлинг, много нод) часто причина проще: вы упёрлись в Docker Hub pull rate limit. Итог одинаковый: Pod не стартует, у Deployment растёт число недоступных реплик, а вы теряете время на не те проверки.
Ниже — практичный разбор: как быстро подтвердить именно лимит, чем отличается registry mirror от registry cache, и как настроить зеркала для containerd, либо поднять proxy-cache (Harbor/Quay/Distribution) и сделать его «щитом» для кластера.
Как выглядит ImagePullBackOff при pull rate limit Docker Hub
Снаружи это выглядит как обычный неудачный pull образа: Pod застревает на старте, kubelet ретраит загрузку, а статус меняется на ImagePullBackOff.
Быстрая проверка через kubectl describe
Начните с событий Pod:
kubectl -n default describe pod my-app-6d7b9c7d5f-abcde
Внизу, в блоке Events, при лимите Docker Hub часто встречаются toomanyrequests или прямое упоминание rate limit. Примеры типовых сообщений (формулировки зависят от runtime и версии):
Failed to pull image "library/nginx:1.25": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/nginx:1.25": failed to resolve reference "docker.io/library/nginx:1.25": too many requests to Docker Hub
Back-off pulling image "docker.io/library/nginx:1.25"
toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating ...
Если в событиях видно
toomanyrequests/ rate limit — это не «битый DNS» и не «не тот тег». Это лимит, и лечится он организацией кэша/зеркала или аутентификацией.
Проверка с ноды: что видит containerd
Если события в Kubernetes «обрезаны» или выглядят неочевидно, подтвердите диагноз с ноды. Для containerd используйте crictl (общение через CRI):
crictl info
crictl pull docker.io/library/nginx:1.25
При rate limit вы увидите похожий toomanyrequests уже на уровне runtime. Это удобная проверка без догадок, особенно если проблема проявляется не на всех нодах.
Почему лимит внезапно «ломает» кластер
Pull rate limit проявляется волнами, поэтому создаёт ощущение «вчера работало, сегодня нет». Типичные причины:
Автоскейлинг: новые ноды приходят пустые и тянут все образы заново.
Частые деплои: CI выкатывает новые теги, и ноды не успевают «прогреть» локальный кэш.
Популярные базовые образы во многих подах (alpine/ubuntu/nginx): суммарно это десятки и сотни pull.
Один внешний IP (NAT): много нод «выглядят» для Docker Hub как один клиент, лимит общий.
Пересоздание нод (immutable-инфраструктура, автообновления): кэш на диске пропадает.
Если у вас registry cache будет жить рядом с кластером, удобно разместить его на отдельной машине/ВМ с быстрым диском и предсказуемой сетью — под такую роль обычно выбирают VDS с нормальным NVMe и возможностью расширять хранилище.

Что делать: три рабочие стратегии
На практике чаще всего комбинируют два подхода: быстро уменьшают число pull и параллельно внедряют кэш/зеркало.
Снизить количество pull (политики, предзагрузка, стабильные теги, контроль пересозданий).
Настроить зеркала (registry mirror) на уровне runtime: часть запросов уйдёт в альтернативный источник.
Поднять proxy cache registry (registry cache): один раз скачали с Docker Hub — дальше раздаём из локальной инфраструктуры.
Стратегия 1: уменьшить число pull
Не используйте
:latestв продакшне: он провоцирует лишние обновления и непредсказуемые pull.Проверьте
imagePullPolicy. Для закреплённых версий чаще нуженIfNotPresent, чтобы нода не тянула образ без необходимости.Если нод много — рассмотрите pre-pull через DaemonSet (например, перед релизом). Это ускоряет старт и сглаживает пики, но не решает проблему системно при частом пересоздании нод.
Важно: даже при IfNotPresent новые ноды всё равно будут тянуть образы. Поэтому для кластеров с автоскейлом почти всегда нужен второй слой защиты: mirror или cache.
Стратегия 2: registry mirror vs registry cache
Термины путают, из-за чего делают «зеркало», ожидая поведения «кэша».
Registry mirror — альтернативная точка входа для скачивания образов. Может быть внешним зеркалом или корпоративным registry с синхронизацией. Плюс: проще маршрутизировать pull. Минус: зеркала не всегда гарантируют, что у вас будет закэширован именно нужный набор репозиториев/тегов.
Registry cache (proxy cache) — прокси, который при первом запросе тянет образ из upstream (Docker Hub) и сохраняет локально, а дальше отдаёт локально. Плюс: радикально снижает количество pull к upstream. Минус: нужны диски, мониторинг и базовая эксплуатация.
Если задача звучит как «пережить Docker Hub rate limit», чаще всего нужен именно registry cache.
Настройка containerd registry mirrors в Kubernetes
В современных кластерах часто используется containerd. Зеркала настраиваются на уровне containerd: вы описываете endpoints для хоста docker.io. Точные пути зависят от дистрибутива (kubeadm/RKE2/K3s), но логика одинаковая.
Вариант A: правка config.toml (универсально)
Посмотрите версию и текущую конфигурацию:
containerd --version
containerd config dump
Дальше добавьте зеркало для docker.io. Пример фрагмента TOML:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://mirror.registry.local", "https://registry-1.docker.io"]
Смысл: сначала containerd попробует mirror.registry.local, и только если не получится — пойдёт в Docker Hub. В роли зеркала может быть ваш proxy cache (Harbor/Quay/Distribution) или корпоративный registry.
Перезапустите containerd:
systemctl restart containerd
Проверьте pull с ноды:
crictl pull docker.io/library/nginx:1.25
Вариант B: hosts.toml (per-registry конфиги)
Часто удобнее вынести настройки конкретного registry в отдельный файл hosts.toml (каталог certs.d), чтобы не трогать большой config.toml.
Общий план:
Создать каталог для хоста
docker.io(иногда нуженregistry-1.docker.io— зависит от дистрибутива).Добавить
hosts.tomlс описанием upstream и зеркал.
Пример (пути уточняйте под вашу систему):
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml << 'EOF'
server = "https://registry-1.docker.io"
[host."https://mirror.registry.local"]
capabilities = ["pull", "resolve"]
[host."https://registry-1.docker.io"]
capabilities = ["pull", "resolve"]
EOF
Дальше — рестарт containerd и повторный crictl pull.
Registry cache: Harbor/Quay/Distribution как «щит» от rate limit
Самый практичный способ системно снизить зависимость от Docker Hub — поднять proxy cache registry рядом с кластером (в той же сети/ЦОД) и направить pull через него.
Harbor proxy cache: прокси к Docker Hub и другим registry, хранит кэш, даёт UI, проекты, роботов, политики, опционально сканирование.
Quay: корпоративный реестр с возможностями зеркалирования/проксирования (в терминах часто говорят «quay mirror»).
Distribution (Docker Registry) в режиме pull-through cache: минималистично, но требовательно к аккуратной настройке и пониманию ограничений.
Как правильно подключить cache к Kubernetes
Поднимите registry cache на стабильном адресе (DNS/балансер) и обеспечьте дисковое хранилище.
Настройте в containerd зеркала для
docker.io, чтобы сначала ходить в cache.При необходимости добавьте доверие к вашему TLS (корпоративный CA) на всех нодах, чтобы containerd мог ходить по HTTPS без ошибок.
Критичный момент — поведение при аварии. Обычно выбирают либо fallback в Docker Hub, либо строгий режим «только через cache», если вы хотите полностью контролировать цепочку поставки и исключить прямые pull наружу.
Если поднимаете cache с собственным TLS, заранее продумайте выпуск и продление сертификата: для продакшна проще и надёжнее использовать публично доверенный сертификат (или централизованно раздать корпоративный CA). В типичных инфраструктурах это как раз тот случай, когда удобно иметь управляемые SSL-сертификаты без ручной возни на каждом узле.

Частые ошибки, из-за которых mirror/cache «не работает»
Ошибка 1: зеркало настроили, но образы всё равно тянутся из Docker Hub
Проверьте:
Какой хост реально используется при pull:
docker.ioиregistry-1.docker.ioмогут по-разному матчиться на конфиг.Порядок endpoints: cache должен стоять первым.
Перезапуск containerd выполнен на всех нодах.
Сетевую доступность cache с нод (DNS, маршрутизация, firewall).
Ошибка 2: вместо ImagePullBackOff появились TLS/сертификаты
Если вы подняли свой registry с нестандартным сертификатом, containerd может ругаться на доверие. Решение: публично доверенный сертификат или установка корпоративного CA в доверенные на каждой ноде, плюс корректные TLS-параметры в настройках containerd для нужного хоста.
Ошибка 3: cache есть, но «не кэширует»
Убедитесь, что включён именно режим proxy cache/pull-through (а не просто «обычный registry»). Для Harbor это отдельная настройка proxy cache проекта/реестра; для Distribution — отдельный режим конфигурации.
Диагностика: чек-лист на 5 минут
События Pod:
kubectl describe pod, ищемtoomanyrequestsи rate limit.Pull с ноды:
crictl pull docker.io/....Определяем runtime (containerd/CRI-O) и где лежат его конфиги.
Временно уменьшаем число pull и параллельно внедряем cache.
Добавляем mirror/cache в настройки registry для containerd, перезапускаем runtime на всех нодах.
Повторяем
crictl pullи перезапускаем проблемный Deployment.
Практика эксплуатации registry cache
Чтобы proxy cache действительно решал проблему и не стал новой точкой отказа:
Диск и IOPS важнее CPU: образы — это слои и метаданные, нагрузка часто упирается в хранилище.
Мониторинг: свободное место, задержки диска, ошибки 5xx, количество запросов, размер кэша.
Политики очистки: кэш не должен расти бесконечно; включите retention/GC и лимиты.
План на аварии: заранее решите, допускаете ли fallback в Docker Hub или блокируете pull без cache.
Если хотите глубже разобраться, как именно хранить слои и эффективно отдавать их через Nginx (и не убить диск), пригодятся материалы про кэширование и форматы изображений, например как настроить Nginx map cache для WebP/AVIF.
Итог
ImagePullBackOff из-за Docker Hub pull rate limit — не редкий «сбой», а закономерность для Kubernetes, который масштабируется и постоянно пересоздаёт ноды/поды. Самый надёжный способ снизить зависимость — поставить registry cache (Harbor/Quay/Distribution) и подключить его через containerd registry mirrors, а затем дополнительно уменьшить количество лишних pull настройками деплоя.


