- Published on
K8s Pod ImagePullBackOff - ECR 403 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Kubernetes에서 ImagePullBackOff는 “이미지 다운로드(풀) 단계에서 실패했다”는 결과이고, 그 하위 원인은 생각보다 다양합니다. 특히 AWS ECR을 쓰는 환경(EKS, self-managed k8s + ECR)에서는 이벤트 로그에 403 Forbidden이 찍히는 케이스가 흔합니다. 문제는 403이 “권한”만 의미하는 게 아니라, 잘못된 리전/레포지토리 엔드포인트, 토큰 갱신 실패, 노드 IAM 역할 미스매치, 프라이빗 서브넷의 ecr/api 접근 불가 등으로도 동일하게 보인다는 점입니다.
이 글에서는 ImagePullBackOff + ECR 403을 10분 내로 원인 분류 → 즉시 조치 → 재발 방지 흐름으로 정리합니다. (유사한 403이라도 STS 토큰 만료 이슈는 별도 케이스가 많아, 필요하면 EKS Pod에서 STS 403 ExpiredToken 해결법도 함께 참고하세요.)
증상 확인: 이벤트에서 “어디서 403이 났는지” 먼저 본다
가장 먼저 해야 할 일은 Pod 이벤트에서 403이 발생한 엔드포인트와 에러 메시지를 확인하는 것입니다.
kubectl describe pod -n <ns> <pod>
# 또는
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 30
자주 보이는 패턴은 다음과 같습니다.
failed to pull image ... rpc error: code = Unknown desc = ... 403 ForbiddenHead "https://<account>.dkr.ecr.<region>.amazonaws.com/...": 403 Forbiddenno basic auth credentials(이건 401/403과 같이 나타나기도 함)denied: User ... is not authorized to perform: ecr:BatchGetImage
여기서 핵심은 ECR 레지스트리(dkr) 호출에서 403인지, 토큰을 받는 ECR API(ecr)에서 막히는지, 혹은 STS/IRSA로 이어지는 인증 체인에서 끊기는지입니다.
원인 1) 노드(또는 실행 주체)에 ECR Pull 권한이 없다
EKS에서 기본적으로 이미지를 당기는 주체는 보통 노드의 IAM Role(Instance Profile) 입니다. Fargate라면 Fargate Pod Execution Role이 주체가 됩니다. 이 역할에 ECR read 권한이 없으면 403이 납니다.
필요한 최소 권한(권장: AWS 관리형 정책)
가장 간단히는 노드 역할에 아래 정책을 붙입니다.
AmazonEC2ContainerRegistryReadOnly
직접 최소 권한을 구성한다면(레포지토리 단위로 제한하려면) 다음 액션이 핵심입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "*"
}
]
}
> 주의: ecr:GetAuthorizationToken은 리소스가 *여야 하는 경우가 많습니다(서비스 특성).
빠른 진단 체크리스트
- EKS Managed Node Group을 쓰면 Node IAM Role에 정책이 붙어 있는지 확인
- self-managed node면 EC2 Instance Profile 확인
aws-authConfigMap 문제로 노드가 다른 역할로 뜨는지(드물지만) 확인
원인 2) ECR 레포지토리 정책(Resource Policy)에서 거부한다
노드 역할에 권한이 있어도, ECR 레포지토리 자체의 policy가 교차 계정/특정 Principal만 허용하도록 설정되어 있으면 403이 납니다. 특히 다음 상황에서 자주 발생합니다.
- 이미지는 A 계정 ECR, 클러스터는 B 계정
- 조직(Organizations) 정책/SCP로 일부 Principal이 제한
- 레포지토리 정책에
aws:PrincipalArn조건이 빡세게 걸림
레포지토리 정책 확인:
aws ecr get-repository-policy \
--repository-name <repo> \
--region <region>
교차 계정 Pull을 허용하려면(예시) 레포지토리 정책에 B계정의 노드 역할을 허용해야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<B_ACCOUNT_ID>:role/<EKS_NODE_ROLE_NAME>"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
}
]
}
여기에 더해 B 계정 노드 역할에는 ecr:GetAuthorizationToken이 있어야 합니다(레포 정책만으로 해결되지 않음).
원인 3) 리전/레포지토리 URL이 틀려서 “다른 곳”에 요청하고 있다
의외로 많습니다. 이미지가 ap-northeast-2에 있는데, 매니페스트는 us-east-1 레지스트리로 가 있거나, 계정 ID가 다른 경우입니다. 이 경우 ECR은 종종 403/404 비슷한 형태로 응답하고, 런타임에서는 그냥 “403 Forbidden”으로 뭉개져 보이기도 합니다.
확인 방법
- Pod spec의 image 문자열을 그대로 확인
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[*].image}'
- 올바른 형태:
<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>
- 실제 레포지토리 존재 확인:
aws ecr describe-repositories --region <region> | jq -r '.repositories[].repositoryName'
aws ecr describe-images --repository-name <repo> --region <region> | head
원인 4) 프라이빗 서브넷/네트워크에서 ECR 엔드포인트 접근이 막혔다
노드가 프라이빗 서브넷에 있고 NAT가 없거나, VPC 엔드포인트 구성이 불완전하면 ECR pull이 실패합니다. 이때도 에러가 403/timeout/connection reset 등으로 다양하게 튈 수 있습니다.
ECR pull은 크게 두 종류의 엔드포인트가 필요합니다.
ecr.api(토큰/메타데이터)ecr.dkr(이미지 레이어 다운로드)- 그리고 레이어는 내부적으로 S3를 쓰므로, 상황에 따라
s3접근도 중요
권장 해결: VPC Interface Endpoint 구성
프라이빗 환경에서 안정적으로 하려면 아래를 구성합니다.
- Interface Endpoint:
com.amazonaws.<region>.ecr.api - Interface Endpoint:
com.amazonaws.<region>.ecr.dkr - Gateway/Interface: S3 (환경에 따라)
그리고 Security Group에서 노드 → 엔드포인트(443) 통신이 열려 있어야 합니다.
> 노드가 늘어나지 않거나(스케줄링은 되는데 새 노드가 안 뜨는) 인프라 쪽 징후가 같이 있다면, 네트워크/노드 측면에서 EKS에서 Karpenter 노드가 안 늘 때 10분 진단도 같이 보면 원인 분리가 빨라집니다.
원인 5) imagePullSecrets를 쓰는 방식에서 토큰이 만료되었다
ECR은 Docker Registry Basic Auth처럼 보이지만, 실제로는 aws ecr get-login-password로 얻는 토큰이 만료(기본 12시간) 됩니다. 만약 다음 방식으로 imagePullSecrets에 ECR 토큰을 “고정 값”으로 넣어두면, 시간이 지나 403/401로 깨집니다.
나쁜 패턴(수동 생성 후 방치)
kubectl create secret docker-registry ecr-secret \
--docker-server=<account>.dkr.ecr.<region>.amazonaws.com \
--docker-username=AWS \
--docker-password="$(aws ecr get-login-password --region <region>)"
이 secret을 Pod/SA에 붙이면 처음엔 되다가 나중에 실패합니다.
권장 패턴
- EKS라면 노드 역할 기반 Pull이 가장 단순하고 안정적
- 꼭
imagePullSecrets를 써야 한다면:- External Secrets Operator + 주기적 갱신
- 혹은 ECR Credential Helper/컨트롤러 계열 사용
원인 6) IRSA를 이미지 Pull에 적용하려고 해서 꼬였다(오해 포인트)
자주 있는 오해: “Pod의 ServiceAccount(IRSA)에 ECR 권한을 줬으니, 그걸로 이미지도 Pull 되겠지?”
대부분의 런타임 구성에서 이미지 Pull은 kubelet/노드가 수행하며, Pod 애플리케이션 컨테이너가 뜨기 전 단계라 IRSA가 적용되지 않습니다. 즉, IRSA로 ECR 권한을 줘도 Pull 단계에는 영향이 없고, 결국 노드 역할이 권한 부족이면 403이 납니다.
예외적으로(환경/런타임/플러그인 구성에 따라) 워크로드 아이덴티티로 풀을 하는 솔루션이 있긴 하지만, 일반적인 EKS 기본 구성에서는 노드 역할을 먼저 의심하는 게 맞습니다.
실전 트러블슈팅: 10분 진단 루틴
아래 순서로 보면 대부분 빠르게 갈라집니다.
1) 이벤트에서 URL/메시지 확보
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
denied: ... not authorized→ IAM/레포 정책 가능성 큼Head https://...ecr.<region>... 403→ 리전/계정/레포/네트워크까지 범위
2) 이미지 문자열이 올바른지(계정/리전/레포/태그)
kubectl get deploy -n <ns> <deploy> -o yaml | yq '.spec.template.spec.containers[].image'
3) 노드 역할에 ECR ReadOnly가 있는지
- EKS 콘솔/CLI에서 NodeGroup의 NodeRole 확인 후 정책 확인
- 가능하면 임시로
AmazonEC2ContainerRegistryReadOnly를 붙여 재현이 사라지는지 확인(원인 분리에 매우 효과적)
4) 교차 계정이면 레포지토리 정책 확인
aws ecr get-repository-policy --repository-name <repo> --region <region>
5) 프라이빗 네트워크면 엔드포인트/NAT 확인
- NAT 없는 프라이빗이면 VPC Endpoint가 거의 필수
ecr.api,ecr.dkr, (필요 시)s3까지 점검
재발 방지 체크리스트
- **노드 역할에
AmazonEC2ContainerRegistryReadOnly**를 기본 탑재 - 교차 계정이면 ECR 레포지토리 정책을 IaC로 관리(Terraform/CloudFormation)
- 프라이빗 서브넷이면 ECR VPC 엔드포인트를 표준 구성으로 포함
imagePullSecrets로 ECR 토큰을 고정하지 말고, 불가피하면 자동 갱신 도입- 운영 중 403이 간헐적으로 나타난다면 STS/자격 증명 만료 계열도 함께 점검: EKS Pod에서 STS 403 ExpiredToken 해결법
(부록) 최소 재현용 예시 매니페스트와 올바른 Pull 구성
Deployment 예시(ECR 이미지)
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: <account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>
imagePullPolicy: IfNotPresent
이 상태에서 imagePullSecrets가 없다면, 노드 역할로 Pull이 되는 구성이어야 정상입니다. 여기서 403이 난다면 1순위는 노드 역할 권한/레포 정책/네트워크입니다.
(참고) imagePullSecrets를 써야 하는 케이스의 ServiceAccount 연결
apiVersion: v1
kind: ServiceAccount
metadata:
name: demo-sa
namespace: default
imagePullSecrets:
- name: ecr-secret
다만 앞서 말했듯 ECR 토큰은 만료되므로, 이 방식은 “갱신 자동화”가 같이 있지 않으면 운영에서 쉽게 깨집니다.
정리하면, ImagePullBackOff + ECR 403은 대개 (1) 노드 역할 ECR 권한, (2) ECR 레포 정책(교차 계정), (3) 리전/URL 오타, (4) 프라이빗 네트워크의 엔드포인트/NAT 누락, (5) 만료된 imagePullSecrets 중 하나입니다. 이벤트 메시지에서 시작해 이 다섯 갈래로 분류하면, 불필요하게 Pod/컨테이너 설정을 만지기 전에 빠르게 원인에 도달할 수 있습니다.