- Published on
K8s ImagePullBackOff·ErrImagePull 원인 12가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡하고 애플리케이션 코드도 문제가 없어 보이는데, 파드가 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 hostx509: 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/
해결
- 노드가 퍼블릭 인터넷으로 나갈 경로(NAT GW, 라우팅, SG/NACL)를 갖는지 확인
- EKS라면 노드 NotReady/네트워크 전반 이슈도 함께 점검해야 합니다. 특히 CNI/보안그룹이 꼬인 경우는 Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검 체크리스트가 바로 도움이 됩니다.
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 authoritycertificate 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
해결
- 사용하지 않는 이미지/컨테이너 정리, ephemeral storage 확장
- “디스크 용량은 남았는데 no space”면 인오드 고갈일 수 있습니다. 이 케이스는 No space left on device인데 용량 남을 때 - inode 0% 해결를 그대로 적용하면 진단이 빨라집니다.
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분 내 결론 내기
kubectl describe pod이벤트에서 첫 번째 Failed pull 메시지를 읽는다.unauthorized면: Secret/SA/레지스트리 권한(노드 IAM 포함)부터 본다.not found/manifest unknown면: 이미지 레퍼런스 오타/태그/리포지토리 존재 여부.timeout/lookup이면: 노드 egress, DNS, 방화벽.x509면: 사설 CA 신뢰/인증서 SAN/체인.no space left면: 디스크/인오드/DiskPressure.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에 넣는 것을 권장합니다.