- Published on
EKS Pod ImagePullBackOff - ECR 인증 7단계
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스가 아니라면, 운영 중 한 번쯤은 ImagePullBackOff를 마주칩니다. 특히 EKS에서 프라이빗 ECR 이미지를 당겨올 때는 노드 IAM, IRSA, 네트워크, 레지스트리 엔드포인트, 이미지 태그 등 여러 축이 얽혀 있어 원인 분리가 어렵습니다.
이 글은 EKS Pod가 ImagePullBackOff일 때 ECR 인증을 7단계로 확인하는 루틴을 제공합니다. 목표는 “대충 권한 추가”가 아니라, 어떤 레이어에서 실패하는지를 로그와 명령으로 증명하고 최소 변경으로 복구하는 것입니다.
문제 진단 방식 자체는 다른 장애에도 그대로 적용됩니다. 예를 들어 mTLS 이슈도 원인 레이어를 나눠 접근해야 빨리 끝납니다. 관련해서는 Kubernetes 서비스메시 mTLS 실패 원인 7가지도 참고가 됩니다.
0) 증상 확정: 진짜 ECR 인증 문제인가
먼저 ImagePullBackOff는 “이미지 pull 실패”의 결과일 뿐, 원인은 다양합니다. 아래로 이벤트 메시지를 확보해두면 이후 단계에서 시간을 절약합니다.
kubectl -n <NAMESPACE> describe pod <POD_NAME>
Events에서 자주 보이는 패턴은 다음과 같습니다.
Failed to pull image .../pull access denied/no basic auth credentialsrpc error: code = Unknown desc = failed to resolve reference ...i/o timeout/context deadline exceeded
여기서
no basic auth credentials,pull access denied는 권한·인증 가능성이 큼i/o timeout,context deadline exceeded는 네트워크·엔드포인트 가능성이 큼
이제부터는 “ECR 인증” 관점으로 7단계를 밟되, 중간에 네트워크 징후가 보이면 바로 네트워크 단계로 점프합니다.
1단계: 이미지 레퍼런스 자체가 맞는지 확인
의외로 가장 흔한 실수입니다. 레지스트리, 리포지토리, 태그, 아키텍처가 정확해야 합니다.
kubectl -n <NAMESPACE> get deploy <DEPLOY_NAME> -o yaml | sed -n '1,200p'
확인 포인트:
- 이미지 형식이
ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/REPO:TAG인지 - 태그가 존재하는지 (
latest가정 금지) - 멀티 아키텍처 여부: Graviton 노드인데
amd64단일 이미지면 실패 가능
ECR에서 태그 존재 여부를 바로 확인합니다.
aws ecr describe-images \
--region <REGION> \
--repository-name <REPO> \
--image-ids imageTag=<TAG>
여기서 이미지가 없다면 인증 이전에 배포 파이프라인부터 확인해야 합니다.
2단계: Pod 이벤트에서 “어디까지 갔다가” 실패했는지 읽기
kubectl describe pod의 이벤트는 매우 중요합니다. 아래처럼 정확한 에러 문자열을 기준으로 분기합니다.
no basic auth credentials:- 노드 또는 컨테이너 런타임이 ECR 토큰을 못 얻음
- IRSA가 필요한데 적용 안 됐거나, 노드 IAM에 권한이 없음
denied: User ... is not authorized to perform: ecr:BatchGetImage:- 토큰은 얻었지만 ECR API 권한 부족
i/o timeout:- ECR API 또는 ECR Docker 엔드포인트 네트워크 경로 문제
이벤트가 너무 빨리 사라지거나 롤링 중이면 다음도 함께 봅니다.
kubectl -n <NAMESPACE> get events --sort-by=.lastTimestamp | tail -n 50
3단계: 노드 IAM Role 권한 확인 (가장 기본)
EKS에서 일반적으로 프라이빗 ECR pull은 노드 IAM Role 권한으로 해결됩니다. 특히 imagePullSecrets 없이도 되는 게 EKS의 기본 동작(노드가 ECR 토큰을 받아 런타임에 공급)입니다.
먼저 노드가 어떤 IAM Role을 쓰는지 확인합니다.
kubectl get node <NODE_NAME> -o jsonpath='{.spec.providerID}'
노드가 속한 Managed Node Group 또는 ASG를 통해 IAM Role을 확인한 뒤, 최소 필요 권한이 있는지 봅니다.
필수 액션(대표):
ecr:GetAuthorizationTokenecr:BatchCheckLayerAvailabilityecr:GetDownloadUrlForLayerecr:BatchGetImage
AWS 관리형 정책으로는 보통 AmazonEC2ContainerRegistryReadOnly를 노드 Role에 붙이면 됩니다.
CLI로 정책 시뮬레이션까지 하면 더 확실합니다.
aws iam simulate-principal-policy \
--policy-source-arn <NODE_ROLE_ARN> \
--action-names \
ecr:GetAuthorizationToken \
ecr:BatchGetImage \
ecr:GetDownloadUrlForLayer \
ecr:BatchCheckLayerAvailability \
--resource-arns "*"
결과가 allowed가 아니라면, 노드 Role 권한이 원인입니다.
4단계: IRSA(ServiceAccount)로 pull하려는 설계인지 확인
조직 정책상 “노드 Role은 최소 권한, 워크로드별 IRSA로 ECR 접근”을 구성하기도 합니다. 이 경우 ServiceAccount에 Role ARN이 정확히 붙어 있어야 합니다.
ServiceAccount 확인:
kubectl -n <NAMESPACE> get sa <SA_NAME> -o yaml
아래 어노테이션이 핵심입니다.
eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...
Pod가 실제로 그 ServiceAccount를 쓰는지도 확인합니다.
kubectl -n <NAMESPACE> get pod <POD_NAME> -o jsonpath='{.spec.serviceAccountName}'
IRSA를 쓴다면, 해당 IAM Role에 3단계의 ECR 권한이 있어야 하고, Role Trust Policy에 OIDC 조건이 정확해야 합니다.
Trust Policy에서 자주 틀리는 지점:
sub가system:serviceaccount:<NAMESPACE>:<SA_NAME>와 불일치- OIDC Provider ARN이 클러스터 것과 다름
이 단계에서 중요한 결론:
- “노드 Role로 pull”인지 “IRSA로 pull”인지 하나로 정리해야 합니다.
- 둘 다 어중간하면, 운영 중 특정 노드/특정 파드만 실패하는 형태로 나타납니다.
5단계: 네트워크 경로 확인 (프라이빗 클러스터에서 특히 중요)
인증이 맞아도 네트워크가 막히면 ImagePullBackOff가 납니다. ECR은 크게 두 경로가 필요합니다.
- ECR API 엔드포인트:
api.ecr.<REGION>.amazonaws.com - ECR Docker(레지스트리) 엔드포인트:
dkr.ecr.<REGION>.amazonaws.com
프라이빗 서브넷 노드라면 다음 중 하나가 필요합니다.
- NAT Gateway를 통한 인터넷 egress
- VPC Endpoint(PrivateLink) 구성
VPC Endpoint를 쓴다면 보통 아래가 필요합니다.
- Interface Endpoint:
com.amazonaws.<REGION>.ecr.api - Interface Endpoint:
com.amazonaws.<REGION>.ecr.dkr - Gateway Endpoint:
com.amazonaws.<REGION>.s3(레이어 다운로드 경로가 S3를 타는 경우가 많음)
노드에서 직접 확인하기 어렵다면, 임시 디버그 파드를 띄워 DNS/HTTPS 연결을 확인합니다.
kubectl -n <NAMESPACE> run net-debug \
--image=public.ecr.aws/docker/library/alpine:3.20 \
--restart=Never \
--command -- sh -c "apk add --no-cache curl bind-tools; sleep 3600"
kubectl -n <NAMESPACE> exec -it net-debug -- sh
파드 내부에서:
nslookup api.ecr.<REGION>.amazonaws.com
nslookup <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com
curl -sS -o /dev/null -w "%{http_code}\n" https://api.ecr.<REGION>.amazonaws.com/
DNS가 내부 IP로 해석되는지, HTTPS가 타임아웃 나는지로 네트워크/엔드포인트 문제를 빠르게 분리할 수 있습니다.
6단계: 컨테이너 런타임과 ECR Credential Provider 동작 확인
최근 EKS는 노드 AMI와 런타임 구성에 따라 ECR 인증이 “자동”처럼 보이지만, 실제로는
- kubelet
- containerd
- ECR credential provider
의 조합이 정상 동작해야 합니다.
증상 예:
- 특정 노드에서만
no basic auth credentials - 노드 교체 후 갑자기 전체 pull 실패
이럴 때는 노드 단에서 로그를 확인해야 합니다. SSM으로 노드에 접속 가능하다면 아래를 확인합니다.
sudo journalctl -u kubelet -n 200 --no-pager
sudo journalctl -u containerd -n 200 --no-pager
또한 EKS Optimized AMI를 쓰지 않고 커스텀 AMI를 쓰는 경우, ECR credential provider 설정이 누락될 수 있습니다.
현장에서 자주 하는 복구는 다음 중 하나입니다.
- 노드그룹 AMI를 EKS Optimized로 되돌리기
- Launch Template에서 bootstrap 스크립트/설정 재검토
- 문제 노드 cordon/drain 후 교체
kubectl cordon <NODE_NAME>
kubectl drain <NODE_NAME> --ignore-daemonsets --delete-emptydir-data
“권한은 맞는데 어떤 노드만 계속 실패”라면, 이 단계가 정답인 경우가 많습니다.
7단계: 최종 복구 패턴 3가지 (운영에 안전한 순서)
원인을 찾았으면, 운영 영향이 작은 순서로 복구합니다.
패턴 A: 노드 Role에 ECR ReadOnly 부여 (가장 단순)
- 장점: 설정 단순, EKS 기본 동작과 일치
- 단점: 워크로드별 최소권한을 강하게 요구하는 조직에서는 부적합
적용 후에는 새 pull이 발생하도록 파드를 재시작합니다.
kubectl -n <NAMESPACE> rollout restart deploy <DEPLOY_NAME>
패턴 B: IRSA로 워크로드별 Role 부여 (권장되는 최소권한)
- 장점: 네임스페이스/서비스 단위로 권한 분리
- 단점: 초기 설정 복잡, Trust Policy 실수 빈번
적용 체크리스트:
- ServiceAccount 어노테이션의 Role ARN 정확
- OIDC Provider/Trust Policy의
sub정확 - Role에 ECR 권한 정확
패턴 C: imagePullSecrets로 도커 레지스트리 시크릿 사용 (응급처치)
ECR 토큰을 시크릿으로 넣는 방식은 토큰 만료(기본 12시간) 때문에 운영에선 권장되지 않지만, 원인 분리나 긴급 복구 용도로는 쓸 수 있습니다.
aws ecr get-login-password --region <REGION> | \
kubectl -n <NAMESPACE> create secret docker-registry ecr-pull \
--docker-server=<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com \
--docker-username=AWS \
--docker-password-stdin
그리고 Pod spec에 연결:
spec:
imagePullSecrets:
- name: ecr-pull
이 방식으로 pull이 성공한다면, 네트워크/이미지 문제보다는 “노드 또는 IRSA 인증 경로” 문제가 거의 확정입니다.
운영 팁: 재발 방지 체크리스트
- 노드그룹 변경(AMI, 런타임, 부트스트랩) 시 ECR pull 스모크 테스트를 CI에 추가
- 프라이빗 클러스터라면 VPC Endpoint 3종(
ecr.api,ecr.dkr,s3)을 표준 템플릿화 kubectl describe pod이벤트를 수집해 알람 룰로 만들기
장애 대응은 결국 “레이어 분리”가 핵심입니다. 인증처럼 보이는 문제도 네트워크, 런타임, 리소스 레퍼런스가 섞여 들어옵니다. 이 글의 7단계를 그대로 체크하면, 불필요하게 Role을 과하게 열지 않고도 빠르게 원인과 해법을 좁힐 수 있습니다.
추가로, 쿠버네티스에서 원인 분리형 디버깅이 필요한 다른 사례로는 mTLS 장애가 대표적입니다. 원인 분해 방식은 동일하니 Kubernetes 서비스메시 mTLS 실패 원인 7가지도 함께 읽어두면 실전에 도움이 됩니다.