Published on

K8s ImagePullBackOff 401·429 - ECR·레지스트리 제한 해결

Authors

Kubernetes에서 ImagePullBackOff는 증상 자체보다 원인 분류가 중요합니다. 특히 이벤트에 401 Unauthorized 또는 429 Too Many Requests가 함께 보이면, 대부분은 (1) 인증/권한 문제 또는 (2) 레지스트리 레이트 리밋/동시 다운로드 제한으로 갈립니다.

이 글은 EKS+ECR을 기준으로 설명하지만, Docker Hub/사설 레지스트리에서도 동일한 진단 프레임으로 적용할 수 있습니다.

1) 먼저 이벤트에서 401과 429를 분리하자

가장 먼저 해야 할 일은 “정말 401인가, 429인가”를 Pod 이벤트에서 확정하는 것입니다.

kubectl describe pod -n $NS $POD

# 또는 이벤트만 필터링
kubectl get events -n $NS --sort-by=.lastTimestamp | tail -n 50
  • 401 Unauthorized/no basic auth credentials/denied: requested access to the resource is denied
    • 자격증명 누락, 토큰 만료, IAM 권한 부족, 잘못된 리포지토리/태그
  • 429 Too Many Requests/toomanyrequests/rate limit exceeded
    • Docker Hub 등 퍼블릭 레지스트리의 IP 기반 제한
    • ECR/사설 레지스트리의 API throttling 또는 동시 pull 폭주

여기서부터는 두 갈래로 나눠 해결합니다.

2) 401 Unauthorized: 인증/권한 문제를 빠르게 좁히는 체크리스트

2.1 이미지 경로/태그 오타부터 제거

의외로 가장 흔합니다.

kubectl get pod -n $NS $POD -o jsonpath='{.spec.containers[*].image}'
  • 리포지토리 이름 대소문자
  • 태그 존재 여부
  • 멀티 아키텍처(ARM/AMD) 이미지 여부

ECR이라면 다음도 확인합니다.

aws ecr describe-images --repository-name $REPO --image-ids imageTag=$TAG

2.2 (EKS) 노드 IAM Role이 ECR Pull 권한을 갖는지

EKS에서 가장 기본적인 패턴은 **노드 인스턴스 프로파일(노드 IAM Role)**이 ECR에서 pull 할 권한을 갖는 것입니다. 최소 권한 예시는 다음 액션이 필요합니다.

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

정책 예시:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ],
      "Resource": "*"
    }
  ]
}

노드가 권한이 없으면 kubelet이 토큰을 못 받아 401 또는 403 성격의 에러가 납니다.

2.3 (EKS) IRSA를 썼는데도 401이 나는 경우

IRSA(서비스어카운트 기반 IAM Role)로 ECR을 pull 하려는 구성은 흔치 않지만, 컨테이너 런타임이 이미지를 pull 하는 주체는 기본적으로 노드(kubelet/containerd) 입니다. 즉, “Pod의 서비스어카운트 권한”만으로는 이미지 pull이 해결되지 않는 경우가 많습니다.

또한 GitHub Actions 등 CI에서 OIDC로 assume-role 하여 ECR에 push 하는 파이프라인이라면, push는 성공했는데 pull에서 401이 나는 케이스가 생깁니다(리포지토리 정책/권한 분리 등). 이 경우 OIDC assume-role 자체 문제도 함께 점검해야 합니다.

2.4 imagePullSecrets(사설 레지스트리) 누락/만료

ECR이 아니라 사설 레지스트리(또는 Docker Hub private)라면 imagePullSecrets가 빠졌거나, dockerconfigjson 토큰이 만료된 경우가 많습니다.

kubectl get secret -n $NS
kubectl get secret -n $NS $SECRET -o jsonpath='{.type}'

Deployment에 명시:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  template:
    spec:
      imagePullSecrets:
        - name: regcred
      containers:
        - name: app
          image: registry.example.com/team/app:1.2.3

ECR을 imagePullSecrets로 쓰는 경우(토큰을 주기적으로 갱신해야 함)에는 운영 난이도가 올라갑니다. 가능하면 EKS에서는 노드 IAM Role 기반으로 단순화하는 편이 안전합니다.

3) 429 Too Many Requests: 레지스트리 제한/풀 폭주를 제어하자

429는 “클러스터가 잘못됐다”기보다 “너무 많이 당겼다”에 가깝습니다. 대규모 롤아웃, 노드 스케일아웃, 동일 이미지의 다중 Pod 재시작이 겹치면 쉽게 터집니다.

3.1 Docker Hub/퍼블릭 레지스트리 레이트 리밋

퍼블릭 레지스트리는 IP 단위로 제한되는 경우가 많아, NAT 뒤에 많은 노드가 붙으면 한 번에 한계에 도달합니다.

대응 우선순위:

  1. 이미지를 ECR로 미러링 (가장 확실)
  2. Docker Hub 유료 플랜 또는 인증 pull
  3. 노드 로컬 캐시/프리풀로 외부 요청 감소

3.2 ECR에서도 429가 날 수 있나

ECR은 Docker Hub처럼 “강한” 레이트 리밋 메시지가 흔하진 않지만, 다음 상황에서 실질적으로 429/Throttling 성격의 문제가 나타날 수 있습니다.

  • 짧은 시간에 대량의 GetAuthorizationToken 호출
  • 신규 노드가 한꺼번에 늘어나면서 동일 레이어를 동시 다운로드
  • 이미지가 너무 크고 레이어 수가 많아 네트워크/스토리지 병목이 겹침

즉, 레지스트리 자체 제한뿐 아니라 클러스터의 동시 pull 패턴이 핵심 원인인 경우가 많습니다.

4) 재발 방지 1: 롤아웃/스케일 시 “동시 Pull”을 줄이는 운영 패턴

4.1 imagePullPolicy를 무조건 Always로 두지 말자

태그 전략에 따라 다르지만, Always는 노드에 캐시가 있어도 매번 레지스트리 확인을 유도합니다. 429 상황에서는 악화 요인이 됩니다.

  • 고정 태그(예: 1.2.3)를 쓴다면 IfNotPresent 권장
  • latest 같은 가변 태그라면 Always가 필요할 수 있으나, 운영에서는 가급적 피하는 것이 좋습니다.
containers:
  - name: app
    image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/app:1.2.3
    imagePullPolicy: IfNotPresent

4.2 롤링 업데이트 폭을 제한해서 pull 스파이크를 줄이기

Deployment의 maxSurge가 크면 한 번에 많은 새 Pod가 뜨면서 동시에 pull을 시도합니다.

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

대규모 트래픽 서비스에서 이 설정은 429뿐 아니라 노드 디스크/네트워크 병목도 완화합니다.

4.3 노드 스케일아웃 시 프리풀(Pre-pull)로 초기 폭주 방지

새 노드가 붙을 때 가장 많이 터지는 게 이미지 pull입니다. DaemonSet으로 주요 이미지를 미리 당겨두면, 실제 워크로드 스케줄링 시 pull 요청이 줄어듭니다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: prepull-main-images
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: prepull
  template:
    metadata:
      labels:
        app: prepull
    spec:
      tolerations:
        - operator: Exists
      containers:
        - name: prepull
          image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/app:1.2.3
          command: ["/bin/sh", "-c", "sleep 3600"]
      terminationGracePeriodSeconds: 0
  • 이 DaemonSet은 “실행이 목적”이 아니라 “이미지 pull이 목적”입니다.
  • 이미지가 여러 개라면 컨테이너를 여러 개 두거나, 우선순위가 높은 것부터 적용합니다.

5) 재발 방지 2: 노드 디스크/GC 문제로 pull이 반복되는 악순환 끊기

429처럼 보여도 실제로는 노드가 이미지를 계속 지웠다가 다시 받는 상황일 수 있습니다.

  • 노드 디스크 부족 DiskPressure
  • 이미지 GC가 자주 발생
  • Evicted가 폭주하면서 재스케줄링 및 재-pull 반복

이 경우는 레지스트리 제한을 아무리 올려도 근본 해결이 안 됩니다. 먼저 노드 디스크/이미지 캐시 유지가 가능해야 합니다.

진단 예:

kubectl describe node $NODE | sed -n '1,200p'

# 컨테이너 런타임이 containerd라면(노드에서)
# sudo crictl images
# sudo crictl df

대응:

  • 노드 루트 볼륨 확장 또는 인스턴스 타입 변경
  • 불필요한 이미지 태그 난사 방지(태그 폭증은 캐시 효율을 떨어뜨림)
  • 큰 이미지 슬림화(레이어 최적화)

6) ECR/레지스트리 인증 토큰과 네트워크 경로 점검 포인트

6.1 ECR 토큰은 12시간 유효, 그러나 노드에서 자동 갱신이 전제

EKS 노드 IAM Role 기반이면 kubelet이 필요 시 토큰을 받아옵니다. 여기서 문제가 생기면 다음을 의심합니다.

  • 노드가 STS/ECR API로 나가는 네트워크 경로 문제
  • VPC 엔드포인트(ECR API, ECR DKR, S3) 구성 미흡
  • 프록시/방화벽에서 특정 도메인 차단

Private 서브넷에서 NAT 없이 운영한다면 VPC 엔드포인트 구성이 중요합니다. 특히 ECR은 API만 열어서는 안 되고, 이미지 레이어 전송에 S3 경로도 연관될 수 있습니다.

6.2 ErrImagePull에서 TLS/CA 문제도 401처럼 보일 수 있음

사설 레지스트리에서 인증서 체인이 노드에 없으면 pull이 실패합니다. 이벤트에 x509: certificate signed by unknown authority 같은 메시지가 있으면 401이 아니라 신뢰 체인 문제입니다.

이 경우는 레지스트리 인증서 배포(노드 신뢰 저장소) 또는 올바른 인증서 체인(중간 인증서 포함) 구성으로 해결합니다.

7) 실전 트러블슈팅 플로우(요약)

아래 순서대로 보면 대부분 빠르게 수렴합니다.

  1. kubectl describe pod로 정확한 에러 문자열 확인(401 vs 429)
  2. 401이면
    • 이미지 경로/태그 존재 확인
    • (EKS) 노드 IAM Role에 ECR pull 권한 확인
    • imagePullSecrets 누락/만료 확인
    • 네트워크 경로(STS/ECR) 및 인증서 문제 확인
  3. 429이면
    • 롤아웃/스케일 이벤트로 동시 pull 폭주 여부 확인
    • imagePullPolicy/maxSurge 조정
    • 프리풀 DaemonSet 도입
    • 노드 디스크/GC로 pull 반복되는지 확인

8) 마무리: 401은 “권한”, 429는 “패턴” 문제다

  • 401은 대개 “누가 pull 하는가(노드 vs Pod)”와 “그 주체의 권한/자격증명”을 맞추면 끝납니다.
  • 429는 레지스트리 한도를 올리는 것보다, 클러스터가 동시에 당기는 패턴(롤아웃, 스케일아웃, 재시작 폭주, 디스크 압박)을 먼저 제어하는 게 효과가 큽니다.

EKS 운영에서 장애는 서로 엮여 나타나는 경우가 많습니다. 예를 들어 노드 디스크 압박으로 Evicted가 발생하면 재스케줄링이 늘고, 그 결과 이미지 pull이 폭증해 429까지 이어질 수 있습니다. 이런 연쇄를 끊는 관점으로 접근하면 ImagePullBackOff는 훨씬 빨리 안정화됩니다.