Published on

K8s ImagePullBackOff·ErrImagePull 원인 12가지

Authors

서버가 멀쩡하고 애플리케이션 코드도 문제가 없어 보이는데, 파드가 ImagePullBackOff 또는 ErrImagePull에서 멈추면 배포 파이프라인 전체가 막힙니다. 이 에러는 단순히 “이미지를 못 받았다”가 아니라 레지스트리 인증, 네트워크, 이미지 레퍼런스, 노드 런타임, 정책(Policy)과 보안 설정까지 다양한 레이어에서 발생할 수 있습니다.

이 글에서는 실제 현장에서 자주 만나는 원인 12가지를 증상 → 확인 명령 → 해결 흐름으로 정리합니다. (CrashLoopBackOff와는 원인이 다르니 혼동하지 마세요. 컨테이너가 “뜬 다음 죽는” 문제는 별도 글인 Kubernetes CrashLoopBackOff 원인 12가지와 진단을 참고하면 좋습니다.)

먼저: 이벤트로 1차 분류하기

가장 먼저 할 일은 파드 이벤트를 보는 것입니다. ImagePullBackOff재시도(back-off) 상태이고, 그 직전 이벤트에 실제 원인이 찍힙니다.

kubectl describe pod -n <ns> <pod>
# 아래 섹션을 집중적으로 확인
# Events:
#   Warning  Failed     ...  Failed to pull image "...": ...
#   Warning  Failed     ...  ErrImagePull
#   Normal   BackOff    ...  Back-off pulling image "..."

추가로 노드/런타임 로그가 필요할 때가 많습니다.

# 어떤 노드에 스케줄됐는지 확인
kubectl get pod -n <ns> <pod> -o wide

# (노드 접속 후) containerd 기준 이미지 pull 로그/상태 확인
sudo crictl info
sudo crictl pull <image>

이제 원인 12가지를 케이스별로 보겠습니다.

1) 이미지 이름/태그 오타, 존재하지 않는 레퍼런스

증상

  • 이벤트에 manifest unknown, not found, pull access denied(퍼블릭인데도) 등이 섞여 보임

확인

kubectl describe pod -n <ns> <pod> | sed -n '/Containers:/,/Conditions:/p'
# image: 레퍼런스 확인

해결

  • repository/name:tag를 정확히 맞추고, 가능하면 digest 고정으로 재현성을 확보합니다.
containers:
  - name: app
    image: ghcr.io/acme/api@sha256:xxxxxxxx...

2) 잘못된 레지스트리 도메인/프로토콜(사설 레지스트리 주소 실수)

증상

  • dial tcp: lookup ... no such host
  • x509: certificate signed by unknown authority(TLS 관련과 함께 등장)

확인

# 노드에서 DNS/접속 확인
nslookup <registry-host>
curl -v https://<registry-host>/v2/

해결

  • 레지스트리 FQDN을 올바르게 설정
  • 내부 DNS(예: CoreDNS) 설정 및 VPC DNS 옵션 점검

3) ImagePullSecret 누락/네임스페이스 불일치

증상

  • pull access denied, unauthorized: authentication required
  • 같은 시크릿을 만들었는데도 계속 실패(다른 namespace에 만들어짐)

확인

kubectl get secret -n <ns>
kubectl get sa -n <ns> <serviceaccount> -o yaml | yq '.imagePullSecrets'

해결

  • 시크릿을 해당 네임스페이스에 만들고, SA 또는 PodSpec에 연결합니다.
kubectl create secret docker-registry regcred \
  --docker-server=ghcr.io \
  --docker-username=<user> \
  --docker-password=<token> \
  -n <ns>
spec:
  imagePullSecrets:
    - name: regcred

4) ImagePullSecret 형식 오류(.dockerconfigjson 키/타입 문제)

증상

  • 인증 실패가 계속되는데, 토큰/계정은 맞다고 확신
  • 이벤트에 error parsing HTTP 401 response body 같은 메시지

확인

kubectl get secret -n <ns> regcred -o jsonpath='{.type}'; echo
kubectl get secret -n <ns> regcred -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d

해결

  • 타입은 반드시 kubernetes.io/dockerconfigjson
  • 키는 반드시 .dockerconfigjson

5) 서비스어카운트(SA) 교체/오버라이드로 imagePullSecrets가 사라짐

증상

  • 예전엔 되던 배포가 특정 릴리즈부터 실패
  • Helm/ArgoCD 적용 이후 SA가 바뀌면서 시크릿 연결이 누락

확인

kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.serviceAccountName}'; echo
kubectl get sa -n <ns> <sa> -o yaml

해결

  • 배포 템플릿(Helm values 등)에서 SA와 imagePullSecrets를 함께 관리

6) 레지스트리 Rate Limit/쿼터/차단(특히 Docker Hub)

증상

  • 간헐적으로만 실패
  • 이벤트에 toomanyrequests: You have reached your pull rate limit 또는 429

확인

kubectl describe pod -n <ns> <pod> | grep -i -E 'toomany|429|rate'

해결

  • Docker Hub는 인증 pull로 전환하거나 미러/사설 레지스트리 사용
  • CI/CD에서 동일 태그를 과도하게 pull하지 않도록 캐시 전략 수립
  • 필요 시 사내 레지스트리로 프록시 캐시 구성

7) 노드 egress 네트워크 문제(NAT/보안그룹/방화벽)

증상

  • i/o timeout, context deadline exceeded
  • 특정 노드에서만 실패(스케줄링 위치에 따라 성공/실패가 갈림)

확인

kubectl get pod -n <ns> <pod> -o wide
# 실패 노드에 접속 후
curl -I https://registry-1.docker.io/v2/

해결

8) DNS 문제(CoreDNS 장애/노드 resolv.conf 이상)

증상

  • lookup <registry> on <dns-ip>:53: no such host
  • 클러스터 내부 서비스는 되는데 외부 도메인만 실패하거나, 반대로 외부만 됨

확인

kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs deploy/coredns --tail=200

# 임시 디버그 파드로 DNS 확인
kubectl run -it --rm dnsutils --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 --restart=Never -- nslookup ghcr.io

해결

  • CoreDNS 설정(ConfigMap) 및 upstream DNS 확인
  • 노드의 /etc/resolv.conf와 VPC DNS 설정 점검

9) 사설 레지스트리 TLS/인증서 문제(자체 CA, SNI, 중간 인증서 누락)

증상

  • x509: certificate signed by unknown authority
  • certificate is valid for ..., not <registry-host>

확인

# 노드에서 인증서 체인 확인
openssl s_client -connect <registry-host>:443 -servername <registry-host> -showcerts </dev/null

해결

  • 노드 OS에 사설 CA를 신뢰하도록 추가
  • 레지스트리 인증서에 올바른 SAN 설정
  • (가능하면) 레지스트리를 표준 CA로 발급

10) 프라이빗 레지스트리 접근 권한(IAM/IRSA/워크로드 아이덴티티) 오해

증상

  • ECR/GCR/ACR 같은 클라우드 레지스트리에서 denied/unauthorized
  • “IRSA 붙였는데 왜 안 되지?” 같은 상황 (대부분 이미지 pull은 노드 권한)

확인

  • EKS ECR의 경우, 기본적으로 노드 IAM Role이 ECR pull 권한을 가져야 합니다.
# EKS: 노드 인스턴스 프로파일/역할 확인(환경별 도구 사용)
# kubectl만으로는 한계가 있으므로 AWS CLI/콘솔에서 노드 Role 정책 확인

해결

  • 노드 Role에 ecr:GetAuthorizationToken, ecr:BatchGetImage, ecr:GetDownloadUrlForLayer 등 권한 부여
  • 사내 정책상 노드 권한을 최소화해야 한다면, 레지스트리 미러/프록시 또는 풀 시크릿 방식으로 전환

11) disk pressure/스토리지 부족으로 이미지 레이어를 저장 못함

증상

  • 이벤트에 no space left on device
  • 노드 상태가 DiskPressure=True

확인

kubectl describe node <node> | grep -A3 -i DiskPressure

# 노드에서 실제 디스크/인오드 확인
df -h
df -i
sudo du -sh /var/lib/containerd /var/lib/docker 2>/dev/null

해결

12) 이미지 아키텍처 불일치(amd64/arm64) 또는 OS/플랫폼 미지원

증상

  • no matching manifest for linux/arm64/v8 in the manifest list entries
  • Graviton(arm64) 노드에서 amd64 전용 이미지를 당겨 실패

확인

# 로컬/CI에서 매니페스트 확인
docker buildx imagetools inspect <image:tag>

# 노드 아키텍처 확인
kubectl get node -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}'

해결

  • 멀티아키텍처 이미지로 빌드/푸시
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t <registry>/<repo>:<tag> \
  --push .

빠른 체크리스트: 5분 내 결론 내기

  1. kubectl describe pod 이벤트에서 첫 번째 Failed pull 메시지를 읽는다.
  2. unauthorized면: Secret/SA/레지스트리 권한(노드 IAM 포함)부터 본다.
  3. not found/manifest unknown면: 이미지 레퍼런스 오타/태그/리포지토리 존재 여부.
  4. timeout/lookup이면: 노드 egress, DNS, 방화벽.
  5. x509면: 사설 CA 신뢰/인증서 SAN/체인.
  6. no space left면: 디스크/인오드/DiskPressure.
  7. no matching manifest면: 아키텍처(arm64/amd64) 빌드 전략.

부록: 재현 가능한 디버그용 Pod 템플릿

이미지 pull 자체를 분리해서 확인하려면, 앱 컨테이너 대신 최소 컨테이너로 동일한 imagePullSecrets/SA를 붙여 테스트합니다.

apiVersion: v1
kind: Pod
metadata:
  name: image-pull-debug
  namespace: default
spec:
  restartPolicy: Never
  imagePullSecrets:
    - name: regcred
  containers:
    - name: debug
      image: <your-private-image>:<tag>
      command: ["sh", "-c", "echo pulled && sleep 10"]
kubectl apply -f image-pull-debug.yaml
kubectl describe pod image-pull-debug

마무리

ImagePullBackOff/ErrImagePull은 원인이 다양하지만, 이벤트 메시지 패턴이 비교적 명확해서 분류만 제대로 하면 해결이 빠른 편입니다. 특히 “인증(Secret/권한) vs 네트워크(DNS/egress) vs 플랫폼(arch) vs 노드 리소스(디스크)” 네 축으로 나눠 접근하면 불필요한 삽질을 줄일 수 있습니다.

다음 단계로는, 같은 문제가 재발하지 않도록 (1) 이미지 digest 고정, (2) 멀티아키 빌드 표준화, (3) 노드 디스크/인오드 모니터링, (4) 레지스트리 인증 만료 알림을 CI/CD에 넣는 것을 권장합니다.