ImagePullBackOff в Kubernetes — это не «ошибка Kubernetes», а симптом: kubelet не смог скачать образ из реестра. Когда в событиях и логах появляется x509 certificate signed by unknown authority, почти всегда виноваты TLS и доверие к цепочке сертификатов: самоподписанный сертификат в приватном registry, корпоративный MITM-прокси, подмена сертификата на периметре или просто отсутствующий кастомный CA на узлах.
Ниже — практический разбор, как быстро подтвердить TLS-причину, где именно лежит доверие (kubelet vs runtime), и как корректно добавить CA для containerd и CRI-O. Чиним без отключения проверки сертификата: «временно insecure» обычно превращается в «навсегда insecure».
Как выглядит проблема: типовые симптомы и где их искать
Обычно вы видите:
- Pod в статусе
ImagePullBackOffилиErrImagePull; - в событиях (
kubectl describe pod) — TLS/x509 ошибки; - иногда — редиректы или
401/403, если реестр за прокси или ingress.
Быстрая проверка через Events
kubectl -n NAMESPACE describe pod POD_NAME
Ищите строки вроде:
Failed to pull image "registry.example.local/team/app:1.2.3": rpc error: code = Unknown desc = failed to pull and unpack image ...: failed to resolve reference ...: failed to do request: Head "https://registry.example.local/v2/...": x509: certificate signed by unknown authority
Ключевая мысль: тянет не Kubernetes, а runtime на ноде
Образы скачивает container runtime на узле (containerd или CRI-O). Kubernetes лишь просит kubelet «запусти контейнер из такого-то образа».
Поэтому «починить в namespace» обычно не получится, если проблема именно в доверии к TLS (CA). imagePullSecrets решает аутентификацию в registry, но не доверие к сертификату.
Диагностика: это точно TLS/CA, а не DNS/доступ/учётка?
Перед изменениями на нодах важно убедиться, что вы лечите правильное. Условно делим причины на четыре группы:
- Сеть и DNS: реестр не резолвится или не доступен с нод.
- Аутентификация:
401/403, неверный секрет. - TLS/CA: unknown authority, bad certificate, name mismatch.
- Прокси и политики периметра: MITM подменяет сертификаты, блоки на FW.
1) Проверяем доступность реестра именно с ноды
Зайдите на проблемную ноду (ту, на которую реально планируется Pod) и проверьте резолв и TCP-доступ:
getent hosts registry.example.local
nc -vz registry.example.local 443
Если реестр на нестандартном порту (например 5000) — проверяйте его:
nc -vz registry.example.local 5000
2) Проверяем сертификат и цепочку через openssl
Команда покажет, какой сертификат предъявляет endpoint, и что он отдает в цепочке:
openssl s_client -connect registry.example.local:443 -servername registry.example.local -showcerts < /dev/null
Смотрите:
subjectиissuer— кто выпустил;Verify return code— причина недоверия;- есть ли промежуточные сертификаты (часто реестр/ingress отдает неполную цепочку).
3) Частая ловушка: name mismatch (SAN)
Иногда ошибка не про CA, а про несоответствие имени:
x509: certificate is valid for ..., not registry.example.local
Добавление CA тут не поможет — надо перевыпускать сертификат реестра (добавлять SAN) или исправлять имя, по которому вы обращаетесь к registry.
Если вы параллельно разбираете похожие CA-истории в других сервисах (БД, клиентские сертификаты), пригодится статья про доверие CA и цепочки: TLS/CA для MySQL и PostgreSQL: цепочки, trust store и типовые ошибки.

Понимаем, какой именно CA нужен: self-signed, корпоративный CA или MITM
Сценарий A: приватный registry с self-signed
Self-signed — это когда реестр сам себе «CA». Тогда на ноды нужно положить именно корневой сертификат, которым подписан серверный. В простейшем (и не самом удачном) случае это может быть сам серверный сертификат, если он одновременно CA.
Сценарий B: корпоративный MITM подменяет сертификаты
Если есть outbound HTTPS inspection, узлы вместо сертификата реестра видят сертификат, выпущенный корпоративным корневым CA. В этом случае надо доверять корпоративному CA на нодах. Иначе pull по HTTPS будет ломаться не только к вашему registry, но и к внешним, если политика распространяется шире.
Сценарий C: custom CA + неполная цепочка на стороне registry
Реестр может использовать intermediate CA, но отдавать неполную цепочку. Тогда на части систем «случайно» работает (intermediate уже есть), а container runtime падает. Правильное лечение:
- на стороне registry/ingress настроить отдачу полного chain (server + intermediate);
- на нодах установить корневой CA.
Где настраивать доверие: системное хранилище vs настройки runtime
Есть два рабочих подхода, и они не взаимоисключающие:
- Системное доверие (OS trust store): добавляем CA в системное хранилище и обновляем его. Это часто достаточно для CRI-O и для части утилит на узле.
- Доверие на уровне runtime: у containerd и CRI-O есть механизмы точечной настройки TLS на уровне registry, когда нужно доверять конкретному хосту отдельным CA-файлом.
Практически удобная схема для прода: системный trust store как база плюс точечная настройка runtime там, где это требует ваш Kubernetes-дистрибутив или политика безопасности.
Решение для containerd: custom CA для registry
Containerd умеет отдельные конфиги на конкретный хост реестра. Это удобно: вы не расширяете системное доверие «на всё», а добавляете доверие только для нужного registry.
Шаг 1: положить CA в certs.d
Стандартный путь (часто используется в Kubernetes-сборках):
sudo mkdir -p /etc/containerd/certs.d/registry.example.local
Скопируйте корневой сертификат корпоративного CA или CA вашего registry в файл ca.crt:
sudo cp /path/to/your-ca.crt /etc/containerd/certs.d/registry.example.local/ca.crt
Имя директории должно совпадать с тем, как вы обращаетесь к registry в образах. Если используете порт — учитывайте его:
sudo mkdir -p /etc/containerd/certs.d/registry.example.local:5000
Шаг 2: прописать hosts.toml (если нужно явно указать CA)
Во многих сборках достаточно ca.crt. Если pull всё равно падает — добавьте hosts.toml:
sudo tee /etc/containerd/certs.d/registry.example.local/hosts.toml > /dev/null <<'EOF'
server = "https://registry.example.local"
[host."https://registry.example.local"]
capabilities = ["pull", "resolve"]
ca = "/etc/containerd/certs.d/registry.example.local/ca.crt"
EOF
Если реестр нужен и для push — добавьте "push" в capabilities.
Шаг 3: перезапустить containerd и проверить
sudo systemctl restart containerd
Проверять удобнее без Kubernetes, через crictl (на большинстве узлов он есть):
sudo crictl pull registry.example.local/team/app:1.2.3
Если pull прошёл — kubelet тоже сможет.
Типовые ошибки containerd, которые помогают понять, что не так
x509: certificate signed by unknown authority— CA не установлен или указан не тот.x509: cannot validate certificate for ... because it doesn't contain any IP SANs— вы обращаетесь по IP, а сертификат выпущен на DNS-имя.certificate is valid for ...— mismatch имени, нужен правильный SAN.
Решение для CRI-O: certs.d и registries.conf
В CRI-O (и в экосистеме containers/image) логика немного другая: обычно управляют настройками через registries.conf и хранилище сертификатов.
Шаг 1: положить CA для конкретного registry
Часто используется путь:
sudo mkdir -p /etc/containers/certs.d/registry.example.local
Далее положить CA как ca.crt:
sudo cp /path/to/your-ca.crt /etc/containers/certs.d/registry.example.local/ca.crt
Если используется порт:
sudo mkdir -p /etc/containers/certs.d/registry.example.local:5000
Шаг 2: убедиться, что registry не помечен как insecure
Проверьте конфиги (в зависимости от дистрибутива пути могут отличаться):
sudo grep -R "registry.example.local" -n /etc/containers/registries.conf /etc/containers/registries.conf.d 2>/dev/null
Если кто-то раньше «починил» через insecure/skip verify — лучше вернуть проверку и правильно поставить CA. Отключение TLS-проверки повышает риск подмены образов на пути доставки.
Шаг 3: рестарт CRI-O и тест pull
sudo systemctl restart crio
sudo crictl pull registry.example.local/team/app:1.2.3
Если у вас managed Kubernetes или ноды «не ваши»: как жить
Самая болезненная ситуация: вы не можете менять trust store на нодах. Тогда варианты зависят от платформы и внутренних процессов:
- использовать реестр с сертификатом от публично доверенного CA;
- разместить registry за ingress/балансировщиком, который отдаёт публичный TLS;
- при корпоративном MITM — согласовать исключение для registry (bypass TLS inspection) или организовать доставку корпоративного CA на ноды поддерживаемым способом провайдера.
Идея простая: Kubernetes не «протащит» вам CA внутрь kubelet. Доверие должно появиться на уровне узла или на уровне точки входа в registry.
Частые вопросы и грабли
Почему на ноутбуке/в CI Docker работает, а в Kubernetes — нет?
Потому что ваш laptop/CI может доверять корпоративному CA (MDM/AD/GPO), а ноды Kubernetes — «голые» и не знают этот корень. Или наоборот: Docker Engine настроен на конкретный CA, а containerd/CRI-O в кластере — нет.
Можно ли решить через imagePullSecrets?
Нет. Это про логин/пароль/токен. Ошибка x509 certificate signed by unknown authority — про TLS-доверие, и секреты Kubernetes тут не помогут.
Можно ли «быстро починить», отключив проверку TLS?
Технически — да, но это ломает базовую гарантию, что вы тянете образ именно из доверенного источника. Для продакшена лучше потратить время и настроить CA правильно.
Что делать, если реестр отдаёт неполную цепочку?
Исправить TLS-конфигурацию на стороне registry/ingress так, чтобы отдавался полный chain (серверный + промежуточные). На ноды при этом добавляют корневой CA.

Мини-чеклист: как довести до стабильного состояния
- Подтвердить ошибку TLS через
kubectl describeи сообщение об ошибке pull. - С ноды проверить сертификат реестра через
openssl s_client(SNI обязателен). - Определить, чей CA нужен: self-signed registry, corporate CA или MITM.
- Установить CA на нодах: системно и/или в конфиге runtime (containerd/CRI-O).
- Проверить pull через
crictl pull. - Пересоздать Pod или дождаться ретраев — убедиться, что
ImagePullBackOffушёл.
Практическая рекомендация для инфраструктуры
Если вы регулярно работаете с приватными registry, проще стандартизировать: либо публично доверенный сертификат на registry, либо единый корпоративный CA, который гарантированно установлен на всех узлах (golden image/automation). Тогда инциденты вида «kubernetes imagepullbackoff tls» превращаются в редкость.
И отдельно: при расследовании всегда уточняйте, какой runtime реально на ваших нодах и где именно у него лежат конфиги. В смешанных кластерах (разные node pools, разные образы ОС) настройки отличаются, и проблема будет «плавающей».
Если ваш registry доступен по доменному имени, не забывайте про базовую гигиену: домен, корректный SAN и предсказуемая цепочка сертификатов. Удобно держать домен под контролем через регистрацию доменов, а для публичных endpoint’ов использовать нормальные SSL-сертификаты.


