배포를 했는데 Pod가 Running이 되지 않습니다.
kubectl get pods를 치면 STATUS가ImagePullBackOff입니다. 이미지 이름은 맞는 것 같은데 왜 Pull이 안 될까요? ImagePullBackOff는 원인이 다양하고, Events 메시지를 정확히 읽어야 원인을 좁힐 수 있습니다.
핵심 요약
| 원인 | Events 메시지 키워드 | 확인 방법 |
|---|---|---|
| 이미지 이름/태그 오류 | manifest unknown, not found |
이미지 이름, 태그, 레지스트리 URL 확인 |
| 레지스트리 인증 실패 | unauthorized, denied, no basic auth credentials |
imagePullSecrets 설정 확인 |
| Private 레지스트리 네트워크 차단 | i/o timeout, connection refused |
Node에서 레지스트리 접근 가능 여부 확인 |
| Docker Hub Rate Limit | toomanyrequests, 429 |
Pull 횟수 확인, 인증 또는 미러 설정 |
| Node 디스크 부족 | no space left on device |
Node 디스크 사용량 확인 |
| 잘못된 imagePullPolicy | 로컬에만 있는 이미지를 Pull 시도 | imagePullPolicy 설정 확인 |
1. ImagePullBackOff란
Pod가 시작되려면 컨테이너 이미지를 Node로 다운로드(Pull)해야 합니다. 이 Pull이 실패하면 Kubernetes는 먼저 ErrImagePull 상태를 표시하고, 이후 재시도 간격을 점점 늘리면서(exponential backoff) ImagePullBackOff 상태로 전환합니다.
ErrImagePull과 ImagePullBackOff의 관계:
ErrImagePull: 이미지 Pull 시도가 실패한 직후 상태ImagePullBackOff: Pull 실패 후 재시도 대기 중인 상태 (10초 → 20초 → 40초 → ... 최대 5분 간격)
핵심 포인트: ImagePullBackOff 자체는 에러가 아니라 "재시도 대기 중"이라는 의미입니다. 실제 원인은 Events에 기록된 에러 메시지에서 확인해야 합니다.
2. 진단 시작: Events 메시지 확인
ImagePullBackOff가 발생하면 가장 먼저 해야 할 일은 Pod의 Events를 확인하는 것입니다.
# Pod Events 확인 (가장 중요한 진단 명령어)
kubectl describe pod <pod-name> -n <namespace>
출력에서 확인할 부분:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 30s default-scheduler Successfully assigned default/my-app-xyz to node-1
Normal Pulling 28s kubelet Pulling image "my-registry.com/my-app:v1.2.3"
Warning Failed 25s kubelet Failed to pull image "my-registry.com/my-app:v1.2.3":
rpc error: code = Unknown desc = Error response from daemon:
manifest for my-registry.com/my-app:v1.2.3 not found
Warning Failed 25s kubelet Error: ErrImagePull
Normal BackOff 10s kubelet Back-off pulling image "my-registry.com/my-app:v1.2.3"
Warning Failed 10s kubelet Error: ImagePullBackOff
Events의 Failed to pull image 메시지가 실제 원인을 알려줍니다. 이 메시지의 키워드로 아래 원인별 섹션을 참고합니다.
3. 원인별 분석
3-1. 이미지 이름 또는 태그 오류
가장 흔한 원인입니다. 이미지 이름에 오타가 있거나, 존재하지 않는 태그를 지정한 경우입니다.
Events 메시지 예시:
Failed to pull image "nginx:v1.25": manifest for docker.io/library/nginx:v1.25 not found
Failed to pull image "my-registry.com/myapp:latest": manifest unknown
실무 시나리오:
CI/CD 파이프라인에서 이미지를 빌드할 때 태그를 git commit SHA로 지정합니다. Deployment YAML에는 v1.2.3 태그를 적었는데, 실제 레지스트리에는 abc1234 SHA 태그로만 Push되어 있습니다. 이름은 맞지만 태그가 없어서 Pull이 실패합니다.
확인 방법:
# 1. Pod에 설정된 이미지 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].image}'
# 2. 레지스트리에서 해당 이미지/태그 존재 여부 확인
# Docker Hub
docker manifest inspect nginx:v1.25
# ECR
aws ecr describe-images \
--repository-name my-app \
--image-ids imageTag=v1.2.3
# GCR / Artifact Registry
gcloud artifacts docker images list \
us-docker.pkg.dev/my-project/my-repo/my-app --include-tags
해결:
# Deployment에서 이미지 태그 수정
spec:
containers:
- name: my-app
image: my-registry.com/my-app:v1.2.3 # 실제 존재하는 태그로 변경
흔한 실수 체크리스트:
| 실수 | 예시 |
|---|---|
| 레지스트리 URL 누락 | my-app:v1 → my-registry.com/my-app:v1 |
| 네임스페이스/프로젝트 누락 | my-app:v1 → my-registry.com/team-a/my-app:v1 |
| 태그 오타 | v1.2.3 vs 1.2.3 vs latest |
| 대소문자 | MyApp vs myapp (레지스트리는 소문자만 허용) |
| 아키텍처 불일치 | ARM 노드에서 AMD64 이미지 Pull 시도 |
3-2. 레지스트리 인증 실패
Private 레지스트리에서 이미지를 Pull하려면 인증 정보가 필요합니다. imagePullSecrets가 설정되지 않았거나, Secret의 인증 정보가 만료된 경우입니다.
Events 메시지 예시:
Failed to pull image "123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:v1":
unauthorized: authentication required
Failed to pull image "ghcr.io/my-org/my-app:v1":
denied: permission denied
실무 시나리오:
EKS 클러스터에서 ECR 이미지를 Pull합니다. 처음에는 잘 되다가 12시간 후부터 새로 생성되는 Pod에서 ImagePullBackOff가 발생합니다. ECR 인증 토큰의 유효 기간이 12시간이기 때문입니다. 기존 Node에 캐시된 이미지는 사용 가능하지만, 새 Node나 이미지 업데이트 시 Pull이 실패합니다.
확인 방법:
# 1. Pod에 imagePullSecrets가 설정되어 있는지 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.imagePullSecrets}'
# 2. Secret이 존재하는지 확인
kubectl get secret <secret-name> -n <namespace>
# 3. Secret 내용 디코딩하여 레지스트리 URL 확인
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d
해결 — 일반 Private 레지스트리:
# Docker Registry Secret 생성
kubectl create secret docker-registry my-registry-secret \
--docker-server=my-registry.com \
--docker-username=my-user \
--docker-password=my-password \
--docker-email=my-email@example.com \
-n my-namespace
# Pod 또는 Deployment에 imagePullSecrets 추가
spec:
imagePullSecrets:
- name: my-registry-secret
containers:
- name: my-app
image: my-registry.com/my-app:v1.2.3
해결 — AWS ECR (EKS 환경):
EKS에서 ECR을 사용하는 경우, Node의 IAM Role에 ECR Pull 권한을 부여하는 것이 운영 관점에서 적합합니다. 토큰 갱신을 신경 쓸 필요가 없습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "*"
}
]
}
ServiceAccount에 imagePullSecrets를 설정하면 해당 ServiceAccount를 사용하는 모든 Pod에 자동 적용됩니다. Pod마다 개별 설정하는 것보다 관리가 편합니다.
# ServiceAccount에 imagePullSecrets 패치
kubectl patch serviceaccount default \
-n my-namespace \
-p '{"imagePullSecrets": [{"name": "my-registry-secret"}]}'
3-3. 네트워크 문제 (레지스트리 접근 불가)
Node에서 컨테이너 레지스트리로의 네트워크 연결이 차단된 경우입니다. Private Subnet에 있는 Node가 NAT Gateway 없이 외부 레지스트리에 접근하려 하거나, 방화벽/프록시가 차단하는 경우에 발생합니다.
Events 메시지 예시:
Failed to pull image "docker.io/library/nginx:1.25":
dial tcp 104.18.123.25:443: i/o timeout
Failed to pull image "ghcr.io/my-org/my-app:v1":
dial tcp: lookup ghcr.io: no such host
실무 시나리오:
EKS 클러스터의 Worker Node가 Private Subnet에 있습니다. NAT Gateway를 통해 외부 인터넷에 접근하는 구조인데, NAT Gateway의 Elastic IP가 변경되면서 회사 방화벽의 허용 목록에서 빠졌습니다. 기존에 캐시된 이미지로 동작하던 Pod는 정상이지만, 새로 스케줄링되는 Pod는 이미지를 Pull할 수 없어 ImagePullBackOff가 발생합니다.
확인 방법:
# 1. Node에 SSH 접속 후 레지스트리 연결 테스트
# (EKS의 경우 SSM Session Manager 사용)
curl -v https://123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/v2/
# 2. DNS 확인
nslookup docker.io
nslookup 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com
# 3. 포트 연결 확인
nc -zv docker.io 443
nc -zv 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com 443
해결:
| 환경 | 해결 방법 |
|---|---|
| Private Subnet + 외부 레지스트리 | NAT Gateway 설정 확인, 라우팅 테이블 점검 |
| Private Subnet + ECR | VPC Endpoint(com.amazonaws.region.ecr.dkr, ecr.api, s3) 설정 |
| 프록시 환경 | containerd/Docker에 HTTP_PROXY 설정 |
| 방화벽 | 레지스트리 도메인/IP 허용 |
ECR VPC Endpoint를 사용할 때는 ecr.dkr, ecr.api, s3 세 개의 Endpoint가 모두 필요합니다. S3 Endpoint가 빠지면 이미지 레이어 다운로드가 실패합니다.
3-4. Docker Hub Rate Limit
Docker Hub는 익명 사용자에게 6시간당 100회, 무료 인증 사용자에게 6시간당 200회의 Pull 제한을 적용합니다. 클러스터 내 여러 Node가 같은 Public IP를 공유하면(NAT Gateway 뒤) 제한에 빠르게 도달할 수 있습니다.
Events 메시지 예시:
Failed to pull image "docker.io/library/nginx:1.25":
toomanyrequests: You have reached your pull rate limit.
You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
확인 방법:
# 남은 Pull 횟수 확인 (Node에서 실행)
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nginx:pull" | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" -I "https://registry-1.docker.io/v2/library/nginx/manifests/latest" | grep -i ratelimit
출력 예시:
ratelimit-limit: 100;w=21600
ratelimit-remaining: 0;w=21600
해결:
# 1. Docker Hub 인증 Secret 생성 (무료 계정이라도 200회로 증가)
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=my-dockerhub-user \
--docker-password=my-dockerhub-token \
-n my-namespace
# 2. 장기 해결: 자체 레지스트리 미러 또는 클라우드 레지스트리 사용
# ECR Pull-through Cache 설정 예시
# ECR이 Docker Hub를 캐시하여 Rate Limit 회피
운영 환경 권장 전략:
| 전략 | 설명 |
|---|---|
| ECR Pull-through Cache | ECR이 Docker Hub 이미지를 캐시, Rate Limit 회피 |
| 자체 미러 레지스트리 | Harbor 등으로 자체 미러 운영 |
| 이미지 사전 Pull | DaemonSet으로 필요한 이미지를 미리 Node에 캐시 |
| 유료 Docker Hub 계정 | Team 플랜 이상에서 무제한 Pull |
3-5. Node 디스크 부족
Node의 디스크 공간이 부족하면 이미지 레이어를 저장할 수 없어 Pull이 실패합니다.
Events 메시지 예시:
Failed to pull image "my-app:v1":
write /var/lib/containerd/...: no space left on device
확인 방법:
# Node 디스크 사용량 확인
kubectl describe node <node-name> | grep -A 5 "Conditions"
# 출력에서 DiskPressure 확인
# DiskPressure True ... kubelet has disk pressure
# Node에 직접 접속하여 확인
df -h /var/lib/containerd
# 또는
df -h /var/lib/docker
# 사용하지 않는 이미지 정리
crictl rmi --prune
해결:
- 사용하지 않는 이미지 정리 (
crictl rmi --prune) - Node의 디스크 크기 증가 (EBS 볼륨 확장 등)
- kubelet의
imageGCHighThresholdPercent설정 조정 (기본 85%) - 이미지 크기 최적화 (multi-stage build, alpine 기반 이미지 사용)
3-6. imagePullPolicy 설정 문제
imagePullPolicy가 Always인데 로컬에만 이미지가 있거나, Never인데 Node에 이미지가 캐시되어 있지 않은 경우입니다.
imagePullPolicy 동작:
| 값 | 동작 | 사용 시점 |
|---|---|---|
Always |
매번 레지스트리에서 Pull 시도 | 태그가 latest이거나 태그 미지정 시 기본값 |
IfNotPresent |
Node에 없을 때만 Pull | 특정 버전 태그 사용 시 기본값 |
Never |
Pull하지 않음, 로컬 이미지만 사용 | 로컬 개발/테스트 환경 |
실무 시나리오:
개발 환경에서 minikube로 로컬 이미지를 테스트할 때 imagePullPolicy: Never로 설정했습니다. 이 YAML을 그대로 스테이징 클러스터에 배포하면, Node에 해당 이미지가 없어 ErrImageNeverPull 에러가 발생합니다.
확인 방법:
# Pod의 imagePullPolicy 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].imagePullPolicy}'
해결:
spec:
containers:
- name: my-app
image: my-app:v1.2.3
imagePullPolicy: IfNotPresent # 운영 환경 권장
운영 환경에서는
latest 태그 대신 구체적인 버전 태그를 사용하고, imagePullPolicy: IfNotPresent를 설정하는 것이 권장됩니다. 불필요한 Pull을 줄이고, 배포 재현성을 보장할 수 있습니다.4. 진단 흐름
ImagePullBackOff가 발생했을 때 아래 순서로 원인을 좁혀갑니다.
Step 1: Events 메시지 확인
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Events"
not found/manifest unknown→ 3-1 (이미지/태그 오류)unauthorized/denied→ 3-2 (인증 실패)i/o timeout/connection refused→ 3-3 (네트워크)toomanyrequests→ 3-4 (Rate Limit)no space left→ 3-5 (디스크 부족)
Step 2: 이미지 존재 여부 확인
# 레지스트리에서 직접 확인
docker manifest inspect <image>:<tag>
# 또는
crane manifest <image>:<tag>
Step 3: Node에서 Pull 테스트
# Node에 접속하여 직접 Pull 시도
crictl pull <image>:<tag>
Step 4: 네트워크/인증 확인
# Node에서 레지스트리 접근 테스트
curl -v https://<registry-url>/v2/
5. 실무 시나리오: EKS에서 ECR 이미지 Pull 실패
상황: EKS 클러스터에 새 Deployment를 배포했는데, 일부 Pod만 ImagePullBackOff가 발생합니다. 같은 Deployment인데 Node에 따라 성공/실패가 갈립니다.
진단 과정:
# 1. Events 확인
kubectl describe pod my-app-abc123 -n production
# → "unauthorized: authentication required"
# 2. 성공하는 Pod와 실패하는 Pod의 Node 비교
kubectl get pods -o wide -n production | grep my-app
# my-app-abc123 ImagePullBackOff node-group-new-1
# my-app-def456 Running node-group-old-1
# 3. Node의 IAM Role 확인
aws eks describe-nodegroup --cluster-name my-cluster --nodegroup-name node-group-new
# → 새 Node Group의 IAM Role에 ECR 권한이 없음
원인: 새로 추가한 Node Group의 IAM Role에 AmazonEC2ContainerRegistryReadOnly 정책이 연결되지 않았습니다. 기존 Node Group에는 정책이 있어서 정상 동작했습니다.
해결:
# Node Group IAM Role에 ECR 읽기 권한 추가
aws iam attach-role-policy \
--role-name eks-node-group-new-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
6. 재발 방지 체크리스트
| 항목 | 권장 설정 | 이유 |
|---|---|---|
| 이미지 태그 | 구체적 버전 사용 (v1.2.3) |
latest는 재현성 없음, 의도치 않은 변경 위험 |
| imagePullPolicy | IfNotPresent (버전 태그 사용 시) |
불필요한 Pull 감소, Rate Limit 회피 |
| imagePullSecrets | ServiceAccount에 설정 | Pod마다 개별 설정보다 관리 용이 |
| ECR 권한 (EKS) | Node IAM Role에 ECR 정책 연결 | 토큰 갱신 불필요, 운영 안정성 |
| Docker Hub 의존 | Pull-through Cache 또는 미러 사용 | Rate Limit 회피, Pull 속도 개선 |
| Node 디스크 | imageGC 임계값 모니터링 | 디스크 부족으로 인한 Pull 실패 방지 |
| CI/CD 파이프라인 | Push 성공 확인 후 배포 | 존재하지 않는 태그로 배포 방지 |
imagePullSecrets에 사용되는 레지스트리 인증 정보는 Kubernetes Secret으로 저장됩니다. Secret은 기본적으로 etcd에 Base64 인코딩으로만 저장되므로, 운영 환경에서는 etcd 암호화(EncryptionConfiguration) 또는 외부 Secret 관리 도구(Sealed Secrets, External Secrets Operator)를 사용하는 것이 적합합니다.
7. 정리
- ImagePullBackOff는 이미지 Pull 실패 후 재시도 대기 상태입니다. 실제 원인은
kubectl describe pod의 Events에서 확인합니다. - 가장 흔한 원인은 이미지 이름/태그 오류와 레지스트리 인증 실패입니다. Events 메시지의 키워드로 원인을 빠르게 좁힐 수 있습니다.
- EKS + ECR 환경에서는 Node IAM Role에 ECR 권한을 부여하는 것이 토큰 갱신 없이 안정적으로 운영할 수 있는 방법입니다.
- 운영 환경에서는 Docker Hub 직접 의존을 줄이고, Pull-through Cache나 자체 미러를 사용하여 Rate Limit과 가용성 문제를 회피합니다.
latest태그 대신 구체적 버전 태그를 사용하면 배포 재현성과 디버깅 편의성이 모두 향상됩니다.
참고 문서
'Troubleshooting' 카테고리의 다른 글
| EKS Pod가 외부 인터넷에 접근하지 못하는 경우 (0) | 2026.06.06 |
|---|---|
| Terraform Error acquiring the state lock 해결 방법 (0) | 2026.06.05 |
| Kubernetes CrashLoopBackOff 원인과 해결 방법 (0) | 2026.06.05 |
| S3 AccessDenied 원인과 해결 방법: Bucket Policy, IAM, KMS, VPC Endpoint까지 (0) | 2026.06.01 |
| ALB 502 Bad Gateway 원인 분석: Target Group, Health Check, 타임아웃까지 (0) | 2026.05.31 |