- Published on
EKS Pod에서 AWS ECR 403 AccessDenied 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 Pod가 기동되며 이미지를 가져오는 단계에서 403 AccessDenied가 발생하면, 표면적으로는 “권한이 없다”로 보이지만 실제 원인은 크게 네 가지 축으로 갈립니다. (1) 누가 ECR에 접근하고 있는가(노드 vs Pod/IRSA), (2) 어떤 API가 막혔는가(GetAuthorizationToken vs BatchGetImage 등), (3) 리포지토리 정책/조직 정책(SCP)에서 차단됐는가, (4) 네트워크/엔드포인트/리전에 의해 다른 계정·리포지토리를 보게 되었는가 입니다.
이 글에서는 EKS에서 흔히 보는 ImagePullBackOff + 이벤트에 찍히는 403을 기준으로, 빠르게 원인을 좁히는 체크리스트와 실전 수정 예시(Terraform/IAM/리포지토리 정책/YAML)를 제공합니다. 401 계열(토큰/인증 실패)과의 구분은 아래 글도 함께 참고하면 좋습니다.
1) 먼저 로그/이벤트에서 “어떤 주체가” 막혔는지 확인
1-1. Pod 이벤트에서 403 메시지 패턴 읽기
다음으로 시작합니다.
kubectl describe pod -n <ns> <pod>
여기서 Failed to pull image 라인에 다음과 같은 힌트가 있습니다.
ecr:GetAuthorizationToken관련 403: ECR 로그인 토큰 발급 단계부터 막힘ecr:BatchGetImage,ecr:GetDownloadUrlForLayer관련 403: 토큰은 받았으나 리포지토리/이미지 접근이 막힘repository does not exist or may require 'docker login': 계정/리전/리포지토리 ARN 불일치 가능
또한 런타임이 containerd라면 노드에서 직접 재현하는 것이 빠릅니다.
# 노드에 접속(SSM/SSH) 후
sudo journalctl -u containerd -n 200 --no-pager | egrep -i "ecr|denied|403|pull"
1-2. EKS에서 이미지 Pull은 기본적으로 “노드 IAM Role”이 한다
매우 중요한 포인트는, 일반적인 EKS 구성에서 kubelet/containerd가 이미지를 pull하는 주체는 Pod의 IRSA가 아니라 노드의 IAM Role(=NodeInstanceRole) 이라는 점입니다.
- IRSA는 “Pod 안에서 AWS SDK 호출”에 주로 적용
- 이미지 pull은 kubelet이 수행 → 노드 자격증명 사용
따라서 “서비스어카운트에 IRSA 붙였는데도 403”이라면, 대부분 노드 Role 권한이 부족한 케이스입니다.
IRSA/자격증명 주체 혼동은 다른 장애(예: SDK NoCredentialProviders)에서도 자주 등장합니다.
2) 원인별 Top 6와 해결책
2-1. (가장 흔함) 노드 IAM Role에 ECR Pull 권한이 없다
증상
- 새 노드그룹을 만들었거나(특히 Terraform/업그레이드)
- 커스텀 노드 Role을 사용하거나
- 기존에 붙어 있던
AmazonEC2ContainerRegistryReadOnly가 빠진 경우
GetAuthorizationToken 또는 BatchGetImage 단계에서 403이 납니다.
해결: 노드 Role에 최소 권한 부여
가장 간단한 방법은 AWS 관리 정책을 붙이는 것입니다.
AmazonEC2ContainerRegistryReadOnly
Terraform 예시:
resource "aws_iam_role_policy_attachment" "node_ecr_readonly" {
role = aws_iam_role.eks_node_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}
직접 정책을 만들고 최소 권한만 주고 싶다면(권장), 아래 권한이 핵심입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:<region>:<account-id>:repository/<repo-name>"
}
]
}
> 팁: GetAuthorizationToken은 Resource가 *여야 하는 케이스가 일반적입니다.
2-2. ECR 리포지토리 정책(Resource policy)에서 노드 Role을 거부
증상
- 같은 계정인데도 특정 리포지토리만 403
- cross-account로 ECR을 공유하는 구조
- 조직(Org)에서 표준 리포지토리 정책을 강제
이 경우 노드 Role에 IAM 권한이 있어도, 리포지토리 정책에서 거부되면 403이 납니다.
해결: 리포지토리 정책에 Principal 추가
예시(다른 계정의 노드 Role이 pull하도록 허용):
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<consumer-account-id>:role/<NodeInstanceRoleName>"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
]
}
]
}
그리고 토큰 발급은 소비자 계정에서 ecr:GetAuthorizationToken이 가능해야 합니다(대개 IAM 정책에서 처리).
2-3. 리전/계정이 달라서 “다른 ECR”을 보고 있다
증상
- 이미지 주소가
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/...인데 - 노드가 다른 리전의 엔드포인트로 나가거나
- 배포 YAML에서 계정 ID를 잘못 넣었거나
- ECR Public/Private 혼동
이때도 결과적으로 403/404 유사 메시지가 섞여 나옵니다.
해결: 이미지 레퍼런스 재검증
배포 YAML의 이미지가 정확한지 확인합니다.
containers:
- name: app
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-repo:2026-02-23
imagePullPolicy: IfNotPresent
CLI로도 확인:
aws ecr describe-repositories --region ap-northeast-2 \
--query "repositories[?repositoryName=='my-repo'].repositoryUri" --output text
2-4. IRSA로 이미지 Pull을 하려고 설계했지만 기본 경로는 그렇지 않다
“Pod가 ECR을 pull할 때 IRSA를 쓰게 만들고 싶다”는 요구가 종종 있는데, 기본 kubelet pull 경로는 노드 Role을 사용합니다.
대안은 다음 중 하나입니다.
- 노드 Role에 ECR pull 권한을 부여(대부분의 표준 해결)
- Private registry credential provider/credential helper를 노드에 구성(운영 난이도 상승)
- ECR 대신 다른 레지스트리/프록시(예: Harbor) 사용
즉, IRSA를 붙였는데도 403이면 “IRSA가 적용되지 않는 경로”인지를 먼저 의심해야 합니다.
2-5. VPC 엔드포인트(ECR API/DKR) 또는 프록시 환경에서 정책이 막힘
프라이빗 서브넷에서 NAT 없이 ECR을 쓰는 경우, 보통 다음 VPC 엔드포인트가 필요합니다.
com.amazonaws.<region>.ecr.apicom.amazonaws.<region>.ecr.dkr- 그리고 이미지 레이어 다운로드를 위해 S3 게이트웨이 엔드포인트 또는 NAT(환경에 따라)
네트워크가 막히면 보통 timeout이 많이 나지만, 엔드포인트 정책이 “특정 Principal/리포지토리만 허용”으로 되어 있으면 403이 날 수 있습니다.
확인 포인트:
- VPC Endpoint policy에 노드 Role principal이 허용되어 있는가
- 엔드포인트가 올바른 VPC/서브넷/라우팅 테이블에 연결돼 있는가
(네트워크 진단 체크리스트는 다른 글에서 다룬 10분 점검 루틴이 유사하게 적용됩니다.)
2-6. SCP(Organizations) 또는 Permission Boundary로 인한 “명시적 거부”
조직 단위로 ecr:* 일부가 제한되어 있거나, 노드 Role에 Permission Boundary가 걸려 있으면 IAM 정책을 붙여도 403이 계속됩니다.
진단 방법:
- CloudTrail에서
AccessDenied이벤트를 찾아explicitDeny여부 확인 - IAM Policy Simulator로 노드 Role의
ecr:GetAuthorizationToken,ecr:BatchGetImage평가
CloudTrail Lake/이벤트 히스토리에서 EventName=GetAuthorizationToken 또는 BatchGetImage를 필터링하면 빠릅니다.
3) 빠른 트러블슈팅 플로우(실전)
3-1. Pod 이벤트로 API 단계 식별
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp | tail -n 30
GetAuthorizationToken403 → 노드 Role에 토큰 권한부터 점검BatchGetImage403 → 리포지토리 정책/리포지토리 ARN/계정 공유 구조 점검
3-2. 노드 Role 확인
노드가 관리형 노드그룹이면 보통 아래로 확인합니다.
aws eks describe-nodegroup \
--cluster-name <cluster> \
--nodegroup-name <ng> \
--query "nodegroup.nodeRole" --output text
해당 Role에 정책이 붙었는지 확인:
aws iam list-attached-role-policies --role-name <NodeRoleName>
3-3. (권장) 최소 권한 정책으로 ECR Pull 보장
운영에서 리포지토리가 여러 개라면 Resource를 리포지토리 ARN 패턴으로 늘리거나, 동일 계정 내에서는 관리 정책을 쓰는 편이 실수가 적습니다.
Terraform로 커스텀 정책을 붙이는 예시:
resource "aws_iam_policy" "node_ecr_pull" {
name = "node-ecr-pull"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
]
Resource = "arn:aws:ecr:ap-northeast-2:123456789012:repository/*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "attach_node_ecr_pull" {
role = aws_iam_role.eks_node_role.name
policy_arn = aws_iam_policy.node_ecr_pull.arn
}
4) 자주 하는 실수 체크리스트
IRSA를 붙였으니 이미지 pull도 되겠지→ 기본적으로 아님(노드 Role이 pull)ecr:GetAuthorizationToken을 리포지토리 ARN으로 제한 → 동작 안 하는 케이스 많음(대개*필요)- cross-account에서 리포지토리 정책에
Principal누락 →BatchGetImage403 - 리전이 다른 ECR URI 사용 → “존재하지 않음/AccessDenied” 혼합
- VPC Endpoint policy로 특정 Role만 허용해 놓고 노드 Role 교체 → 갑자기 403
5) 결론: 403은 “권한의 주체”부터 잡으면 빨리 끝난다
EKS에서 ECR 403을 가장 빨리 푸는 방법은 “누가 pull 하는가”를 먼저 확정하는 것입니다. 대부분의 표준 EKS에서는 노드 IAM Role이 이미지를 가져오므로, 노드 Role에 GetAuthorizationToken + 이미지 다운로드 3종 권한을 부여하면 즉시 해결됩니다. 그 다음에도 403이 남으면, 리포지토리 정책(cross-account), 엔드포인트 정책, SCP/Boundary의 명시적 거부를 순서대로 확인하면 됩니다.
추가로 401/토큰/secret 계열로 보이는 경우에는 아래 글의 진단 루틴이 그대로 도움이 됩니다.