- Published on
K8s ImagePullBackOff·ErrImagePull 원인 9가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡하고 Pod 스펙도 단순해 보이는데도 ImagePullBackOff 또는 ErrImagePull이 뜨면, 대부분은 노드(런타임/네트워크/DNS) 혹은 레지스트리 인증/권한/이미지 참조 중 한 축에서 문제가 납니다. 중요한 건 “왜 Pull이 실패했는지”를 이벤트 메시지에서 정확히 읽고, 원인 범주를 빠르게 좁히는 것입니다.
이 글에서는 현장에서 자주 만나는 원인 9가지를 증상 → 확인 방법 → 해결 순으로 정리합니다. (containerd 기준 예시 포함)
먼저: 3분 진단 루틴(이벤트가 답이다)
아래 3개만으로도 70%는 방향이 잡힙니다.
# 1) Pod 이벤트(가장 중요)
kubectl describe pod -n <ns> <pod>
# 2) 해당 컨테이너 상태만 빠르게
kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[*].state.waiting.message}'
# 3) 노드에서 런타임 로그(필요 시)
# containerd
sudo journalctl -u containerd -n 200 --no-pager
# kubelet
sudo journalctl -u kubelet -n 200 --no-pager
describe 출력에서 흔히 보게 되는 힌트는 다음과 같습니다.
Failed to pull image ... rpc error: code = Unknown desc = ...pull access denied,unauthorized: authentication requiredmanifest unknown,not foundx509: certificate signed by unknown authoritydial tcp ...: i/o timeout,no such host
이제부터는 원인별로 쪼개서 봅니다.
원인 1) 이미지 이름/태그 오타(존재하지 않는 manifest)
전형적인 이벤트
manifest unknown: manifest unknownnot found
확인
image: repo/app:tag에서 tag가 실제로 존재하는지- 멀티아키 이미지인지(amd64/arm64)도 함께 확인
kubectl get deploy -n <ns> <deploy> -o jsonpath='{.spec.template.spec.containers[*].image}'
해결
- 올바른 태그로 수정
- 태그 고정 대신 digest 고정(재현성 확보)
containers:
- name: app
image: registry.example.com/team/app@sha256:0123abcd...
원인 2) Private Registry 인증 실패(ImagePullSecret 누락/오타)
전형적인 이벤트
pull access deniedunauthorized: authentication requireddenied: requested access to the resource is denied
확인
- Pod에
imagePullSecrets가 붙어 있는지
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.imagePullSecrets}'
- Secret 타입이 맞는지
kubectl get secret -n <ns> <secret> -o jsonpath='{.type}'
# kubernetes.io/dockerconfigjson 이어야 함
해결
- dockerconfigjson secret 생성 후 서비스어카운트에 연결(권장)
kubectl create secret docker-registry regcred \
-n <ns> \
--docker-server=registry.example.com \
--docker-username="$USER" \
--docker-password="$PASS" \
--docker-email="dev@example.com"
kubectl patch serviceaccount default -n <ns> \
-p '{"imagePullSecrets":[{"name":"regcred"}]}'
> EKS에서 IRSA/OIDC가 꼬이면 “레지스트리 권한”이 아니라 “노드/파드 IAM” 문제로 번질 때가 있습니다. 특히 ECR 접근이 갑자기 전부 실패한다면 OIDC/IRSA 계층도 의심하세요: EKS OIDC Provider 삭제로 IRSA 전부 실패했을 때 복구
원인 3) 레지스트리 권한(인증은 되지만 Authorization 실패)
전형적인 이벤트
denied: requested access to the resource is deniedinsufficient_scope: authorization failed
확인
- 동일한 자격증명으로 해당 repo/path pull이 가능한지
- 조직/프로젝트 단위 권한(GitLab Registry, Harbor, ECR repo policy 등)
해결
- 레지스트리에서 repo pull 권한 부여
- ECR이라면 노드 IAM role 또는 IRSA role에
ecr:GetAuthorizationToken,ecr:BatchGetImage,ecr:GetDownloadUrlForLayer등 포함
원인 4) 노드가 레지스트리로 나갈 수 없음(egress 차단/라우팅/방화벽)
전형적인 이벤트
dial tcp <ip>:443: i/o timeoutconnect: connection refusedcontext deadline exceeded
확인(클러스터 내부에서 네트워크 테스트)
간단한 디버그 Pod를 띄워 DNS/HTTPS를 확인합니다.
kubectl run net-debug -it --rm \
--image=curlimages/curl:8.5.0 \
-n <ns> -- sh
# Pod 안에서
nslookup registry.example.com
curl -Iv https://registry.example.com/v2/
노드 보안그룹/라우팅/방화벽 이슈는 “Pod는 뜨지도 못하니” 네트워크가 멀쩡해 보이는 착시를 만듭니다. Azure/클라우드 환경이라면 NSG/UDR 같은 계층에서 egress가 막히는 경우가 많습니다. SSH/네트워크 진단 관점은 아래 글의 체크리스트가 그대로 도움이 됩니다: Azure VM SSH 접속 불가 - NSG·UDR·Bastion 진단법
해결
- 노드/서브넷 egress 허용(443)
- 프라이빗 레지스트리면 VPC 엔드포인트/프라이빗 링크 구성
- 회사망 프록시가 필요하면 containerd/systemd에 프록시 환경변수 설정
원인 5) DNS 문제(레지스트리 도메인 해석 실패)
전형적인 이벤트
lookup registry.example.com: no such hostTemporary failure in name resolution
확인
- CoreDNS 상태 및 로그
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=200
- 노드의
/etc/resolv.conf및 VPC DNS 설정
해결
- CoreDNS 리소스 부족/루프/업스트림 DNS 장애 해결
- 사내 DNS가 split-horizon이면 노드/파드 DNS 정책 재검토
원인 6) 레지스트리 TLS 인증서 문제(사설 CA, MITM 프록시)
전형적인 이벤트
x509: certificate signed by unknown authorityx509: certificate is valid for ..., not <host>
확인
- 레지스트리 인증서 체인/호스트네임 일치 여부
- 사내 프록시가 TLS를 가로채는지(중간자 CA)
해결
- 노드 OS 및 containerd가 신뢰하는 CA에 사설 CA 추가
- Insecure registry 설정은 최후의 수단(보안/컴플라이언스 이슈)
containerd에서 레지스트리별 TLS 설정은 보통 /etc/containerd/certs.d/<registry>/hosts.toml로 관리합니다(배포판/구성에 따라 상이).
예시(개념):
# /etc/containerd/certs.d/registry.example.com/hosts.toml
server = "https://registry.example.com"
[host."https://registry.example.com"]
ca = "/etc/ssl/certs/company-ca.pem"
적용 후 containerd 재시작:
sudo systemctl restart containerd
원인 7) 아키텍처/플랫폼 불일치(arm 노드에 amd64 이미지)
전형적인 이벤트
no matching manifest for linux/arm64 in the manifest list entries
확인
- 노드 아키텍처 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}'
해결
- 멀티아키 이미지로 빌드/푸시(buildx)
docker buildx build --platform linux/amd64,linux/arm64 \
-t registry.example.com/team/app:1.2.3 \
--push .
- 또는 노드 셀렉터로 아키텍처를 강제(임시 회피)
spec:
template:
spec:
nodeSelector:
kubernetes.io/arch: amd64
원인 8) 이미지 풀 레이트 리밋/쿼터(Docker Hub 등)
전형적인 이벤트
toomanyrequests: You have reached your pull rate limit429 Too Many Requests
확인
- 같은 노드/같은 NAT 뒤에서 동시 Pull이 폭증했는지
- CI/CD가 짧은 시간에 많은 노드를 스케일아웃했는지
해결
- Docker Hub는 유료 플랜/인증 사용 또는 미러/프라이빗 레지스트리로 캐싱
- ECR/ACR/GCR 같은 내부 레지스트리로 base image 미러링
imagePullPolicy: IfNotPresent로 불필요한 재풀 방지(단, 태그 전략과 함께 사용)
containers:
- name: app
image: registry.example.com/team/app:1.2.3
imagePullPolicy: IfNotPresent
원인 9) 노드 디스크/인오드 부족, 런타임 스토리지 문제
이미지 자체는 정상인데, 노드에서 레이어를 저장/압축해제하는 과정에서 실패하는 케이스입니다.
전형적인 이벤트/로그
no space left on devicefailed to extract layer- containerd 로그에 snapshotter 오류
확인
노드에서 디스크/인오드 확인:
df -h
df -i
sudo du -sh /var/lib/containerd/* 2>/dev/null | sort -h | tail
해결
- 미사용 이미지/컨테이너 정리
- kubelet eviction 설정/노드 사이즈 확장
- (가능하면) 이미지 태그/레이어 최적화로 크기 감소
containerd에서 간단 정리(환경에 따라 주의):
# nerdctl 사용 시
sudo nerdctl image prune -a
sudo nerdctl container prune
실전 팁: 메시지 패턴으로 “원인 범주”를 먼저 고정하기
운영에서 시간을 아끼는 방식은 에러 문자열을 범주화하는 것입니다.
unauthorized/denied→ 2) 인증 또는 3) 권한manifest unknown/not found→ 1) 오타/태그x509→ 6) TLS/CAno such host→ 5) DNSi/o timeout→ 4) 네트워크/egressno matching manifest→ 7) 아키텍처toomanyrequests/429→ 8) 레이트 리밋no space left on device→ 9) 디스크
(부록) 자주 쓰는 점검용 YAML: 이미지 풀만 검증하는 Job
애플리케이션 자체 실행 전에 “이미지 Pull 가능 여부”만 빠르게 검증하고 싶을 때가 있습니다.
apiVersion: batch/v1
kind: Job
metadata:
name: image-pull-smoke
namespace: default
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
imagePullSecrets:
- name: regcred
containers:
- name: smoke
image: registry.example.com/team/app:1.2.3
command: ["sh", "-c", "echo pulled && sleep 1"]
이 Job이 Completed로 끝나면, 최소한 레지스트리 접근/인증/manifest는 통과한 것입니다. 이후 문제는 앱 런타임/환경변수/볼륨 쪽으로 좁힐 수 있습니다.
마무리
ImagePullBackOff는 “쿠버네티스가 이미지를 못 가져왔다”는 결과일 뿐, 원인은 매우 다양합니다. 하지만 이벤트 메시지의 키워드를 기준으로 9가지 범주 중 어디에 속하는지 먼저 분류하면, 불필요하게 클러스터 전체를 의심하거나(혹은 반대로 앱만 의심하거나) 시간을 낭비하지 않게 됩니다.
특히 클라우드 환경에서는 네트워크 계층(NSG/라우팅/프록시)과 IAM(레지스트리 권한/IRSA)이 복합적으로 얽혀 장애가 커지기 쉬우니, 위 진단 루틴과 원인별 체크리스트를 팀 런북에 그대로 넣어두는 것을 권합니다.