Published on

EKS ImagePullBackOff - ECR 인증·IRSA·노드캐시 진단

Authors

서론

EKS에서 Pod가 ImagePullBackOff 또는 ErrImagePull로 멈추면 대부분 “ECR 로그인 문제”로 뭉뚱그려 처리되지만, 실제 원인은 훨씬 다양합니다. 노드 IAM Role과 IRSA의 책임 경계가 헷갈리거나, imagePullSecret이 잘못 붙었거나, 프라이빗 서브넷 NAT/엔드포인트 구성이 빠졌거나, 심지어 노드의 이미지 캐시/디스크(inode 포함) 문제로도 동일한 증상이 납니다.

이 글은 EKS에서 ECR 이미지를 당겨오지 못할 때를 기준으로,

  1. 이벤트에서 원인 문구를 읽는 법 → 2) 인증(노드/IRSA/Secret) → 3) 네트워크(ECR 엔드포인트) → 4) 노드 캐시/런타임 → 5) 재발 방지 순서로 실전 진단 흐름을 제공합니다.

> IRSA 자체 진단 감각을 더 키우고 싶다면: EKS IRSA는 되는데 S3만 403? 30분 진단도 함께 보면 “정책은 맞는데 호출이 실패하는” 유형을 분해하는 데 도움이 됩니다.

1) 먼저 이벤트에서 실패 지점을 확정하기

ImagePullBackOff는 “이미지 pull이 실패했고 kubelet이 backoff로 재시도 중”이라는 결과일 뿐입니다. 원인은 이벤트에 거의 다 나옵니다.

kubectl describe pod -n <ns> <pod>

아래 Events에서 특히 이 문구들을 찾으세요.

  • no basic auth credentials → 레지스트리 인증 토큰/secret 문제
  • denied: User ... is not authorized to perform: ecr:BatchGetImage → IAM 권한 문제
  • manifest unknown / not found → 태그/리포지토리/리전/계정(Registry ID) 불일치
  • dial tcp ... i/o timeout / context deadline exceeded → 네트워크 경로(NAT/SG/NACL/엔드포인트/DNS)
  • x509: certificate signed by unknown authority → 프록시/미러/사설 CA/노드 신뢰 저장소

추가로, kubelet 로그가 필요하면 노드에서 확인합니다(관리형 노드 그룹/자가관리 노드 모두 유효).

# 노드 SSH 가능할 때
sudo journalctl -u kubelet -n 200 --no-pager

2) 가장 흔한 오해: “ECR pull은 IRSA로 된다?”

결론부터 말하면, 기본적으로 ECR 이미지 pull은 노드의 IAM Role(EC2 instance profile)이 담당합니다.

  • Pod가 AWS API(S3/SQS/STS 등)를 호출할 때: 보통 IRSA(서비스어카운트 역할)
  • 노드가 컨테이너 이미지를 레지스트리에서 pull 할 때: 보통 노드 IAM Role

> 일부 환경(특정 런타임/구성)에서 Pod identity 기반으로 pull을 시도하는 케이스가 논의되지만, 운영에서 가장 안정적인 기본 전제는 “노드 권한으로 pull”입니다. 따라서 ImagePullBackOff는 IRSA보다 노드 IAM Role부터 의심하는 것이 맞습니다.

2.1 노드 IAM Role에 필요한 ECR 권한

노드 역할에 아래 액션이 있어야 합니다(최소 권한 예시).

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ],
      "Resource": "*"
    }
  ]
}
  • GetAuthorizationToken은 리소스가 *여야 하는 경우가 일반적입니다.
  • 크로스 계정 ECR(다른 AWS 계정의 ECR)이라면 노드 Role 권한 + ECR 리포지토리 정책 둘 다 필요합니다.

노드가 어떤 Role을 쓰는지 확인:

kubectl get node -o wide
# 노드 인스턴스 ID 확인 후 AWS 콘솔/CLI에서 IAM instance profile 확인

또는 EKS 관리형 노드 그룹이면 노드 그룹 설정에서 Node Role을 확인합니다.

2.2 크로스 계정 ECR일 때 리포지토리 정책 체크

A계정(EKS)에서 B계정(ECR) 이미지를 pull한다면, B계정 ECR 리포지토리에 A계정 노드 Role(또는 해당 Role이 속한 계정/Principal)을 허용해야 합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPullFromOtherAccount",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::<A_ACCOUNT_ID>:role/<EKS_NODE_ROLE>"},
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ]
    }
  ]
}

여기서 흔한 실수:

  • Principal을 “EKS 클러스터 Role”로 넣음(실제 pull은 노드가 함)
  • 리전이 다름(예: ap-northeast-2 EKS인데 us-east-1 ECR URL을 씀)

3) imagePullSecret: 필요할 때와 함정

ECR은 보통 kubelet이 노드 자격증명으로 토큰을 받아 처리하므로 imagePullSecret이 없어도 됩니다. 하지만 아래 케이스에서는 imagePullSecret이 필요하거나, 반대로 잘못된 secret이 붙어 pull을 망치는 일이 생깁니다.

3.1 no basic auth credentials가 뜨는 경우

  • 프라이빗 레지스트리(사내 Harbor 등)
  • ECR인데도 imagePullSecrets를 강제로 붙여놓았고, 그 secret이 틀렸거나 만료됨

서비스 어카운트/파드 스펙을 확인하세요.

kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.imagePullSecrets}'
kubectl get sa -n <ns> <sa> -o yaml | sed -n '/imagePullSecrets/,$p'

3.2 ECR용 docker-registry secret을 굳이 만들 때(권장 X지만 필요할 때)

예: 노드 Role로 pull이 불가능한 특수 상황(엄격한 네트워크/권한 분리)에서 임시로 사용.

AWS_REGION=ap-northeast-2
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>

주의:

  • 이 방식은 토큰 만료(기본 12시간)로 인해 운영 지속성이 떨어집니다.
  • External Secrets/자동 갱신 컨트롤러를 붙이지 않으면 다시 장애가 납니다.

4) 네트워크: NAT/엔드포인트/DNS가 pull을 막는 패턴

이벤트에 i/o timeout, context deadline exceeded가 보이면 인증보다 네트워크를 먼저 보세요.

4.1 프라이빗 서브넷 노드인데 NAT가 없다

프라이빗 서브넷의 노드가 인터넷 경로가 없으면 ECR에 접근할 수 없습니다.

해결 옵션:

  • NAT Gateway/Instance 구성
  • 또는 VPC Interface Endpoint를 구성해 인터넷 없이 ECR/STS에 접근

4.2 VPC 엔드포인트를 쓸 때 필요한 것들

ECR pull은 보통 아래가 필요합니다.

  • com.amazonaws.<region>.ecr.api (Interface)
  • com.amazonaws.<region>.ecr.dkr (Interface)
  • com.amazonaws.<region>.sts (Interface) — 토큰/서명 흐름에서 필요해지는 경우가 많음
  • com.amazonaws.<region>.s3 (Gateway) — 레이어가 S3로 내려오는 경로

또한 엔드포인트의 Security Group이 노드(또는 노드 SG)에서 443을 허용해야 합니다.

4.3 DNS/리전/레지스트리 URL 오타

이미지 URL에서 자주 틀리는 것들:

  • <ACCOUNT_ID>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>
  • GovCloud/China 리전은 도메인이 다름
  • ECR Public은 public.ecr.aws/... 형식

확인:

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

5) 노드 이미지 캐시·컨테이너 런타임·디스크(inode)까지 보기

인증/네트워크가 멀쩡한데도 pull이 실패하거나, 특정 노드에서만 반복된다면 노드 상태를 의심해야 합니다.

5.1 노드 디스크 부족 vs inode 고갈

컨테이너 런타임은 레이어를 로컬 디스크에 저장합니다. 디스크 여유가 있어도 inode가 고갈되면 파일 생성이 실패할 수 있습니다.

노드에서 확인:

df -h
df -i
sudo du -sh /var/lib/containerd 2>/dev/null || true
sudo du -sh /var/lib/docker 2>/dev/null || true

조치:

  • 불필요한 이미지/컨테이너 정리
  • 노드 루트 볼륨 확장 또는 인스턴스 타입/AMI 설계 변경
  • 오토스케일링/노드 교체로 드레인 후 재생성

5.2 containerd 이미지/스냅샷 정리

EKS는 최근 AMI에서 containerd가 기본입니다. 노드에서:

sudo crictl images
sudo crictl rmi --prune

# containerd 네임스페이스 기준으로 더 깊게 볼 때
sudo ctr -n k8s.io images ls
sudo ctr -n k8s.io content ls | head

주의: 운영 중인 파드에 영향이 갈 수 있으니, 드레인 후 수행을 권장합니다.

5.3 특정 노드에서만 ImagePullBackOff가 난다

이 경우는 “클러스터 공통 설정 문제”보다:

  • 노드 SG/라우팅이 일부만 다름
  • 노드가 오래 떠서 캐시/디스크가 누적
  • 노드의 시간/CA 번들/프록시 환경이 꼬임

을 더 자주 봅니다.

진단 팁:

# 어떤 노드에 스케줄됐는지
kubectl get pod -n <ns> <pod> -o wide

# 문제 노드만 골라서 파드 재스케줄
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

6) IRSA는 언제 관련이 있나: “컨테이너가 뜬 뒤”의 ECR 호출

ImagePullBackOff 자체는 보통 노드 권한 이슈지만, 다음 케이스에서는 IRSA가 간접 원인이 될 수 있습니다.

  • initContainer/사이드카가 런타임에 ECR API를 직접 호출(예: 메타데이터 조회, 이미지 태그 검사, 배포 도구)
  • External Secrets/컨트롤러가 ECR 토큰을 생성해 imagePullSecret을 주기 갱신

이때는 IRSA 진단 루틴(서비스어카운트 어노테이션, OIDC provider, STS 엔드포인트, 정책의 리소스 범위)을 그대로 적용하면 됩니다. IRSA 관련 “정책은 맞는데 403” 유형은 위 내부 링크의 흐름이 그대로 재사용됩니다.

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

7.1 노드 Role 표준화

  • EKS 관리형 노드 그룹의 Node Role에 ECR pull 최소 권한을 기본 포함
  • 크로스 계정 ECR이면 리포지토리 정책 템플릿화

7.2 프라이빗 클러스터는 엔드포인트를 표준 구성

  • ecr.api, ecr.dkr, sts, s3 엔드포인트를 IaC로 고정
  • 엔드포인트 SG에 노드 SG → 443 허용을 명시

7.3 노드 디스크/이미지 GC 모니터링

  • 노드 파일시스템 사용률 + inode 사용률 모니터링
  • 이미지 레이어가 큰 워크로드는 노드 루트 볼륨 사이징을 넉넉히

8) 빠른 결론: 10분 안에 끝내는 우선순위

  1. kubectl describe pod 이벤트에서 문구로 분류(인증/권한/네트워크/미존재)
  2. ECR이면 노드 IAM Roleecr:* 최소 권한 확인
  3. 크로스 계정이면 ECR 리포지토리 정책에 노드 Role Principal 허용
  4. 타임아웃이면 NAT 또는 VPC 엔드포인트(ecr.api/ecr.dkr/sts/s3) 점검
  5. 특정 노드만 문제면 디스크/inode, containerd 캐시, 노드 교체(드레인)로 해결

이 흐름대로 보면 “IRSA가 문제인가요?” 같은 질문을 하기 전에, 대부분의 ImagePullBackOff는 이벤트 문구 한 줄과 노드 Role 확인만으로도 빠르게 원인이 좁혀집니다.