Published on

K8s ImagePullBackOff·ErrImagePull 원인 9가지

Authors

서버가 멀쩡하고 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 required
  • manifest unknown, not found
  • x509: certificate signed by unknown authority
  • dial tcp ...: i/o timeout, no such host

이제부터는 원인별로 쪼개서 봅니다.

원인 1) 이미지 이름/태그 오타(존재하지 않는 manifest)

전형적인 이벤트

  • manifest unknown: manifest unknown
  • not 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 denied
  • unauthorized: authentication required
  • denied: requested access to the resource is denied

확인

  1. Pod에 imagePullSecrets가 붙어 있는지
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.imagePullSecrets}'
  1. 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 denied
  • insufficient_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 timeout
  • connect: connection refused
  • context 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 host
  • Temporary 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 authority
  • x509: 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 limit
  • 429 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 device
  • failed 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/CA
  • no such host → 5) DNS
  • i/o timeout → 4) 네트워크/egress
  • no 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)이 복합적으로 얽혀 장애가 커지기 쉬우니, 위 진단 루틴과 원인별 체크리스트를 팀 런북에 그대로 넣어두는 것을 권합니다.