- Published on
K8s ImagePullBackOff - IRSA·ECR 권한 5분 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
배포는 성공한 것처럼 보이는데 Pod가 ImagePullBackOff로 멈추면, 대부분은 이미지 레지스트리 인증/권한 문제입니다. 특히 EKS에서는 IRSA(IAM Roles for Service Accounts) 를 쓰는 순간, “노드 IAM 역할이면 되겠지”라는 가정이 깨지면서 ECR Pull이 막히는 경우가 흔합니다.
이 글은 “왜 안 되지?”를 길게 파지 않고, 5분 안에 원인 분기가 되도록 구성했습니다. 핵심은 3가지입니다.
- 이벤트에서 정확한 실패 메시지를 먼저 확인
- ECR Pull 경로가 노드 역할인지, IRSA 역할인지 구분
- 필요한 IAM 권한과 SA 어노테이션, OIDC 조건을 최소 셋으로 맞추기
네트워크/보안그룹 이슈로 Pull이 실패하는 경우도 있으니, 노드 통신 자체가 의심되면 EKS에서 NodePort만 안 열릴 때 CNI·SG 점검처럼 CNI/SG/라우팅도 함께 점검하세요.
1) 30초 진단: 이벤트에서 답이 나온다
먼저 Pod 이벤트를 봅니다. 여기서 에러 문구가 거의 확정 진단입니다.
kubectl -n <NAMESPACE> describe pod <POD_NAME>
자주 보는 패턴은 아래처럼 갈립니다.
no basic auth credentials
레지스트리 인증 정보가 전혀 없거나, ImagePullSecret이 없거나, ECR 로그인 토큰을 못 얻는 상태pull access denied/denied: User ... is not authorized to perform: ecr:BatchGetImage
IAM 권한 부족repository does not exist
레포 이름/리전/계정이 틀렸거나, 크로스 계정인데 정책이 없음i/o timeout/dial tcp ... timeout
네트워크(프라이빗 서브넷에서 NAT/VPCE 없음 등)
EKS에서 ECR을 쓰는 일반 케이스라면, 대개는 다음 두 줄 중 하나로 수렴합니다.
failed to authorize: rpc error: code = Unknown desc = ... ecr:GetAuthorizationToken ... AccessDeniedExceptionnot authorized to perform: ecr:BatchGetImage
이제 “누가 ECR 권한을 가져야 하는가”를 결정해야 합니다.
2) 가장 흔한 오해: IRSA는 ‘Pod의 AWS 권한’이고, 이미지 Pull은 ‘노드의 권한’이다
중요한 사실부터 정리합니다.
- 컨테이너 이미지 Pull은 kubelet(노드)에서 수행합니다.
- 즉, 기본적으로 ECR Pull 권한은 노드 IAM Role(인스턴스 프로파일) 에 있어야 합니다.
- IRSA는 Pod 안의 애플리케이션이 AWS API를 호출할 때 쓰는 권한이며, 이미지 Pull 자체에는 직접 적용되지 않는 경우가 대부분입니다.
그런데도 “IRSA·ECR 권한” 조합으로 ImagePullBackOff가 터지는 이유는 다음 중 하나입니다.
- 노드 역할에 ECR Pull 권한이 없는데 IRSA만 설정해둠
- Fargate, 또는 특정 런타임/구성에서 이미지 Pull 경로가 별도 권한을 요구하는데 정책이 미스매치
- 크로스 계정 ECR을 쓰면서 레포 리소스 정책이 없거나, IRSA/노드 역할 어느 쪽에도 권한이 완성되지 않음
따라서 먼저 노드 역할부터 확인하는 게 가장 빠릅니다.
3) 1분 체크: 노드 IAM Role에 ECR Pull 권한이 있는가
EKS Managed Node Group이라면 노드 역할에 보통 아래 정책이 붙습니다.
AmazonEC2ContainerRegistryReadOnly
확인은 AWS 콘솔에서 NodeGroup의 IAM Role을 보거나, CLI로도 가능합니다.
aws eks describe-nodegroup \
--cluster-name <CLUSTER_NAME> \
--nodegroup-name <NODEGROUP_NAME> \
--query 'nodegroup.nodeRole' \
--output text
출력된 Role에 ECR ReadOnly가 없다면 붙입니다.
aws iam attach-role-policy \
--role-name <NODE_ROLE_NAME> \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
이 한 방으로 끝나는 경우가 상당히 많습니다.
최소 권한 정책(커스텀) 예시
조직 정책상 AWS Managed Policy를 못 붙이면 아래 액션이 최소 셋입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
}
]
}
ecr:GetAuthorizationToken은 Resource를 *로 두는 게 일반적입니다(권한 모델 특성).
4) IRSA를 쓰는 서비스라면: ServiceAccount 어노테이션부터 확인
이미지 Pull은 노드가 하지만, 운영 중에는 “IRSA도 같이 손봤는데 왜 안 되지?”가 자주 나옵니다. 이때 IRSA 자체가 깨져 있으면, 애플리케이션이 ECR 외 다른 AWS 호출에서 실패하고, 장애가 겹쳐 보일 수 있습니다. 그래서 IRSA도 함께 2분만 점검해두면 좋습니다.
4-1) ServiceAccount에 Role ARN이 붙었는지
kubectl -n <NAMESPACE> get sa <SERVICEACCOUNT> -o yaml
아래 어노테이션이 있어야 합니다.
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
YAML 예시는 다음과 같습니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/app-irsa-role
그리고 Deployment가 해당 SA를 쓰는지 확인합니다.
kubectl -n <NAMESPACE> get deploy <DEPLOY> -o jsonpath='{.spec.template.spec.serviceAccountName}'
4-2) OIDC Provider가 클러스터에 연결되어 있는지
IRSA는 EKS OIDC Provider가 필요합니다.
aws eks describe-cluster \
--name <CLUSTER_NAME> \
--query 'cluster.identity.oidc.issuer' \
--output text
해당 issuer가 IAM의 OIDC Provider로 등록되어 있어야 합니다. 없다면 eksctl utils associate-iam-oidc-provider로 연결합니다.
eksctl utils associate-iam-oidc-provider \
--cluster <CLUSTER_NAME> \
--approve
4-3) Trust Policy 조건이 sub와 aud를 정확히 가리키는지
IRSA Role의 Trust Policy에서 가장 자주 틀리는 지점이 sub입니다. 네임스페이스/SA 이름 하나만 달라도 AssumeRoleWithWebIdentity가 실패합니다.
예시(핵심만):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:default:app-sa"
}
}
}
]
}
여기서 system:serviceaccount:<NAMESPACE>:<SERVICEACCOUNT>가 정확해야 합니다.
5) 크로스 계정 ECR이면 90%는 “레포 리소스 정책” 문제다
같은 계정의 ECR은 노드 역할에 ReadOnly만 있어도 대개 됩니다. 하지만 다른 AWS 계정의 ECR을 Pull하면, 다음 두 조건이 모두 필요합니다.
- Pull하는 쪽(클러스터 계정)의 IAM Role에 ECR Pull 권한
- 이미지를 가진 쪽(레포 계정)의 ECR Repository Policy에서 Pull 역할을 허용
레포 정책 예시(레포 계정에서 설정):
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/<NODE_OR_IRSA_ROLE>"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
}
]
}
여기서 Principal을 “클러스터 노드 역할”로 줄지, “특정 IRSA 역할”로 줄지는 운영 모델에 따라 다릅니다. 다만 앞서 말했듯 이미지 Pull 주체가 노드인 케이스가 많으므로, 노드 역할을 Principal에 넣는 쪽이 현실적으로 단순합니다.
6) 프라이빗 서브넷에서 ImagePullBackOff면 NAT 또는 VPC 엔드포인트를 의심
에러가 i/o timeout 계열이면 권한보다 네트워크입니다.
- 노드가 프라이빗 서브넷인데 NAT Gateway가 없음
- ECR API/DKR 엔드포인트로 나가는 라우팅이 막힘
- VPC 엔드포인트를 만들었지만 SG/NACL이 막음
ECR은 보통 아래 엔드포인트가 필요합니다.
com.amazonaws.<region>.ecr.apicom.amazonaws.<region>.ecr.dkr- 그리고 이미지 레이어는 S3를 타므로
com.amazonaws.<region>.s3(Gateway endpoint)도 고려
네트워크가 의심되면, 같은 결의 점검법으로 EKS에서 CoreDNS 정상인데 DNS가 간헐 실패할 때처럼 “정상처럼 보이는데 실제로는 경로가 불안정한” 상황도 같이 확인하는 게 좋습니다.
7) 5분 복구 플레이북(체크리스트)
아래 순서대로 하면 대부분의 ImagePullBackOff는 빠르게 정리됩니다.
7-1) 이벤트 메시지로 분기
kubectl -n <NAMESPACE> describe pod <POD_NAME>
AccessDenied면 IAM/레포 정책no basic auth credentials면 인증 경로(노드 ECR 토큰 권한, imagePullSecrets, 런타임 설정)timeout이면 NAT/VPCE/SG
7-2) 노드 역할에 ECR ReadOnly 부여
aws iam attach-role-policy \
--role-name <NODE_ROLE_NAME> \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
7-3) 크로스 계정이면 레포 정책 추가
레포 계정에서 해당 레포에 BatchGetImage 등 Pull 액션을 허용.
7-4) IRSA는 “애플리케이션 AWS 호출” 관점에서 정상화
kubectl -n <NAMESPACE> get sa <SERVICEACCOUNT> -o yaml
aws eks describe-cluster --name <CLUSTER_NAME> --query 'cluster.identity.oidc.issuer' --output text
Trust Policy의 sub가 system:serviceaccount:<NAMESPACE>:<SERVICEACCOUNT>인지 확인.
8) 재발 방지: 배포 파이프라인에 사전 검증을 넣자
운영에서는 “이미지 태그만 바꿨는데 갑자기 Pull이 안 됨” 같은 일이 생깁니다. 재발 방지를 위해 아래를 추천합니다.
- 클러스터 생성/노드그룹 생성 시점에 노드 역할 ECR Pull 권한을 IaC로 고정
- 크로스 계정 ECR이면 레포 정책을 Terraform 등으로 함께 관리
- 신규 네임스페이스/SA 생성 시 IRSA Trust Policy 템플릿을 표준화
- 프라이빗 클러스터는 NAT 또는 ECR/S3 VPC 엔드포인트 구성을 체크리스트화
애플리케이션 레벨 장애와 인프라 레벨 장애가 겹치면 원인 파악 시간이 폭증합니다. 예를 들어 ALB 504처럼 겉으로는 “서비스가 죽었다”로 보이지만 실제는 경로/설정 문제인 경우가 많습니다. 비슷한 문제 해결 흐름은 EKS ALB Ingress 504인데 Pod는 정상일 때도 참고할 만합니다.
9) 결론
ImagePullBackOff는 무섭게 보이지만, 대부분은 노드 역할 ECR Pull 권한 누락 또는 크로스 계정 ECR 레포 정책 누락으로 끝납니다. IRSA는 이미지 Pull 자체의 만능키가 아니며, “Pod의 AWS 권한”이라는 본래 역할로 분리해서 봐야 진단이 빨라집니다.
다음만 기억하면 5분 내 해결 확률이 크게 올라갑니다.
- 에러는
kubectl describe pod이벤트에 이미 적혀 있다 - 이미지 Pull은 보통 노드가 한다: 노드 IAM Role을 먼저 본다
- 크로스 계정은 레포 정책이 절반이다
timeout이면 권한보다 네트워크다