Published on

EKS Pod에서 AWS ECR 403 AccessDenied 해결

Authors

서론

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.api
  • com.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
  • GetAuthorizationToken 403 → 노드 Role에 토큰 권한부터 점검
  • BatchGetImage 403 → 리포지토리 정책/리포지토리 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 누락 → BatchGetImage 403
  • 리전이 다른 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 계열로 보이는 경우에는 아래 글의 진단 루틴이 그대로 도움이 됩니다.