docker/k8s『Image Pull BackOff』の原因と対処法

docker/k8s『Image Pull BackOff』の原因と対処法

KubernetesのPodがコンテナイメージ取得に失敗し、一定間隔で再試行し続ける状態。典型原因はイメージ名・タグの誤り、プライベートレジストリの認証不備、ネットワークやDNSの障害、プロキシ/ファイアウォール、レート制限、レジストリ側の障害など。失敗理由はノード側のイベント/ログに必ず痕跡が残るため、原因を特定してからタグ・資格情報・ネットワークのいずれかを修正する。

症状と発生条件(まず把握)

・PodがPending→ContainerCreating→ImagePullBackOff / ErrImagePullを繰り返す。

・ノードCRI(containerd/CRI-O/Docker)でのpullが401/403/404/429/500系などで失敗。

・誤ったレジストリホスト名/リポジトリ名/タグ、認証情報がない/期限切れ、ネットワーク不可達、DNS解決失敗、プロキシ設定不整合、レート制限到達などで再現。

最短切り分け(「どこで」「なぜ」落ちたかを確認)

# Podの概要と直近イベント
kubectl get pods -n <ns>
kubectl describe pod <pod> -n <ns> | sed -n '/Events/,$p'

# イメージ別のpullログ(失敗理由のHTTPコードやメッセージ)
kubectl logs <pod> -n <ns> --previous --all-containers=true 2>/dev/null || true

# ノードで直接pull(権限・タグ・ネットワーク検証)
crictl pull <image:tag>              # containerd/CRI-O
# or
sudo nerdctl -n k8s.io pull <image:tag>
# or (Dockerホストなら)
docker pull <image:tag>

イメージ名とタグを正規化(404/manifest unknown対策)

・<レジストリ/リポジトリ/タグ/ダイジェスト>の4要素を明示。latest依存は避け、ダイジェスト固定を推奨。

# NG: あいまいなlatest
image: ghcr.io/org/app:latest

# OK: 明示タグ or ダイジェスト固定
image: ghcr.io/org/app:v1.2.3
image: ghcr.io/org/app@sha256:...  # 再現性重視

プライベートレジストリ認証(imagePullSecretsで401/403を解消)

# docker login 相当のSecretを作成
kubectl create secret docker-registry regcred \
  --docker-server=ghcr.io \
  --docker-username=YOUR_USER \
  --docker-password=YOUR_TOKEN \
  --docker-email=dev@example.com -n <ns>

# Pod/ServiceAccountへ紐付け
kubectl patch serviceaccount default -n <ns> \
  -p '{"imagePullSecrets":[{"name":"regcred"}]}'
# 直接Podマニフェストへ指定する例
apiVersion: v1
kind: Pod
metadata: { name: app, namespace: default }
spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: app
      image: ghcr.io/org/app:v1.2.3

ネットワーク/DNS/プロキシの検査(name resolution/timeout対策)

# ノード内でレジストリFQDNを解決・到達確認
nsenter -t $(pgrep -f kubelet | head -n1) -m -n -- sh -lc "
  getent hosts ghcr.io && \
  nc -vz ghcr.io 443 || true"

# Podからも確認(BusyBox等)
kubectl run dnstest --rm -it --image=busybox --restart=Never -- sh -c "
  nslookup ghcr.io && wget -qO- https://ghcr.io || true"

・企業プロキシ下ではノード側のCRIにHTTP(S)_PROXY/NO_PROXYを設定し、レジストリFQDNをNO_PROXYに含める。

レート制限/スロットリング(429/Too Many Requests対策)

・Docker Hub等のpull制限に到達すると連続失敗→BackOff。

・自前レジストリ/キャッシュ(proxy)の導入、CI/CDでのpull節約(イメージ再利用・ダイジェスト固定)を検討。

# ダウンロード重複を抑える(同一ノードでのWarm-upなど)
crictl images | grep <image> || crictl pull <image:tag>

レジストリ証明書/社内CA(x509/証明書エラー対策)

・社内レジストリ/自己署名ではノードにCAを配布。containerdなら/etc/containerd/certs.d//hosts.tomlを用意。

# containerd の hosts.toml 例(TLS/ミラーを定義)
sudo mkdir -p /etc/containerd/certs.d/registry.intra.local
sudo tee /etc/containerd/certs.d/registry.intra.local/hosts.toml <<'EOF'
server = "https://registry.intra.local"
[host."https://registry.intra.local"]
  capabilities = ["pull", "resolve"]
  ca = "/etc/ssl/certs/intra-ca.pem"
EOF
sudo systemctl restart containerd

イメージの存在確認と公開設定(404/deniedの根治)

# GitHub Container Registry 例:可視性・権限を確認
# 1) リポジトリの"packages"が公開か、PATにread:packagesが含まれるか
# 2) 該当タグ/ダイジェストが存在するか(UI/gh apiで確認)

# CLIでダイジェストを固定確認(Docker)
docker pull ghcr.io/org/app:v1.2.3
docker inspect ghcr.io/org/app:v1.2.3 --format '{{.RepoDigests}}'

Podテンプレートの更新と再作成(BackOffの持続解消)

# Deployment のイメージ差し替え
kubectl set image deploy/app app=ghcr.io/org/app:v1.2.4 -n <ns>
kubectl rollout status deploy/app -n <ns>

# ロールアウトが止まる場合のイベント確認
kubectl describe deploy/app -n <ns> | sed -n '/Events/,$p'

・誤タグ修正やimagePullSecrets追加など、Spec差分を適用後にBackOffは解消される。

ノード/CRIのpull並列制御と再試行(一時障害に強くする)

# containerdのコンフィグ(/etc/containerd/config.toml)を作成/編集
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
# 必要に応じて timeout や registry 設定、mirrors を記述
sudo systemctl restart containerd

・CI/CDの大量同時デプロイ時はロールアウトのmaxUnavailable/Surgeを調整して集中を緩和。

最小再現テンプレ(小さな公開イメージで健全性確認)

# まずは確実にpullできる公式イメージでノード健全性を確認
kubectl run pull-test --image=busybox:1.36 --restart=Never --command -- sh -c "echo ok && sleep 5"
kubectl get pod pull-test -w
kubectl logs pull-test || true
kubectl delete pod pull-test

NG→OK早見(クイック修正)

・NG:latestに依存 → OK:固定タグ/ダイジェストで再現性を確保。

・NG:プライベートなのにimagePullSecrets未設定 → OK:docker-registry SecretをSA/Podに紐付け。

・NG:ノードからレジストリFQDN解決不可 → OK:CoreDNS/ノードDNS/NO_PROXYを整備。

・NG:レート制限に突入 → OK:キャッシュ/ミラー/pull削減で回避。

・NG:自己署名レジストリでx509失敗 → OK:CA配布+containerd hosts.toml。

・NG:404/manifest unknown → OK:リポジトリ名/タグを正規化し存在確認。

・NG:BackOffのまま放置 → OK:Spec修正→rollout監視で確実に復旧。

チェックリスト(上から順に潰す)

・describeのEventsに出ているHTTPコード/理由を読む(401/403/404/429/5xx)。

・イメージ名・タグ・ダイジェストが正しいか、非公開ならimagePullSecretsは正しく紐付いているか。

・ノードからレジストリFQDNへDNS解決/443到達できるか(プロキシ/Firewall/NO_PROXY)。

・レート制限やレジストリ障害の可能性はないか(時間を置く・ミラー使用)。

・自己署名/社内CAはノードCRIへ配布されているか。

・Deployment/PodSpecを更新してロールアウトが完了したか。

・小さな公開イメージでpull健全性をまず確認し、問題の切り分けを終える。