Published on

EKS ImagePullBackOff - IRSA·ECR 인증 8단계

Authors

EKS에서 파드가 ImagePullBackOff 또는 ErrImagePull로 멈추면 대부분은 ECR 인증(권한/토큰) 또는 네트워크(프라이빗 서브넷, VPC 엔드포인트, DNS) 문제로 귀결됩니다. 특히 IRSA(IAM Roles for Service Accounts)를 쓰는 환경에서는 “파드의 서비스어카운트 권한으로 ECR에서 이미지를 당겨올 수 있다”라고 오해하기 쉬운데, 기본적으로 이미지 풀은 노드(또는 런타임)가 수행합니다. 다만 Fargate, 특정 CNI/런타임 구성, 프라이빗 레지스트리 인증을 위한 웹훅/사이드카 패턴 등에서는 서비스어카운트/토큰 설정이 얽히며 진단 난도가 올라갑니다.

아래 8단계는 “지금 당장 원인을 좁히고 재현 가능한 증거를 남기는” 순서로 구성했습니다. 단계별로 확인한 결과를 기록해두면, 같은 유형의 장애가 재발했을 때 평균 복구 시간을 크게 줄일 수 있습니다.

관련 진단 글로, 파드가 뜬 뒤 반복 재시작에 빠지는 케이스는 EKS CrashLoopBackOff? OOMKilled 진단 7단계도 함께 참고하면 좋습니다.

1단계: 이벤트에서 “정확한 실패 문장” 확보

가장 먼저 해야 할 일은 추측이 아니라 이벤트 원문을 확보하는 것입니다. ImagePullBackOff는 결과 상태일 뿐이고, 원인은 이벤트에 남습니다.

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

kubectl -n <namespace> get events --sort-by=.lastTimestamp | tail -n 50

자주 보는 패턴은 다음과 같습니다.

  • no basic auth credentials : 레지스트리 인증 정보(도커 로그인/풀 시크릿) 부재
  • pull access denied / denied: User ... is not authorized : IAM/ECR 권한 부족
  • manifest unknown / not found : 이미지 태그/리포지토리 경로 오타
  • i/o timeout / TLS handshake timeout : 네트워크/라우팅/DNS/NAT/ECR 엔드포인트
  • x509: certificate signed by unknown authority : 프록시/사내 CA/미러 레지스트리 인증서 문제

여기서 오타/태그 문제권한/네트워크 문제가 1차로 갈립니다.

2단계: 이미지 경로·태그·리전부터 정합성 확인

ECR은 리전 종속입니다. ap-northeast-2에 푸시했는데 us-east-1 엔드포인트로 풀을 시도하면 인증이 맞아도 실패합니다.

  • 이미지 URI 형식: ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/REPO:TAG
  • TAG 존재 여부
  • REPO 이름(슬래시 포함 여부) 정확성
# 로컬/CI에서 태그 존재 확인
aws ecr describe-images \
  --region ap-northeast-2 \
  --repository-name <repo> \
  --image-ids imageTag=<tag>

만약 이벤트가 manifest unknown이면 이 단계에서 끝나는 경우가 많습니다.

3단계: 노드 IAM Role에 ECR Pull 권한이 있는지 확인

EKS에서 일반 노드 그룹(EC2) 이 이미지를 풀 때 주체는 대개 노드의 IAM Role입니다. 따라서 IRSA를 잘 구성했더라도 노드 Role에 ECR Pull 권한이 없으면 그대로 실패합니다.

노드 인스턴스 프로파일(Role)을 확인한 뒤, 아래 권한이 포함되어 있는지 봅니다.

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

가장 빠른 방법은 AWS 관리형 정책 AmazonEC2ContainerRegistryReadOnly가 붙어 있는지 확인하는 것입니다.

# 노드가 속한 노드그룹 확인
aws eks list-nodegroups --cluster-name <cluster>

# 노드그룹 상세에서 nodeRole 확인
aws eks describe-nodegroup --cluster-name <cluster> --nodegroup-name <ng>

권한이 없다면 노드 Role에 정책을 부여하고, 문제 파드를 재스케줄링해 재시도합니다.

4단계: IRSA를 쓰는 경우, “이 워크로드가 정말 IRSA가 필요한가” 분리

여기서 중요한 포인트는 다음입니다.

  • 이미지 풀 자체는 보통 IRSA와 무관합니다(EC2 노드 기준).
  • 하지만 조직에 따라 다음이 섞여 장애가 복잡해집니다.
    • 프라이빗 레지스트리 인증을 위해 imagePullSecrets를 IRSA 기반으로 동적으로 주입하는 컨트롤러 사용
    • Fargate 프로파일(노드가 없고, 실행 주체가 다름)
    • ECR 외 레지스트리(Artifactory, Harbor 등)와 혼용

따라서 먼저 “이 파드가 ECR만 풀면 되는가, 아니면 별도 인증 체인이 있는가”를 분리하세요.

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

imagePullSecrets가 비어 있고 ECR인데도 no basic auth credentials가 뜬다면, 노드 IAM 권한/네트워크 쪽으로 다시 회귀하는 게 맞습니다.

5단계: 서비스어카운트 어노테이션과 OIDC 신뢰 정책 점검(IRSA 핵심)

IRSA를 실제로 사용하는 워크로드(예: 앱이 S3/STS 호출)라면, 여기서 틀어질 확률이 큽니다. 특히 OIDC Provider, Trust Policy의 sub 조건이 조금만 어긋나도 STS AssumeRoleWithWebIdentity가 실패합니다.

서비스어카운트에 Role ARN이 올바르게 붙었는지 확인합니다.

kubectl -n <namespace> get sa <sa-name> -o yaml

다음 형태가 있어야 합니다.

  • eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...

그리고 IAM Role의 신뢰 정책에서 sub가 정확히 일치해야 합니다. 예시는 아래와 같습니다(각 값은 환경에 맞게).

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:<namespace>:<sa-name>",
          "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

여기서도 MDX 렌더링을 고려해 <...> 같은 표기를 본문에서 그대로 쓰지 말고, 위처럼 코드 블록 안에서만 사용하세요.

6단계: 파드 내부에서 WebIdentity 토큰 마운트 여부 확인

IRSA가 동작하려면 파드에 토큰 파일이 마운트되고, 환경변수로 Role ARN과 토큰 경로가 주입됩니다.

kubectl -n <namespace> exec -it <pod> -- sh -lc 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE"'
kubectl -n <namespace> exec -it <pod> -- sh -lc 'ls -l $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 20 $AWS_WEB_IDENTITY_TOKEN_FILE; echo'
  • 값이 비어 있다면: 서비스어카운트 지정 누락, automountServiceAccountToken 비활성화, 혹은 Admission/Webhook 정책 영향 가능
  • 토큰 파일이 없거나 권한이 이상하면: PSA/OPA/Gatekeeper 정책, 커스텀 SA 설정을 의심

이 단계는 “IRSA 자체가 살아있는지”를 빠르게 확인하는 데 유용합니다.

7단계: ECR 네트워크 경로 점검(NAT vs VPC Endpoint)

프라이빗 서브넷의 노드가 ECR에 접근하려면 보통 둘 중 하나가 필요합니다.

  • NAT Gateway를 통한 아웃바운드 인터넷
  • VPC Interface Endpoint(ECR API, ECR DKR) + Gateway Endpoint(S3)

ECR은 이미지 레이어를 S3에서 내려받는 흐름이 포함되므로, VPC 엔드포인트를 쓴다면 S3까지 같이 봐야 합니다.

체크 포인트:

  • 노드 서브넷 라우팅 테이블에 0.0.0.0/0가 NAT로 가는지
  • 보안그룹/NACL이 443 아웃바운드 허용인지
  • VPC 엔드포인트를 쓴다면 Private DNS 활성화 여부

노드에서 직접 DNS/HTTPS 확인을 해보면 단서가 빨리 나옵니다. 디버그 파드를 띄워 점검하세요.

kubectl -n <namespace> run net-debug \
  --image=public.ecr.aws/docker/library/alpine:3.19 \
  --restart=Never -it --rm -- sh

# 파드 안에서
apk add --no-cache curl bind-tools
nslookup <account>.dkr.ecr.<region>.amazonaws.com
curl -I https://api.ecr.<region>.amazonaws.com

i/o timeout이나 DNS 실패가 나오면 IAM이 아니라 네트워크 문제일 가능성이 큽니다.

8단계: 컨테이너 런타임/노드 캐시/레지스트리 제한(최종 수습 단계)

위 1~7단계를 통과했는데도 간헐적으로만 실패한다면, 마지막으로 런타임과 노드 상태를 봅니다.

  • 노드 디스크 압박(DiskPressure)로 이미지 풀 실패
  • containerd가 꼬여서 인증 토큰 캐시 문제
  • ECR API rate limit/네트워크 순간 장애

확인 명령:

kubectl get node
kubectl describe node <node>

# 노드로 접속 가능하다면(SSM/SSH)
sudo journalctl -u containerd --since "30 min ago" | tail -n 200
sudo df -h
sudo df -i

디스크는 남았는데 inode가 고갈된 케이스도 실제로 자주 나옵니다. 이 경우는 용량 남는데 No space left? inode 고갈 해결법을 같이 보면 원인 파악이 빨라집니다.


재발 방지 체크리스트(운영 기준)

운영 환경에서는 “한 번 해결”보다 “다음에 안 터지게”가 더 중요합니다. 아래를 권장합니다.

  1. 노드 Role에 AmazonEC2ContainerRegistryReadOnly 포함 여부를 IaC로 강제(Terraform/CloudFormation)
  2. 프라이빗 서브넷이면 NAT 또는 VPC 엔드포인트(ECR API/ECR DKR/S3) 표준화
  3. kubectl get events 기반의 알람 룰 구축(예: ImagePullBackOff가 5분 이상 지속)
  4. IRSA는 워크로드별로 최소 권한 정책 + Trust Policy sub를 템플릿화
  5. 디버그용 네트워크 점검 파드(알파인/넷슈트)를 표준 런북에 포함

부록: 최소 ECR Pull 정책 예시

노드 Role 또는 (특수한 구성에서) 풀 주체에 붙일 수 있는 최소 권한 예시입니다.

{
  "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>"
    }
  ]
}

위 정책에서 Resource는 리포지토리 단위로 최소화하고, 멀티 리포지토리면 ARN을 추가하세요.


정리

ImagePullBackOff는 증상은 단순하지만 원인 축이 명확합니다.

  • 이벤트 원문으로 방향을 잡고(1단계)
  • 이미지/리전 오타를 제거한 뒤(2단계)
  • 노드 IAM과 IRSA를 분리해서 보고(3~6단계)
  • 마지막으로 네트워크와 런타임 상태를 확인하면(7~8단계)

대부분의 케이스는 30분 안에 원인을 특정할 수 있습니다. 특히 “IRSA를 붙였는데 왜 ECR 풀을 못 하지?” 같은 혼란은 3단계에서 노드 Role을 확인하는 것만으로 빠르게 정리되는 경우가 많습니다.