/인프라/ImagePullBackOff·ErrImagePull 해결: Events로 원인 진단하고 복붙으로 끝내기
인프라KubernetesImagePullBackOff

ImagePullBackOff·ErrImagePull 해결: Events로 원인 진단하고 복붙으로 끝내기

kubectl ImagePullBackOff와 ErrImagePull을 Events 메시지 진단표로 5분 만에 특정합니다. imagePullSecrets 네임스페이스, private registry 인증, Docker Hub rate limit, ECR 토큰 만료까지 복붙 명령어로 해결하는 실전 가이드.

ImagePullBackOff·ErrImagePull 해결: Events로 원인 진단하고 복붙으로 끝내기

ImagePullBackOff·ErrImagePull 해결: Events 메시지로 원인 진단하고 복붙으로 끝내기

K8s_Troubleshooting_Guide 17편

배포 파이프라인은 초록불인데 Pod가 또 ImagePullBackOff로 멈췄다면, PVC도 네트워크 정책도 통과한 다음 단계, 바로 kubelet이 레지스트리에서 이미지를 끌어오는 단계에서 막힌 겁니다. 이 글은 그 한 단계에만 집중합니다. kubectl describe podEvents 한 줄만 보고 5가지 원인 중 정답을 특정하고, 원인별 복붙 명령어로 5분 안에 복구하는 게 목표예요.

ImagePullBackOff vs ErrImagePull, 둘은 같은 문제다

먼저 헷갈리는 두 상태부터 정리하죠. 둘은 별개 문제가 아니라 같은 실패의 진행 단계입니다.

  • ErrImagePull: kubelet이 이미지 풀을 처음 시도했다가 실패한 직후 상태
  • ImagePullBackOff: 실패가 반복되어 kubelet이 재시도 간격을 지수적으로 늘리며(back-off) 대기 중인 상태

ErrImagePull이 잠깐 보이다가 ImagePullBackOff로 굳습니다. 둘 다 원인은 동일하고 진단 방법도 같습니다. 상태 이름에 집착하지 말고 곧장 Events를 봅시다.

1단계 진단 — Events 메시지 읽는 법

원인의 90%는 Events 마지막 줄에 적혀 있습니다. 다음 두 명령이면 충분해요.

Bash
# 최신순으로 이벤트 모아보기
kubectl get events --sort-by=.lastTimestamp -n <namespace>

# 특정 Pod의 Events만 빠르게 추출
kubectl describe pod <pod> -n <namespace> | grep -A10 Events

Warning Failed 줄의 메시지 문구를 아래 진단표에 그대로 대조하세요.

원인 진단표

Events 메시지 (핵심 문구)원인해결 섹션
Failed to pull image ... not found / manifest unknown태그 오타·존재하지 않는 태그태그 문제
unauthorized: authentication required / pull access denied인증 누락(private registry)인증 누락
toomanyrequests: You have reached your pull rate limitDocker Hub rate limitrate limit
dial tcp: lookup ... no such host / i/o timeout레지스트리 호스트 해석·도달 실패호스트 해석
failed to ... no space left on device노드 디스크 부족디스크

이 표 한 장이면 "메시지 보이면 바로 여기로" 가 됩니다.

2단계 해결 — 원인별 복붙 명령어

원인 1: 태그 오타·없는 태그

not found면 가장 흔하고 가장 허무한 원인입니다. 레지스트리에 실제로 그 태그가 있는지 먼저 확인하세요.

Bash
# 매니페스트에 박힌 이미지 문자열 확인
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.containers[*].image}'

# 로컬에서 동일 태그 풀 가능 여부 테스트
docker pull <registry>/<image>:<tag>

오타였다면 kubectl set image deployment/<deploy> <container>=<image>:<correct-tag> 로 교체하면 끝입니다.

원인 2: 인증 누락 — imagePullSecrets

unauthorized면 자격증명이 없는 겁니다. 시크릿을 만들고 Pod에 연결하세요.

Bash
# 1) 레지스트리 로그인이 되는지 먼저 확인
docker login <registry>

# 2) docker-registry 타입 시크릿 생성 (반드시 앱과 같은 네임스페이스에!)
kubectl create secret docker-registry regcred \
  --docker-server=<registry> \
  --docker-username=<user> \
  --docker-password=<password> \
  --docker-email=<email> \
  -n <namespace>

Pod(또는 Deployment의 podTemplate)에 시크릿을 붙입니다.

YAML
spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: app
      image: <registry>/<image>:<tag>

매 워크로드마다 적기 귀찮다면 ServiceAccount에 한 번 붙여 네임스페이스 전체에 적용하세요.

Bash
kubectl patch serviceaccount default -n <namespace> \
  -p '{"imagePullSecrets":[{"name":"regcred"}]}'

원인 3: Docker Hub rate limit

2024~2025년 들어 Docker Hub의 익명(anonymous) pull rate limit이 강화되면서 CI/운영 노드에서 toomanyrequests가 폭증했습니다. 같은 NAT IP를 쓰는 클러스터는 특히 잘 걸립니다. 대응은 두 갈래예요.

(A) 인증 계정으로 풀하기 — 인증 사용자는 한도가 크게 올라갑니다. 위의 regcred를 Docker Hub 계정으로 만들어 ServiceAccount에 붙이면 즉시 완화됩니다.

(B) 미러/풀스루 캐시로 전환 — 근본 해결책입니다. Harbor proxy cache나 ECR pull-through cache를 두고 이미지 경로만 바꾸세요.

YAML
# 기존: nginx:1.27  (Docker Hub 직접)
# 변경: <account>.dkr.ecr.<region>.amazonaws.com/dockerhub/library/nginx:1.27
image: harbor.mycorp.com/dockerhub-proxy/library/nginx:1.27

실무 경험담: 한 클러스터에서 야간 배치가 동시에 수십 개 Pod를 띄우자 전부 toomanyrequests로 깨진 적이 있습니다. 인증 계정만 붙여도 급한 불은 꺼졌지만, 결국 Harbor 프록시 캐시를 깔고 나서야 재발이 멈췄어요. CI와 운영이 같은 출구 IP를 쓴다면 캐시는 선택이 아니라 필수입니다.

원인 4: 레지스트리 호스트 해석 실패

lookup ... no such host는 kubelet이 레지스트리 도메인을 DNS로 풀지 못한 경우입니다. (DNS 일반 트러블슈팅은 별도 편에서 다뤘으니 여기선 레지스트리 호스트 관점만.) 노드에서 직접 확인하세요.

Bash
# 노드에 들어가 레지스트리 도메인 해석 테스트
nslookup <registry-host>
curl -v https://<registry-host>/v2/

사내 레지스트리라면 노드 /etc/hosts나 사설 DNS 등록 여부, 프라이빗 엔드포인트 라우팅을 점검합니다.

원인 5: 노드 디스크 부족

no space left on device는 이미지 레이어를 풀 디스크 공간이 없다는 뜻입니다.

Bash
# 어느 노드에 떴는지 확인
kubectl get pod <pod> -n <ns> -o wide

# 노드 디스크 압박 상태 확인
kubectl describe node <node> | grep -A5 Conditions   # DiskPressure 확인
# 노드 접속 후
df -h /var/lib/containerd   # 또는 /var/lib/docker
crictl rmi --prune          # 미사용 이미지 정리

3단계 흔한 함정과 클라우드 레지스트리

함정 1: secret 네임스페이스 불일치

가장 많이 밟는 지뢰입니다. 시크릿을 default에만 만들고 앱은 prod 네임스페이스에서 도는 경우, 인증은 절대 적용되지 않습니다. imagePullSecrets는 Pod와 같은 네임스페이스의 시크릿만 참조하거든요.

Bash
# prod ns에 똑같이 만들어 줘야 함
kubectl create secret docker-registry regcred ... -n prod

함정 2: latest + imagePullPolicy: Always의 디버깅 지옥

latest 태그에 Always 정책을 쓰면, 매 재시작마다 다른 이미지를 받아올 수 있어 "어제는 됐는데 오늘은 안 되는" 재현 불가 장애가 생깁니다. 권장 전략은 불변 태그(immutable tag) 입니다.

  • myapp:latest + imagePullPolicy: Always
  • myapp:1.4.2 또는 myapp:git-a1b2c3d (커밋/빌드 기반 고정 태그)

불변 태그를 쓰면 imagePullPolicy: IfNotPresent로도 안전하고, 캐시 적중률도 올라갑니다.

클라우드별 토큰 만료 자동 갱신

매니지드 레지스트리는 토큰이 만료됩니다. 어제 잘 되던 인증이 오늘 unauthorized로 바뀌면 토큰 만료를 의심하세요.

레지스트리인증 명령만료/권장
AWS ECRaws ecr get-login-password | docker login ...토큰 12시간 만료 → IRSA로 노드/Pod에 IAM 부여가 정석. 부득이하면 CronJob이 시크릿 재생성
GCP GCR/ARgcloud auth configure-dockerWorkload Identity 권장, 토큰 단기
Azure ACRaz acr login --name <registry>AAD 토큰 단기 → AKS는 --attach-acr로 자동 인증

ECR을 정적 시크릿(regcred)으로 쓰면 12시간 뒤 무조건 깨집니다. 가능하면 IRSA / Workload Identity로 키 없는 인증을 구성하고, 그게 어렵다면 토큰 갱신 CronJob을 두세요.

YAML
# ECR 토큰 갱신 CronJob 핵심 로직 (11시간마다)
schedule: "0 */11 * * *"
# 컨테이너 command 예시
# kubectl delete secret regcred -n app --ignore-not-found
# kubectl create secret docker-registry regcred \
#   --docker-server=<acct>.dkr.ecr.<region>.amazonaws.com \
#   --docker-username=AWS \
#   --docker-password=$(aws ecr get-login-password --region <region>) -n app

결론: 재발 방지 체크리스트

  • ✅ 첫 액션은 무조건 kubectl describe pod | grep -A10 Events
  • ✅ 시크릿은 앱과 같은 네임스페이스에 생성, ServiceAccount에 붙여 전파
  • latest 금지, 불변 태그 + IfNotPresent 표준화
  • ✅ Docker Hub 직접 풀 대신 인증 계정 + 풀스루 캐시
  • ✅ ECR/AR/ACR은 IRSA/Workload Identity 우선, 정적 시크릿이면 갱신 자동화

다음 18편에서는 이미지를 잘 받아와 컨테이너는 떴는데 CrashLoopBackOff로 무한 재시작하는 단계 — 로그·exit code·probe로 원인을 가르는 법을 다룹니다.

참고: 공식 문서

이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.

자주 묻는 질문 (FAQ)

Q. ImagePullBackOff와 ErrImagePull, 뭐가 더 심각한 건가요? A. 심각도 차이가 아니라 진행 단계 차이입니다. ErrImagePull은 풀 실패 직후, ImagePullBackOff는 실패가 반복돼 재시도 대기 중인 상태예요. 원인과 해결법은 완전히 동일하니 곧장 Events 메시지를 확인하면 됩니다.

Q. imagePullSecrets를 분명히 만들었는데 계속 unauthorized가 떠요. A. 십중팔구 네임스페이스 불일치입니다. 시크릿은 Pod와 같은 네임스페이스에 있어야 참조됩니다. kubectl get secret regcred -n <앱-네임스페이스>로 존재를 확인하고, ECR이라면 토큰 12시간 만료도 함께 의심하세요.

Q. Docker Hub toomanyrequests, 인증만 붙이면 끝인가요? A. 급한 상황은 인증 계정으로 완화되지만, CI와 운영이 같은 출구 IP를 공유하면 재발합니다. Harbor proxy cache나 ECR pull-through cache 같은 미러를 두고 이미지 경로를 캐시로 돌리는 것이 근본 해결책입니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·

댓글

첫 번째 댓글을 남겨보세요.