Published on

K8s ImagePullBackOff 401·429 해결 - ECR

Authors

서론

EKS/Kubernetes 환경에서 ImagePullBackOff는 흔하지만, 이벤트 메시지에 401 Unauthorized 또는 429 Too Many Requests가 붙는 순간부터는 디버깅 난이도가 급격히 올라갑니다. 특히 ECR(Elastic Container Registry)은 “도커 로그인 토큰(AuthorizationToken)”을 주기적으로 발급받아야 하고, 대량 스케일링 시에는 ECR API/레지스트리 측 스로틀링과 노드 단의 네트워크/캐시 정책이 복합적으로 얽힙니다.

이 글은 401(인증 실패)429(요청 과다/스로틀링) 를 명확히 분리해 원인을 좁히고, EKS에서 가장 재현이 잦은 케이스(노드 IAM, IRSA, imagePullSecret, NAT/프록시, 대량 롤아웃) 기준으로 즉시 적용 가능한 해결책을 제공합니다.

관련해서 노드 상태/네트워크 계층 문제도 함께 의심될 때는 아래 글도 참고하면 진단이 빨라집니다.

1) 증상 빠르게 분류하기: 이벤트/로그에서 401 vs 429

가장 먼저 Pod 이벤트를 확인해 어디서 실패하는지(인증/네트워크/레지스트리) 를 분류합니다.

kubectl describe pod <pod> -n <ns>

# 이벤트만 빠르게
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp | tail -n 30

자주 보이는 패턴:

  • 401 Unauthorized

    • failed to authorize: rpc error: code = Unknown desc = failed to fetch anonymous token
    • no basic auth credentials
    • pull access denied
    • 의미: 레지스트리 인증 토큰을 못 얻었거나, 얻었지만 권한이 없거나, 잘못된 레지스트리/리전을 보고 있음
  • 429 Too Many Requests / throttling

    • 429 Too Many Requests
    • ThrottlingException (ECR API 호출에서)
    • 의미: 대량 스케일링/동시 풀로 ECR API(특히 GetAuthorizationToken) 또는 레지스트리 레이어 다운로드가 제한에 걸림

추가로 노드 런타임 로그도 중요합니다(특히 containerd).

# 노드에 접속 가능할 때
sudo journalctl -u containerd -n 200 --no-pager
sudo journalctl -u kubelet -n 200 --no-pager

2) 401 Unauthorized: ECR 인증 경로를 먼저 고정

ECR 이미지를 풀려면 크게 2가지 경로 중 하나가 정상이어야 합니다.

  1. 노드 IAM Role(Instance Profile) 로 kubelet/container runtime이 ECR 토큰을 발급받아 풀
  2. imagePullSecret 으로 도커 레지스트리 크리덴셜을 제공해 풀

EKS에서는 보통 1) 노드 IAM으로 해결하는 경우가 많고, Fargate/특수 환경 또는 멀티계정/프록시 환경에서는 2)도 씁니다.

2.1 가장 흔한 원인: 노드 IAM Role에 ECR 권한이 없음

노드 그룹(Managed Node Group/AutoScaling Group)의 IAM Role에 아래 권한이 필요합니다.

  • ecr:GetAuthorizationToken
  • ecr:BatchCheckLayerAvailability
  • ecr:GetDownloadUrlForLayer
  • ecr:BatchGetImage

AWS에서 제공하는 관리형 정책을 쓰면 빠릅니다.

  • AmazonEC2ContainerRegistryReadOnly

정책 부착 확인(예시):

aws iam list-attached-role-policies --role-name <NodeInstanceRoleName>

권한이 없으면 ECR 토큰 발급 자체가 실패하며, 결과적으로 kubelet/containerd에서 401로 떨어집니다.

2.2 리전/레지스트리 URL 불일치(의외로 자주 발생)

ECR 레포 URL은 리전에 종속입니다.

  • 올바른 형태: <account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>

다음 실수를 많이 합니다.

  • 클러스터는 ap-northeast-2인데 이미지 URL은 us-east-1
  • Private ECR인데 Public ECR 도메인으로 착각

매니페스트의 image를 먼저 검증하세요.

kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.template.spec.containers[*].image}'

2.3 IRSA를 썼는데 401? (ServiceAccount 권한은 이미지 풀에 직접 영향이 적다)

많이 헷갈리는 지점: IRSA(ServiceAccount IAM Role) 는 “Pod 내부 AWS API 호출” 권한을 주는 것이고, 이미지 풀은 기본적으로 노드의 kubelet/container runtime이 수행합니다.

즉, Pod에 IRSA를 붙였다고 해서 이미지 풀 인증이 해결되지 않습니다.

예외적으로, 특정 구성(커스텀 크리덴셜 프로바이더, 프라이빗 레지스트리 프록시 등)에서는 Pod 자격증명이 영향을 줄 수 있지만, 표준 EKS에서는 노드 IAM을 먼저 봐야 합니다.

2.4 imagePullSecret로 해결해야 하는 케이스와 생성 방법

다음 상황이면 imagePullSecret이 유용합니다.

  • 노드 IAM을 건드릴 수 없는 환경(제한된 계정/조직 정책)
  • 멀티계정 ECR 접근(크로스어카운트)에서 임시로 빠르게 우회
  • 특정 네임스페이스만 다른 레지스트리를 사용

ECR은 “고정 비밀번호”가 아니라 12시간 토큰 기반이므로, Secret을 정적으로 만들어두면 언젠가 만료됩니다. 따라서 보통은 컨트롤러로 주기 갱신(예: external-secrets, cronjob)하거나, 가능하면 노드 IAM으로 푸는 게 정석입니다.

토큰을 이용해 Secret 생성(일회성 예시):

AWS_REGION=ap-northeast-2
ACCOUNT_ID=123456789012

aws ecr get-login-password --region $AWS_REGION | \
  kubectl create secret docker-registry ecr-pull-secret \
  --docker-server=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \
  --docker-username=AWS \
  --docker-password-stdin \
  -n <ns>

Deployment에 적용:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      imagePullSecrets:
        - name: ecr-pull-secret
      containers:
        - name: app
          image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myrepo:1.0.0

3) 429 Too Many Requests: “누가” 과도하게 요청하는지부터 찾기

429는 단순히 “트래픽이 많다”가 아니라, 보통 아래 둘 중 하나입니다.

  1. ECR API(특히 GetAuthorizationToken) 호출이 폭증 → 토큰 발급 요청이 너무 많음
  2. 레지스트리 레이어 다운로드 동시성이 과도 → 노드 수/파드 수가 동시에 증가하며 레이어 풀 경쟁

3.1 대량 롤아웃/오토스케일 시 토큰 발급 폭증 줄이기

노드가 새로 붙거나, 대량의 Pod가 동시에 스케줄되면 각 노드가 이미지 풀을 시도합니다. 이때 노드/런타임이 ECR 토큰을 자주 갱신하거나, 네트워크 오류로 재시도 루프가 걸리면 GetAuthorizationToken이 폭증해 429가 나기 쉽습니다.

대응 전략:

  • 롤아웃 속도 제한: Deployment maxSurge, maxUnavailable 조정
  • HPA 급격한 스케일링 완화: stabilization window/behavior 설정
  • 이미지 태그 전략: 동일 노드에서 재사용되도록 불필요한 태그 변경(매 빌드마다 고유 태그) 남발을 줄이기

Deployment 롤아웃 완화 예시:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%

HPA behavior 예시(급격한 스케일 아웃 억제):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app
spec:
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60

(HPA가 폭주하는 상황 자체를 안정화하는 패턴은 EKS HPA 폭주를 KEDA 큐기반 오토스케일링으로 안정화도 함께 참고할 만합니다.)

3.2 노드에서 이미지 캐시를 최대한 활용하기

429의 체감은 “동시에 같은 레이어를 너무 많이 당긴다”에서 옵니다. 캐시가 있으면 레지스트리 호출이 줄어듭니다.

  • imagePullPolicy: IfNotPresent 사용(태그 전략과 함께)
  • 동일 노드에 재스케줄될 가능성이 높게(예: topology spread/affinity 조정) 하는 것은 상황에 따라 도움이 되지만, 과도한 고정은 장애 내성을 해칠 수 있어 주의

예시:

containers:
  - name: app
    image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myrepo:1.0.0
    imagePullPolicy: IfNotPresent

주의: :latest 같은 가변 태그에 IfNotPresent를 쓰면 “배포했는데 안 바뀌는” 문제가 생깁니다. 가급적 불변 태그(빌드 SHA 등) 를 쓰되, 롤아웃 동시성/캐시 전략으로 429를 제어하는 편이 안전합니다.

3.3 프라이빗 서브넷 NAT/프록시 병목이 429를 유발하는 경우

ECR은 레지스트리(이미지 레이어)와 API 엔드포인트가 분리되어 있고, 프라이빗 서브넷에서는 NAT Gateway/프록시를 통해 나갑니다. NAT 대역폭/커넥션 추적/프록시 제한으로 다운로드가 실패하면, 런타임이 재시도하면서 결과적으로 429처럼 보이거나 실제로 ECR에 재요청을 쏟아붓게 됩니다.

진단 포인트:

  • 특정 AZ/특정 노드그룹에서만 집중 발생하는지
  • 같은 시간대에 노드 네트워크 에러(i/o timeout, connection reset)가 동반되는지

개선책:

  • VPC Interface Endpoint(PrivateLink) 로 ECR API/DKR 엔드포인트를 VPC 내부로 끌어오기
    • com.amazonaws.<region>.ecr.api
    • com.amazonaws.<region>.ecr.dkr
    • 그리고 S3 Gateway Endpoint(레이어 전송 경로에 필요할 수 있음)

이 구성을 하면 NAT 의존도가 줄어 대량 풀에서 안정성이 크게 좋아집니다.

4) 크로스어카운트 ECR에서 401/403이 섞여 나올 때

EKS 클러스터 계정(A)과 ECR 레포 계정(B)이 다르면 다음 둘이 모두 맞아야 합니다.

  • (A의 노드 IAM Role) → (B의 ECR) 접근 권한
  • (B의 ECR Repository Policy)에서 (A의 Role/Account) 허용

레포 정책 예시(개념):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPullFromAccountA",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111111111111:root"},
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchCheckLayerAvailability"
      ]
    }
  ]
}

여기서 GetAuthorizationToken레포 정책이 아니라 호출자 IAM 측 권한으로 처리되는 경우가 많아, 양쪽을 같이 점검해야 합니다.

5) 재발 방지 체크리스트(운영 관점)

5.1 관측(Observability) 포인트

  • 이벤트 수집: ImagePullBackOff, ErrImagePull 카운트 알림
  • 노드 런타임 로그 수집(containerd/kubelet)
  • ECR CloudTrail/CloudWatch에서 GetAuthorizationToken 호출량 추적(429/Throttling 확인)

5.2 안전한 배포 습관

  • 대량 배포는 반드시 롤링 업데이트 파라미터로 “동시 풀 수”를 제한
  • HPA 급격 스케일 아웃 정책 완화(특히 트래픽 스파이크 서비스)
  • 프라이빗 서브넷이면 ECR VPC Endpoint 도입 검토

6) 빠른 트러블슈팅 플로우(요약)

  1. kubectl describe pod로 401인지 429인지 분류
  2. 401이면
    • 이미지 URL(리전/계정/레포) 확인
    • 노드 IAM Role에 AmazonEC2ContainerRegistryReadOnly 또는 동등 권한 확인
    • 크로스어카운트면 ECR 레포 정책 확인
    • 불가피하면 imagePullSecret(단, 토큰 만료 자동화 필요)
  3. 429이면
    • 롤아웃/HPA로 동시 풀 폭증 여부 확인
    • imagePullPolicy/캐시 전략 점검
    • NAT/프록시 병목 및 VPC Endpoint 도입 검토

결론

ImagePullBackOff의 401과 429는 겉으로 비슷해 보여도 해결책이 완전히 다릅니다. 401은 권한/인증 경로를 고정하면 빠르게 끝나고, 429는 동시성(배포/스케일링) + 네트워크 경로(NAT/Endpoint) + 캐시를 함께 손봐야 재발이 줄어듭니다. 특히 EKS 운영에서 대량 롤아웃과 오토스케일이 잦다면, VPC Endpoint와 롤아웃 제한만으로도 체감 안정성이 크게 개선됩니다.