Published on

K8s ImagePullBackOff 401 해결 - ECR·Secret

Authors

운영 중인 Kubernetes에서 Pod가 뜨지 않고 ImagePullBackOff가 반복되며 이벤트에 401 Unauthorized가 찍히는 경우는 대부분 레지스트리 인증 정보가 없거나, 만료됐거나, 잘못 연결된 상황입니다. 특히 AWS ECR은 인증 토큰이 12시간 만료되므로, 한 번은 되다가도 어느 순간부터 갑자기 401이 발생하기 쉽습니다.

이 글은 ECR을 기준으로, 401이 나는 지점을 이벤트 확인 → Secret 확인 → ServiceAccount 연결 → 노드/IRSA 권한 → 네트워크/엔드포인트 순서로 좁혀가며 해결하는 실전 체크리스트를 제공합니다.

참고로 클러스터 전반의 API 호출 지연이나 타임아웃이 동반된다면 네트워크/컨트롤플레인 이슈일 수 있으니, 원인 분리용으로 Kubernetes apiserver i/o timeout 원인과 해결도 함께 확인해두면 좋습니다.

증상 빠르게 확정하기: 이벤트에서 401을 먼저 본다

먼저 Pod 이벤트를 보면 원인이 거의 드러납니다.

kubectl -n <namespace> describe pod <pod-name>

아래 같은 메시지라면 인증 문제로 확정입니다.

  • Failed to pull image ...: failed to authorize: rpc error ... 401 Unauthorized
  • pull access denied
  • no basic auth credentials

여기서 중요한 포인트는 ImagePullBackOff는 결과(재시도 백오프)이고, 진짜 원인은 이벤트에 있다는 점입니다.

401 유형 분류: Secret 없음 vs 만료 vs 권한 부족

401은 크게 3가지로 나뉩니다.

  1. Secret이 아예 없음 / imagePullSecrets 미지정
    • 이벤트에 no basic auth credentials가 자주 보입니다.
  2. ECR 토큰 만료(12시간)
    • 한동안 잘 되다가 특정 시점 이후 동일 노드/네임스페이스에서 동시다발로 터집니다.
  3. IAM 권한 부족 또는 잘못된 계정/리전 레지스트리
    • 토큰은 만들었는데 ecr:BatchGetImage 등이 막혀서 실패하거나, 다른 계정 ECR을 바라보는 경우.

이제부터는 가장 흔한 1, 2를 빠르게 제거하고, 3을 확인하는 흐름으로 진행합니다.

1) 이미지 레퍼런스부터 점검: 리전·계정·레포 이름

의외로 가장 흔한 실수는 이미지 주소 오타입니다.

예시(정상):

  • 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:1.2.3

체크리스트:

  • 계정 ID가 맞는가
  • 리전이 클러스터/노드가 접근 가능한 리전인가
  • 레포지토리 이름이 실제 존재하는가
  • 태그가 존재하는가(또는 latest에 의존하고 있지 않은가)

이미지 존재 여부는 로컬/CI에서 다음으로 확인할 수 있습니다.

aws ecr describe-images \
  --region ap-northeast-2 \
  --repository-name my-app \
  --query 'imageDetails[0].imageTags'

2) 가장 단순한 해결: docker-registry Secret 직접 생성

Kubernetes는 레지스트리 인증을 kubernetes.io/dockerconfigjson 타입 Secret으로 받습니다. ECR은 aws ecr get-login-password로 비밀번호를 받아 docker login 형태로 주입합니다.

아래는 네임스페이스에 Secret을 만드는 대표 패턴입니다.

NAMESPACE=default
REGION=ap-northeast-2
ACCOUNT_ID=123456789012
SECRET_NAME=ecr-pull

kubectl -n $NAMESPACE create secret docker-registry $SECRET_NAME \
  --docker-server=${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com \
  --docker-username=AWS \
  --docker-password="$(aws ecr get-login-password --region $REGION)"

이제 Deployment(또는 Pod)에 imagePullSecrets를 붙입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  template:
    spec:
      imagePullSecrets:
        - name: ecr-pull
      containers:
        - name: app
          image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:1.2.3

적용 후 재배포:

kubectl -n default rollout restart deploy/my-app
kubectl -n default get pods -w

Secret이 제대로 들어갔는지 확인

kubectl -n default get secret ecr-pull -o yaml

typekubernetes.io/dockerconfigjson인지 확인합니다. 값 자체는 base64라 읽기 어렵지만, 타입과 존재 여부만으로도 1차 진단이 됩니다.

3) 자주 터지는 함정: Secret은 만들었는데 Pod가 못 씀

Secret을 만들었더라도 다음 케이스에서 401이 지속됩니다.

  • Secret을 default 네임스페이스에 만들고, Pod는 prod 네임스페이스에 떠 있음
  • Deployment에 imagePullSecrets를 안 붙였거나 오타
  • ServiceAccount에 붙이려다 누락

네임스페이스를 먼저 확인하세요.

kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.serviceAccountName}'

ServiceAccount에 imagePullSecrets를 기본으로 붙이기

매번 Deployment에 넣기 싫다면 ServiceAccount에 기본으로 연결할 수 있습니다.

kubectl -n default patch serviceaccount default \
  -p '{"imagePullSecrets": [{"name": "ecr-pull"}]}'

이 방식은 같은 네임스페이스의 많은 워크로드에 일괄 적용할 때 유용합니다.

4) ECR 토큰 만료 문제를 근본적으로 해결하기

앞의 방식은 “당장” 해결은 되지만, ECR 토큰이 12시간 만료라서 언젠가 다시 401이 납니다. 운영 환경에서는 토큰을 자동 갱신하는 구조가 필요합니다.

대표적인 선택지는 2가지입니다.

선택지 A: ECR Credential Helper 또는 노드 IAM으로 끌어오기(환경 의존)

런타임이 Docker이고 노드에 ECR credential helper 설정이 되어 있으면 자동으로 갱신되기도 합니다. 다만 EKS의 기본 런타임(containerd)과 설정 상태에 따라 다르고, 클러스터/노드 AMI 구성에 의존합니다.

운영에서 예측 가능성을 높이려면 다음 선택지 B가 더 흔히 사용됩니다.

선택지 B: ecr-credential-provider 또는 External Secret로 갱신 자동화

  • EKS에서는 버전/구성에 따라 kubelet의 ECR credential provider를 사용하는 패턴이 있습니다.
  • 또는 CronJob으로 주기적으로 Secret을 갱신하는 방식도 많이 씁니다.

CronJob으로 갱신하는 가장 단순한 형태(개념 예시):

apiVersion: batch/v1
kind: CronJob
metadata:
  name: refresh-ecr-secret
  namespace: default
spec:
  schedule: "0 */6 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          serviceAccountName: refresh-ecr-secret-sa
          containers:
            - name: refresh
              image: public.ecr.aws/aws-cli/aws-cli:2
              command:
                - /bin/sh
                - -c
                - |
                  set -e
                  REGION=ap-northeast-2
                  ACCOUNT_ID=123456789012
                  SECRET_NAME=ecr-pull
                  PASSWORD=$(aws ecr get-login-password --region $REGION)

                  kubectl -n default delete secret $SECRET_NAME --ignore-not-found
                  kubectl -n default create secret docker-registry $SECRET_NAME \
                    --docker-server=${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com \
                    --docker-username=AWS \
                    --docker-password="$PASSWORD"

주의점:

  • 위 예시는 동작 원리를 보여주는 용도이며, 실제로는 kubectl을 Job 컨테이너에서 쓰기 위한 RBAC(Secret delete/create 권한)와 클러스터 접근 구성이 필요합니다.
  • Secret을 삭제 후 생성하는 방식은 짧은 순간 레이스가 생길 수 있어, 가능하면 apply 또는 서버사이드 패치로 갱신하는 방식이 더 안전합니다.

5) IAM 권한 점검: 노드 역할 또는 IRSA

ECR에서 이미지를 받으려면 최소한 다음 권한이 필요합니다.

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

어디에 권한이 있어야 하냐는 클러스터 구성에 따라 달라집니다.

  • 일반적으로는 노드 인스턴스 프로파일(IAM role) 이 ECR Pull 권한을 가집니다.
  • 일부 구성에서는 IRSA를 통해 Pod 단위로 권한을 주려는 시도를 하는데, 이미지 Pull은 kubelet 레벨에서 일어나므로 “Pod IAM만 줬는데 Pull이 안 됨” 같은 혼동이 생깁니다.

노드 역할을 확인(예: EKS 워커 노드):

aws ec2 describe-instances \
  --instance-ids <instance-id> \
  --query 'Reservations[0].Instances[0].IamInstanceProfile.Arn'

노드 역할에 AmazonEC2ContainerRegistryReadOnly 같은 정책이 붙어있는지 확인합니다.

6) 같은 401처럼 보이지만 다른 문제: 프록시·VPC 엔드포인트·DNS

가끔 이벤트에 401이 찍혀도 실제로는 네트워크 경로가 꼬여 인증 토큰 교환이 정상적으로 되지 않는 경우가 있습니다. 특히 다음 환경에서 자주 발생합니다.

  • 사내 프록시를 거치는 노드
  • Private subnet에서 NAT 없이 ECR에 접근
  • ECR API/DP에 VPC 엔드포인트를 붙였는데 라우팅/DNS가 어긋남

이 경우는 401 외에도 i/o timeout, TLS handshake timeout 같은 로그가 섞일 수 있습니다. 클러스터 전반 증상이라면 Kubernetes apiserver i/o timeout 원인과 해결의 네트워크/타임아웃 점검 방식이 원인 분리에 도움이 됩니다.

7) 재발 방지 체크리스트

운영에서 ImagePullBackOff 401을 “한 번 해결하고 끝”내려면 아래를 표준화하는 게 좋습니다.

  • ECR 토큰 만료를 고려해 Secret 자동 갱신 또는 kubelet credential provider 사용
  • 네임스페이스별로 imagePullSecrets를 ServiceAccount에 기본 주입
  • 이미지 주소(계정/리전/레포/태그) 검증을 CI에서 선제 체크
  • 노드 IAM role에 ECR pull 권한이 일관되게 부여되었는지 IaC로 관리
  • 장애 시 describe pod 이벤트를 수집하는 런북/알람(예: Failed to pull image 카운트)

마무리: 가장 빠른 해결 순서 요약

  1. kubectl describe pod에서 401 유형 확인
  2. 이미지 주소(계정/리전/레포/태그) 오타 제거
  3. docker-registry Secret 생성 후 imagePullSecrets 연결
  4. 토큰 만료 재발 방지(자동 갱신/credential provider)
  5. 노드 IAM 권한(ECR read) 확인
  6. Private 네트워크라면 NAT/VPC 엔드포인트/DNS 점검

이 순서대로 보면 대부분의 K8s ImagePullBackOff 401은 30분 안에 원인까지 좁혀지고, 재발 방지까지 설계할 수 있습니다.