Learning Platform
Глоссарий Troubleshooting
Урок 05.05 · 18 мин
Средний
imagePullPolicyimagePullSecretsprivate registryImagePullBackOffServiceAccount

imagePullPolicy и imagePullSecrets

Когда Pod планируется на node, kubelet должен где-то взять образ: либо использовать уже скачанный, либо тянуть из registry. Решение принимает поле imagePullPolicy. Если registry приватный — нужны credentials, которые передаются через imagePullSecrets. Эти две темы дают второй по частоте класс ошибок в production: ImagePullBackOff и ErrImagePull. Разбираемся в политиках и в том, как привязывать registry-credentials к Pod через сам Pod или через ServiceAccount.


Теги и digests: mutable label vs immutable id

Три значения imagePullPolicy

spec.containers[].imagePullPolicy определяет, когда kubelet тянет образ из registry:

imagePullPolicy: когда kubelet вызывает registry
AlwaysПри каждом старте контейнера kubelet делает HEAD-запрос к registry, проверяет, что digest актуальный, и если изменился — pull. Гарантирует свежесть, но замедляет старт и нагружает registry.
IfNotPresentPull только если образа нет в локальном кэше containerd на node. Если есть — использовать локальную копию без проверки digest. Быстрый старт, но mutable tags могут застрять.
NeverНикогда не pull. Контейнер запустится только если образ уже на node (например, предзагружен через node image cache). Иначе ErrImageNeverPull.
PolicyПоведениеUse case
AlwaysPull каждый разLatest или mutable теги, dev окружения, security-critical
IfNotPresentPull если нет локальноProduction с immutable теги или digest
NeverНе pull никогдаAir-gapped кластеры, preloaded images

Default rule: что подразумевается, когда policy не указана

Это любимый CKAD trick-question. Если imagePullPolicy явно не задан:

  • Если тег образа = latest → default становится Always.
  • Если тега нет вообще (image: nginx, что эквивалентно nginx:latest) → Always.
  • Если тег указан и не latest (nginx:1.27) → default становится IfNotPresent.
  • Если образ указан через digest (nginx@sha256:abc..., с тегом или без) → default IfNotPresent. Digest immutable, повторный pull бессмыслен.
# imagePullPolicy не указан — будет Always (тег latest)
- image: myapp
- image: myapp:latest

# imagePullPolicy не указан — будет IfNotPresent
- image: myapp:1.0.3

# imagePullPolicy не указан — будет IfNotPresent (digest immutable)
- image: myapp@sha256:abc123...

# imagePullPolicy: Always явно — переопределяет default
- image: myapp:1.0.3
  imagePullPolicy: Always
WARNING

Это значит, что использование :latest даёт двойной эффект: (1) tag mutable; (2) kubelet тянет на каждый restart. На больших кластерах с rolling updates это создаёт реальную нагрузку на registry и замедляет старты.


Когда IfNotPresent ломается

IfNotPresent быстр и эффективен, но имеет неочевидную ловушку. Допустим, вы делаете CI/CD с тегом myapp:v2-beta и переписываете этот тег при каждом push новой версии (mutable tag pattern):

  1. Pod на node-A pull-ил myapp:v2-beta → получил digest abc.
  2. Вы пушите новый myapp:v2-beta → digest стал def.
  3. Pod на node-B pull-ит → получает def (новую версию).
  4. На node-A IfNotPresent — kubelet видит, что образ есть, и НЕ pull-ит. Pod на node-A работает по старому digest abc.

Получается split-brain: одна и та же image: myapp:v2-beta крутит две разные версии кода на разных nodes. Решение — либо imagePullPolicy: Always для mutable тегов, либо использовать immutable теги / digest pinning.


Private registries: imagePullSecrets

Публичные registries (Docker Hub anonymously, registry.k8s.io) не требуют auth. Приватные (внутрикорпоративные Harbor, ECR, GHCR с private repos) — требуют. kubelet должен предъявить credentials при pull. Это делается через imagePullSecrets.

imagePullSecret — это Secret типа kubernetes.io/dockerconfigjson. Внутри — base64-кодированный JSON в формате ~/.docker/config.json:

{
  "auths": {
    "registry.example.com": {
      "username": "ci-bot",
      "password": "ghp_xxx",
      "email": "[email protected]",
      "auth": "Y2ktYm90Omdocl94eHg="
    }
  }
}

Создание через kubectl — проще, чем вручную:

kubectl create secret docker-registry my-registry-creds \
  --docker-server=registry.example.com \
  --docker-username=ci-bot \
  --docker-password='ghp_xxx' \
  [email protected] \
  -n myapp

Использование в Pod:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  imagePullSecrets:
  - name: my-registry-creds
  containers:
  - name: app
    image: registry.example.com/team/app:1.0

kubelet при pull увидит imagePullSecrets, прочитает Secret, выберет нужный entry в auths по hostname registry и пошлёт credentials.

TIP

imagePullSecrets — список (можно указать несколько). kubelet попробует каждый, пока pull не удастся. Полезно, когда образ может тянуться из разных registries.


Killer момент: imagePullSecrets в ServiceAccount

Указывать imagePullSecrets в каждом Pod — утомительно. Альтернативный (и часто более правильный) подход — привязать секрет к ServiceAccount. Каждый Pod в кластере привязан к ServiceAccount (default, если не указан явно). Если у ServiceAccount задан imagePullSecrets, kubelet автоматически применит его ко всем Pod-ам с этим SA.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: myapp
imagePullSecrets:
- name: my-registry-creds

Теперь любой Pod в namespace myapp с spec.serviceAccountName: default (или без указания SA — он и так default) получит этот pull-secret. Удобно для multi-team setups: команда раз настраивает SA, дальше все деплои работают.

# Pod может не указывать ServiceAccount — будет использован default
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  namespace: myapp
spec:
  containers:
  - name: app
    image: registry.example.com/team/app:1.0
  # imagePullSecrets не указан, но возьмётся из ServiceAccount default

Это самый production-friendly способ организовать pull-creds: единое место, RBAC-контролируемое, не дублируется в Pod-spec.


ImagePullBackOff: troubleshooting

ImagePullBackOff — kubelet не смог скачать образ и ждёт по backoff перед следующей попыткой. Backoff растёт так же, как для CrashLoopBackOff (10s → 20s → … → 300s cap). Перед ним обычно мелькает ErrImagePull (свежая ошибка).

Алгоритм диагностики:

# 1. Что говорит kubelet — Events
kubectl describe pod <name>
# Events:
#   Failed to pull image "registry.example.com/team/app:1.0": rpc error: code = Unknown
#   desc = failed to pull and unpack image: failed to resolve reference: pulling from
#   host registry.example.com failed with status code [manifests 1.0]: 401 Unauthorized

Причины и их симптомы:

ImagePullBackOff: основные причины
Typo в имениImage не существует в registry. Сообщение: manifest unknown или not found. Чек: попробуйте docker pull / crane pull снаружи кластера.
Auth required401 Unauthorized. Нет imagePullSecret или он невалиден. Чек: проверьте Secret, попробуйте логин снаружи с теми же creds.
Forbidden403 Forbidden. Пользователь авторизован, но не имеет прав на этот repo. Проверьте, что аккаунт ci-bot имеет pull-доступ.
Networkdial tcp: i/o timeout, connection refused. Узел не может достучаться до registry: DNS, firewall, NetworkPolicy (для kube-system?), egress proxy.
Expired credsGHCR token expired, AWS ECR token истекает каждые 12 часов и требует refresh. Решение: ecr-credential-helper, IRSA для EKS, либо ротация Secret.
Wrong archno matching manifest for linux/arm64. Образ собран только для amd64, а узел — Graviton/Apple M-series. Билдите multi-arch.
# 2. Проверим, что Secret вообще существует
kubectl get secret my-registry-creds -n myapp

# 3. И что он типа dockerconfigjson
kubectl get secret my-registry-creds -n myapp -o jsonpath='{.type}'
# kubernetes.io/dockerconfigjson

# 4. Содержимое (base64-decoded для дебага)
kubectl get secret my-registry-creds -n myapp -o jsonpath='{.data.\.dockerconfigjson}' \
  | base64 -d | jq

# 5. Привязан ли к ServiceAccount?
kubectl get sa default -n myapp -o yaml | yq '.imagePullSecrets'
WARNING

Self-hosted registries (Harbor, in-cluster registry) часто используют self-signed TLS. kubelet по умолчанию отвергнет такой сертификат и выдаст x509: certificate signed by unknown authority. Решение: добавить CA в trust store узла (/etc/containerd/certs.d/) или, для dev, разрешить insecure_skip_verify в конфиге containerd. Это уровень администратора кластера, не Pod-spec.


Обзор: где конкретно живёт image cache

Для понимания: после pull образ хранится в content store containerd (по умолчанию /var/lib/containerd). На каждой node — свой кэш. Когда вы пересоздаёте Pod на той же node — IfNotPresent использует этот кэш. Когда Pod планируется на новый node — pull повторяется. Поэтому в больших кластерах с высоким churn-ом полезно node image preloading или registry mirror внутри кластера.


Проверка знанийKnowledge check
Pod manifest: image: myapp:dev. imagePullPolicy не указан. Что выбрал kubelet и почему?
ОтветAnswer
IfNotPresent. Default определяется по тегу: latest или без тега → Always; любой другой тег (включая dev, v1, 1.0) → IfNotPresent. dev — обычный тег, не latest, поэтому IfNotPresent. Это может стать проблемой если CI переписывает dev тег: kubelet будет использовать старую закэшированную версию.
Проверка знанийKnowledge check
ImagePullBackOff. Events показывают: 'manifest for registry.example.com/team/app:1.0 not found: manifest unknown'. Что нужно проверить?
ОтветAnswer
Имя образа и тег. 'manifest unknown' означает, что registry дошёл до запроса, но такого образа или тега в нём нет. Это либо typo в spec, либо CI не запушил образ, либо у пользователя нет доступа к repo (некоторые registries возвращают 'not found' вместо 403 чтобы не раскрывать существование private repo). Не auth — auth дал бы 401.
Проверка знанийKnowledge check
Команда хочет, чтобы все Pod в namespace 'platform' автоматически могли pull из private GHCR без указания imagePullSecrets в каждом манифесте. Как это сделать?
ОтветAnswer
Создать Secret типа docker-registry с GHCR creds в namespace platform, потом отредактировать ServiceAccount 'default' в этом namespace и добавить imagePullSecrets с именем созданного Secret. kubelet автоматически применит этот pull-secret ко всем Pod, использующим default SA — то есть ко всем, где SA не указан явно.
Проверка знанийKnowledge check
Pod использует image: nginx:1.27 (без digest), imagePullPolicy: IfNotPresent. На node-A Pod работает 30 дней. Maintainers nginx запушили новую версию под тем же тегом. Что произойдёт?
ОтветAnswer
Ничего. IfNotPresent проверяет только наличие образа в локальном кэше containerd, не digest. Pod продолжает работать со старым образом неограниченно, даже если в registry есть новый. Если node-A рестартанётся и кэш очистится — следующий pull заберёт новую версию. Это и плюс (стабильность) и минус (drift между node-ами): если хотите гарантированно одинаковые версии, пиньте по digest.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Pod manifest: image: myapp:dev. imagePullPolicy не указан явно. Какое значение применит kubelet?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5