- Published on
K8s ImagePullBackOff - registry 인증·캐시 진단법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡해 보이는데 Pod만 ImagePullBackOff로 멈추면, 문제는 대개 (1) 레지스트리 인증, (2) 레지스트리까지의 네트워크 경로/정책, (3) 노드 런타임/캐시 상태 중 하나입니다. 특히 EKS/ECR 조합에서는 토큰 만료, IRSA/노드 IAM 권한, 프라이빗 엔드포인트 정책 등으로 원인이 분산됩니다.
이 글은 “무조건 재배포/재시작” 대신, 이벤트 → 인증 → 네트워크 → 캐시/런타임 순서로 재현 가능하게 원인을 좁히는 체크리스트를 제공합니다.
> 참고: Pod가 뜬 뒤 앱이 죽는 CrashLoopBackOff와는 결이 다릅니다. ImagePullBackOff는 컨테이너가 시작조차 못한 상태입니다. (관련 디버깅 프레임은 Kubernetes CrashLoopBackOff 원인별 로그·Probe·리소스 디버깅도 참고)
1) 가장 먼저: 이벤트에서 “실패 지점”을 확정
ImagePullBackOff는 결과 상태일 뿐이고, 진짜 단서는 Pod 이벤트에 있습니다.
kubectl describe pod -n <ns> <pod>
# 또는 이벤트만
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 50
이벤트에서 자주 보이는 패턴과 의미는 다음과 같습니다.
Failed to pull image ... unauthorized: authentication required- 인증/권한 문제(Secret, ECR 권한, 토큰 만료 등)
manifest unknown/not found- 태그/리포지토리 경로 오타, 빌드/푸시 누락
x509: certificate signed by unknown authority- 사설 레지스트리의 CA 미신뢰, 노드에 CA 배포 필요
i/o timeout,dial tcp ... timeout,connection refused- 네트워크 경로/방화벽/프록시/DNS 문제
too many requests/429- Docker Hub 등 레이트 리밋, 미러/캐시/인증 필요
여기서 목표는 “대충 인증 같음”이 아니라, unauthorized인지 timeout인지 not found인지를 1분 안에 확정하는 것입니다.
2) 이미지 레퍼런스 자체를 검증(가장 값싼 실수 제거)
의외로 태그 오타, 잘못된 registry host, 멀티 아키텍처 미지원이 빈번합니다.
2-1) 태그/다이제스트 확인
- 태그 대신 다이제스트를 쓰면 배포 재현성이 올라가고, 캐시/태그 변경 이슈를 줄일 수 있습니다.
# deployment.yaml 일부
spec:
containers:
- name: app
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp@sha256:....
2-2) 아키텍처 불일치(ARM 노드에 amd64만 푸시 등)
Graviton(arm64) 노드가 섞인 클러스터에서 amd64 단일 이미지만 올리면 pull 단계에서 실패하거나 실행 직후 실패할 수 있습니다.
- 해결: 멀티 아키텍처 manifest를 푸시
docker buildx build --platform linux/amd64,linux/arm64 \
-t <registry>/<repo>:<tag> --push .
3) Registry 인증: imagePullSecret vs 노드 IAM vs IRSA
인증 방식은 크게 두 갈래입니다.
- (A) imagePullSecret: Docker registry credentials를 Secret에 넣고 Pod에 연결
- (B) 클라우드 네이티브(EKS/ECR): 노드 IAM 또는 ECR credential provider를 통해 자동 인증
어떤 방식인지 먼저 확정해야 삽질을 줄일 수 있습니다.
3-1) imagePullSecret 연결 상태 확인
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.imagePullSecrets}'
kubectl get sa -n <ns> <serviceaccount> -o yaml | yq '.imagePullSecrets'
Secret이 존재하는지 확인:
kubectl get secret -n <ns> <secret-name>
kubectl get secret -n <ns> <secret-name> -o jsonpath='{.type}'; echo
# 보통 kubernetes.io/dockerconfigjson
dockerconfigjson 내용이 올바른지(레지스트리 주소, username/password/token) 점검:
kubectl get secret -n <ns> <secret-name> -o jsonpath='{.data..dockerconfigjson}' | base64 -d
> 주의: .dockerconfigjson 키는 쉘에서 이스케이프가 필요할 수 있습니다. 위 예시는 일부 환경에서 깨질 수 있으니, 필요하면 kubectl get secret -o yaml로 확인하세요.
3-2) EKS + ECR에서 흔한 인증 실패 포인트
ECR은 기본적으로 단기 토큰 기반입니다. 과거에는 노드에서 docker login을 수동으로 해놓고 “되던 게 어느 날부터 안 됨”이 자주 발생했는데, 토큰 만료로 재현됩니다.
현재 권장 경로는 노드가 ECR에서 토큰을 자동으로 받아오는 구성(컨테이너 런타임/credential provider)입니다. 다음을 확인하세요.
- 노드 인스턴스 프로파일(IAM Role)에
ecr:GetAuthorizationToken,ecr:BatchGetImage,ecr:GetDownloadUrlForLayer등이 있는지 - ECR 리포지토리 정책이 노드 Role을 허용하는지(특히 cross-account)
노드에서 직접 확인(접속 가능할 때):
# 노드에서
aws sts get-caller-identity
aws ecr get-authorization-token --region <region> > /dev/null
만약 Pod가 AWS API 호출은 되는데 특정 엔드포인트/정책에서 막히는 패턴이라면, VPC 엔드포인트 정책/라우팅도 같이 보세요. S3 사례지만 “DNS는 되는데 실제 접근이 막힘”의 진단 프레임은 동일합니다: EKS Pod DNS는 되는데 S3만 503? 엔드포인트 정책
3-3) 사설 레지스트리(자체 Harbor 등) 인증
사설 레지스트리는 대개 Basic Auth 또는 Robot Account 토큰을 씁니다.
kubectl create secret docker-registry regcred \
--docker-server=<registry.example.com> \
--docker-username=<user> \
--docker-password='<token-or-password>' \
-n <ns>
# Pod spec
# imagePullSecrets:
# - name: regcred
여기서 흔한 실수는 --docker-server에 프로토콜(https://) 을 포함하거나, 실제 이미지 host와 다른 값을 넣는 것입니다. docker login <host>에서 쓰는 host와 정확히 일치해야 합니다.
4) 네트워크/정책: “노드가 레지스트리에 닿는가?”
이미지 pull은 Pod 네트워크가 아니라 노드 런타임이 수행합니다. 즉, Pod 안에서 curl이 된다고 해서 pull이 되는 게 아닐 수 있습니다.
4-1) 노드 레벨 egress/프록시/보안그룹
- 프라이빗 서브넷 노드라면 NAT/IGW 경로가 있는지
- 사내 프록시가 필요한데 containerd가 프록시 환경변수를 못 받는지
- 보안그룹/네트워크 ACL에서 443 egress가 막혔는지
노드에서 레지스트리로 TLS 핸드셰이크만 확인:
# 노드에서
curl -Iv https://<registry-host>/v2/ 2>&1 | head -n 20
ECR의 경우 리전별 도메인으로 나가야 하며, PrivateLink(VPC Endpoint) 사용 시에는 ECR API(dkr/api) 엔드포인트가 모두 구성되어야 합니다.
4-2) NetworkPolicy는 보통 원인이 아니다(하지만 예외 있음)
일반적인 Kubernetes NetworkPolicy는 Pod 간 통신을 제어하며, 이미지 pull은 노드에서 나가므로 직접 원인이 아닙니다. 다만 CNI/노드 방화벽 정책(예: Calico의 host endpoint, eBPF 정책)까지 건드린 경우에는 노드 egress가 막힐 수 있습니다.
네트워크 정책 적용 후 통신이 끊기는 복구 패턴은 EKS Calico NetworkPolicy 적용 후 통신 끊김 복구에서 다룬 접근(정책 범위/우선순위/host endpoint 확인)이 유용합니다.
5) 캐시/런타임 진단: “이미지는 있는데 왜 못 쓰지?”
인증/네트워크가 정상인데도 ImagePullBackOff가 반복되면, 노드 런타임(containerd) 상태나 이미지 캐시/스토리지 문제를 의심합니다.
5-1) imagePullPolicy와 태그 캐시
imagePullPolicy: IfNotPresent+:latest같은 태그 조합은 문제를 숨깁니다.- 같은 태그를 덮어쓰기(push)하면 노드에 남은 캐시와 기대 이미지가 달라질 수 있습니다.
권장:
- 운영에서는 태그 불변(예:
1.2.3) 또는 digest pinning - 혹은 배포 시점에만
imagePullPolicy: Always
containers:
- name: app
image: <repo>:1.2.3
imagePullPolicy: IfNotPresent
디버깅 목적이라면 일시적으로:
imagePullPolicy: Always
5-2) 노드 디스크 부족 / 이미지 GC 실패
no space left on device는 이벤트에 직접 나오기도 하지만, 간접적으로 pull 실패로 보일 수도 있습니다.
노드에서 확인:
df -h
sudo du -sh /var/lib/containerd
sudo journalctl -u containerd -n 200 --no-pager
containerd 이미지 목록/정리(노드):
sudo crictl images
sudo crictl rmi --prune
> 운영 노드에서 정리는 주의가 필요합니다. 무작정 prune하면 재시작 시 대량 pull로 오히려 장애가 커질 수 있어, 스케일아웃/드레인 전략과 함께 진행하세요.
5-3) 이미지 레이어 손상/부분 다운로드
네트워크가 불안정하거나 노드가 갑자기 재부팅되면 레이어가 깨질 수 있습니다. 동일 노드에서만 실패한다면 노드 단위 문제일 가능성이 큽니다.
- 특정 노드에 스케줄된 Pod만 실패하는지 확인
kubectl get pod -n <ns> -o wide
# NODE 컬럼에서 특정 노드에 몰려 실패하는지
문제 노드 격리:
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
6) 재현 가능한 “원인 좁히기” 루틴(체크리스트)
현장에서 가장 빠른 루틴은 아래 순서입니다.
- Pod 이벤트 확인: unauthorized / timeout / not found / x509 중 무엇인지 분류
- 이미지 문자열 검증: registry host, repo, tag/digest, 아키텍처
- 인증 경로 확정
- imagePullSecret이면: Secret 존재/연결/내용/레지스트리 host 일치
- ECR이면: 노드 IAM 권한 + (cross-account면) repo policy
- 노드에서 레지스트리 연결 확인:
curl -Iv https://host/v2/ - 노드 런타임/디스크 확인:
journalctl -u containerd,df -h,crictl images - 노드 편향 확인: 특정 노드에서만 실패하면 cordon/drain 후 원인 분석
7) 실전 예시: ECR + 잘못된 인증으로 ImagePullBackOff
ECR private repo를 당겨야 하는데 노드 Role에 권한이 없으면 이벤트는 보통 이렇게 나옵니다.
Failed to pull image "...dkr.ecr.../myapp:1.0.0":
rpc error: code = Unknown desc = failed to pull and unpack image:
failed to resolve reference ...: unexpected status from HEAD request ...: 403 Forbidden
해결은 다음 중 하나입니다.
- 노드 IAM Role(또는 EKS managed node group role)에 ECR read 권한 추가
- cross-account면 ECR repository policy에 노드 Role principal 허용 추가
- PrivateLink 사용 시 ECR API/DKR 엔드포인트 및 정책 확인
권한이 토큰/세션 만료 성격이라면(예: 배포 파이프라인에서 임시 자격증명 사용), STS 만료 이슈도 함께 점검해야 합니다. 앱 런타임에서의 케이스지만 “만료된 자격증명으로 실패”라는 축은 동일합니다: EKS Pod에서 STS 403 ExpiredToken 해결법
8) 마무리: ‘재시작’보다 ‘근거 있는 분류’가 먼저
ImagePullBackOff는 증상이 단순해 보이지만, 실제로는 인증/권한, 네트워크 경로, 노드 런타임/캐시가 얽혀 있습니다. 가장 중요한 건 이벤트를 통해 실패 유형을 먼저 분류하고, 이미지 레퍼런스와 인증 경로를 확정한 뒤, 마지막에 노드 캐시/디스크/런타임을 보는 것입니다.
이 루틴을 팀의 온콜 런북으로 고정해두면 “어제는 됐는데 오늘은 안 됨”류의 장애를 훨씬 빠르게 수습할 수 있습니다.