Published on

Kubernetes ImagePullBackOff - ECR 인증 만료 해결

Authors

서론

운영 중인 EKS/Kubernetes에서 갑자기 ImagePullBackOff가 터지면 대부분은 “이미지 태그가 잘못됐나?”부터 의심합니다. 하지만 AWS ECR을 쓰는 환경에서는 ECR 인증 토큰(12시간 유효), 노드/파드의 IAM 경로, 네트워크에서 ECR API/STS로 나가는 경로 중 하나가 깨지면서 동일한 증상으로 나타나는 경우가 많습니다.

이 글은 ImagePullBackOff가 떴을 때 이벤트 로그에서 ECR 인증 만료(Expired/Unauthorized) 계열을 빠르게 판별하고, 단기 처방(Secret 갱신)과 근본 해결(노드 IAM/IRSA/프라이빗 엔드포인트)까지 한 번에 정리합니다.


증상 확인: ImagePullBackOff가 “ECR 인증”인지 먼저 판별

먼저 파드 이벤트를 봅니다.

kubectl describe pod <pod> -n <ns>

다음과 같은 메시지가 보이면 ECR 인증/권한/토큰 문제일 확률이 큽니다.

  • Failed to pull image ... / rpc error: code = Unknown desc = Error response from daemon: pull access denied
  • no basic auth credentials
  • denied: User ... is not authorized to perform: ecr:BatchGetImage
  • unauthorized: authentication required

여기서 중요한 포인트는 Kubernetes가 이미지를 pull할 때 사용하는 주체는 기본적으로 “노드(워커) IAM” 이라는 점입니다. (일반적인 구성에서 kubelet/컨테이너 런타임이 노드 자격증명으로 ECR에 접근)

즉, 애플리케이션 파드에 IRSA를 붙였다고 해서 이미지 pull 권한이 자동으로 해결되지는 않습니다.


ECR 인증 토큰의 구조: 왜 “만료”가 자주 보이나?

ECR은 Docker Registry 인증을 위해 GetAuthorizationToken으로 base64 인코딩된 사용자/비밀번호를 발급합니다. 이 토큰은 기본적으로 12시간 만료이며, 다음 두 가지 방식 중 하나로 클러스터에 적용됩니다.

  1. 노드가 ECR API를 호출해 자동으로 토큰을 받아서 pull (EKS 관리형 노드그룹/권장 구성)
  2. 사람이 docker login 또는 imagePullSecret을 만들어서 수동으로 토큰을 주입

2)번은 시간이 지나면 12시간 후 반드시 만료하므로, 운영에서 가장 흔한 장애 패턴이 됩니다.


원인 1) imagePullSecret로 ECR 토큰을 넣어둔 경우(만료)

진단

다음처럼 imagePullSecrets가 붙어 있고, 그 값이 kubernetes.io/dockerconfigjson 타입이면 수동 토큰일 가능성이 큽니다.

kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.imagePullSecrets[*].name}'
kubectl get secret <secret> -n <ns> -o yaml

단기 해결(즉시 복구): Secret 재생성

AWS CLI로 토큰을 다시 받아 Secret을 갱신합니다.

AWS_REGION=ap-northeast-2
ACCOUNT_ID=123456789012

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

그리고 디플로이먼트를 롤아웃합니다.

kubectl rollout restart deploy/<deploy> -n <ns>

근본 해결: “수동 Secret”을 제거하고 노드 IAM 기반으로 전환

장기 운영에서는 ECR 토큰을 Secret로 넣는 패턴을 피하는 것이 좋습니다.

  • EKS/EC2 노드 IAM Role에 ECR Pull 권한 부여
  • kubelet/컨테이너 런타임이 자동으로 ECR 인증을 처리하도록 구성

수동 Secret을 꼭 써야 한다면, CronJob으로 주기 갱신(예: 6시간마다)하는 방식이 필요하지만, 운영 복잡도가 커집니다.


원인 2) 노드 IAM Role에 ECR Pull 권한이 없거나 경로가 깨짐

노드가 ECR에서 이미지를 받아오려면 최소 아래 권한이 필요합니다.

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

빠른 체크: 노드가 어떤 Role을 쓰는지 확인

노드의 인스턴스 프로파일/Role을 확인합니다.

kubectl get nodes -o wide
# 노드 이름으로 EC2 인스턴스 찾은 뒤 IAM Role 확인

EKS 관리형 노드그룹이면 보통 AmazonEC2ContainerRegistryReadOnly 정책을 노드 Role에 붙이면 해결됩니다.

ECR 권한 정책 예시(최소 권한)

특정 리포지토리만 허용하는 형태로도 줄일 수 있습니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ecr:GetAuthorizationToken"],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchCheckLayerAvailability"
      ],
      "Resource": "arn:aws:ecr:ap-northeast-2:123456789012:repository/my-repo"
    }
  ]
}

원인 3) STS/토큰 계열 오류(ExpiredToken)로 ECR 호출 자체가 실패

ECR 토큰을 받으려면(= GetAuthorizationToken) 결국 AWS API 호출이 필요하고, 이 과정에서 STS 자격증명 만료/시간 오차/IRSA 설정 문제가 있으면 ECR 인증이 “만료처럼” 보이기도 합니다.

특히 파드가 AWS SDK를 쓰는 경우에는 ExpiredToken이 더 노골적으로 드러나지만, 노드 측 자격증명이 꼬여도 유사 현상이 발생할 수 있습니다.

관련해서 STS ExpiredToken을 파드 관점에서 해결하는 체크리스트는 아래 글이 도움이 됩니다.

추가로 IRSA를 이미 썼는데도 AccessDenied가 난다면(= 올바른 Role로 Assume이 안 됨) 이 글의 점검 순서가 유용합니다.


원인 4) 네트워크 문제로 ECR 엔드포인트에 못 붙는 경우(인증 만료처럼 보임)

ImagePullBackOff는 단순 인증 실패뿐 아니라 ECR API/Registry에 대한 네트워크 연결 실패에서도 발생합니다. 프라이빗 서브넷에서 NAT/라우팅/NACL/MTU 이슈로 HTTPS만 막히면, 이벤트에는 종종 i/o timeout, TLS handshake timeout 같은 문구가 섞여 나옵니다.

이때는 “DNS는 되는데 HTTPS만 실패” 패턴을 의심해야 합니다.

ECR에 필요한 대표 엔드포인트

리전마다 다르지만 보통 아래가 필요합니다.

  • api.ecr.<region>.amazonaws.com (ECR API)
  • <account>.dkr.ecr.<region>.amazonaws.com (Docker Registry)
  • 그리고 이미지 레이어는 S3 백엔드를 타므로 S3 경로도 영향

프라이빗 환경에서 NAT 없이 운영하려면 VPC Endpoint(Interface/Gateway)를 고려합니다.

  • Interface Endpoint: com.amazonaws.<region>.ecr.api, com.amazonaws.<region>.ecr.dkr, com.amazonaws.<region>.sts
  • Gateway Endpoint: com.amazonaws.<region>.s3

실전 트러블슈팅 순서(10분 컷)

운영에서 시간을 아끼려면 아래 순서가 효율적입니다.

  1. 이벤트에서 에러 문자열 분류

    • no basic auth credentials → Secret/인증 주입 문제
    • not authorized → IAM 권한 문제
    • i/o timeout, TLS handshake → 네트워크 문제
  2. imagePullSecret 사용 여부 확인

    • 사용 중이면 우선 재생성으로 즉시 복구
  3. 노드 IAM Role의 ECR 권한 확인

    • AmazonEC2ContainerRegistryReadOnly 또는 최소 권한 정책 부여
  4. 프라이빗 서브넷이면 ECR/ST S/S3 엔드포인트 또는 NAT 경로 확인

  5. 클러스터 시간 동기화/STS 만료 이슈 확인


권장 아키텍처: “노드 풀 권한 + 최소 Secret”

정리하면, ECR 이미지 pull은 다음 패턴이 가장 안정적입니다.

  • 노드 IAM Role에 ECR Pull 권한 부여(표준)
  • 애플리케이션 파드는 IRSA로 AWS 리소스 접근 권한만 부여(분리)
  • 프라이빗 클러스터는 ECR API/DKR + STS + S3에 대한 VPC Endpoint 구성
  • 수동 imagePullSecret은 가급적 제거(필요 시 자동 갱신 체계 필수)

부록: ECR 로그인 Secret을 CronJob으로 자동 갱신(불가피한 경우)

정책상/구조상 imagePullSecret이 꼭 필요하다면, 만료 전에 주기적으로 갱신하는 CronJob을 둘 수 있습니다. 아래는 개념 예시이며, 실제로는 IRSA를 붙여 ecr:GetAuthorizationToken이 가능해야 합니다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: refresh-ecr-pull-secret
  namespace: kube-system
spec:
  schedule: "0 */6 * * *" # 6시간마다
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: ecr-secret-refresher
          restartPolicy: OnFailure
          containers:
            - name: refresh
              image: public.ecr.aws/aws-cli/aws-cli:2.15.0
              env:
                - name: AWS_REGION
                  value: ap-northeast-2
                - name: ACCOUNT_ID
                  value: "123456789012"
                - name: NAMESPACE
                  value: "default"
              command:
                - /bin/sh
                - -lc
                - |
                  set -e
                  PASS=$(aws ecr get-login-password --region "$AWS_REGION")
                  kubectl create secret docker-registry ecr-pull \
                    --docker-server="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" \
                    --docker-username=AWS \
                    --docker-password="$PASS" \
                    -n "$NAMESPACE" \
                    --dry-run=client -o yaml | kubectl apply -f -

이 방식은 “증상 재발 방지”에는 도움이 되지만, 운영 복잡도와 보안 표면적이 커지므로 가능하면 노드 IAM 기반으로 단순화하는 편이 낫습니다.


결론

ImagePullBackOff는 결과(파드가 이미지를 못 받음)만 보여주기 때문에 원인이 다양합니다. ECR을 쓴다면 특히 다음 네 가지를 우선순위로 보면 해결이 빨라집니다.

  • imagePullSecret 기반 ECR 토큰이 12시간 만료됐는가?
  • 노드 IAM Role에 ECR Pull 권한이 있는가?
  • STS/IRSA 경로가 깨져 AWS API 호출이 실패하는가?
  • 프라이빗 네트워크에서 ECR/STS/S3로 HTTPS가 막힌 건 아닌가?

이 네 가지를 순서대로 좁혀가면, 대부분의 ImagePullBackOff: ECR 인증 만료 케이스는 짧은 시간 안에 복구하고 재발까지 막을 수 있습니다.