- Published on
K8s ImagePullBackOff - ECR 403·토큰 만료 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 정상인데도 배포만 하면 ImagePullBackOff가 뜨고, 이벤트에는 403 Forbidden 혹은 token has expired 류의 메시지가 찍히는 경우가 있습니다. 특히 AWS ECR을 프라이빗 레지스트리로 쓰는 EKS 환경에서 자주 보이며, 원인은 크게 3가지로 수렴합니다.
- 노드(또는 파드)가 ECR에 접근할 IAM 권한이 없음
- 이미지 풀 인증 토큰이 만료되었거나 갱신이 안 됨
- ECR 엔드포인트/네트워크/리전/계정이 꼬여서 다른 레지스트리를 보고 있음
이 글에서는 kubectl describe에서 보이는 메시지를 기준으로 원인을 빠르게 분류하고, EKS에서 가장 안정적으로 고치는 방법(노드 IAM, IRSA, imagePullSecret, ecr-credential-provider)을 각각 정리합니다.
관련해서 EKS 노드 상태 이슈를 함께 겪는 경우도 많습니다. 노드가 준비되지 않으면 이미지 풀 단계에서 증상이 섞여 보일 수 있으니, 필요하면 EKS kubelet NotReady - CNI plugin not initialized 해결도 같이 점검하세요.
1) 증상 확인: 이벤트에서 단서 뽑기
가장 먼저 이벤트를 봅니다.
kubectl get pods -n <NAMESPACE>
kubectl describe pod <POD_NAME> -n <NAMESPACE>
Events 섹션에서 자주 보이는 패턴은 다음과 같습니다.
Failed to pull image ...: rpc error: code = Unknown desc = failed to pull and unpack image ... 403 Forbiddenno basic auth credentialsdenied: User ... is not authorized to perform: ecr:BatchGetImagetoken has expired또는expired token(컨테이너 런타임/credential helper 로그에 따라 표현이 다름)
이 메시지에 따라 접근 전략이 달라집니다.
no basic auth credentials: imagePullSecret이 없거나 잘못됨(혹은 kubelet이 ECR 토큰을 생성/주입하지 못함)is not authorized: IAM 정책/역할 문제403 Forbidden만 덩그러니: 리전/계정/레포지토리 정책/프라이빗 링크/프록시 등도 함께 의심
추가로 노드에서 직접 풀 테스트를 하면 분류가 빨라집니다(권한 vs 네트워크 vs 토큰).
2) ECR 인증 구조 이해: 왜 “토큰 만료”가 나는가
ECR은 도커 레지스트리 로그인에 쓰는 인증 문자열을 GetAuthorizationToken으로 발급합니다. 이 토큰은 영구 자격증명이 아니라 유효기간이 있는 임시 토큰입니다.
Kubernetes에서 ECR 이미지를 당겨오는 방식은 보통 3가지 중 하나입니다.
imagePullSecret에 도커 레지스트리 인증을 넣고 파드가 그 시크릿을 사용- 노드 IAM 권한으로 kubelet(또는 credential provider)이 ECR 토큰을 동적으로 가져와 사용
- IRSA로 파드 서비스어카운트가 ECR 토큰을 가져와 사용(워크로드 단위 권한)
여기서 “토큰 만료”는 보통 1번에서 발생합니다. 사람이 aws ecr get-login-password로 만든 값을 시크릿에 넣어두면, 어느 순간부터 만료되어 ImagePullBackOff가 납니다.
따라서 장기적으로는 **정적 imagePullSecret에 의존하지 않는 구조(2 또는 3)**가 운영 친화적입니다.
3) 원인 A: 노드/파드 IAM 권한 부족으로 ECR 403
3-1) 노드 IAM 역할에 필요한 최소 권한
EKS에서 노드가 ECR에서 이미지를 받는 가장 흔한 경로는 노드 IAM Role입니다. 노드 역할에 아래 권한이 없으면 ecr:BatchGetImage 등에서 403이 납니다.
필수 액션(대표 세트):
ecr:GetAuthorizationTokenecr:BatchCheckLayerAvailabilityecr:GetDownloadUrlForLayerecr:BatchGetImage
정책 예시는 다음처럼 붙일 수 있습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
}
]
}
운영에서는 Resource를 특정 레포지토리 ARN으로 제한하고 싶지만, ecr:GetAuthorizationToken은 Resource가 *인 형태가 일반적입니다.
3-2) 레포지토리 정책(Repository policy)도 확인
계정 간(크로스 어카운트)으로 ECR을 공유하거나, 레포지토리 정책으로 접근을 제한한 경우 노드 IAM에 권한이 있어도 레포지토리 정책에서 거부되어 403이 납니다.
점검 포인트:
- 이미지 URI의 계정 ID가 현재 EKS 노드가 속한 계정과 같은지
- 크로스 어카운트면 레포지토리 정책에 해당 Principal이 허용되어 있는지
- 리전이 맞는지(다른 리전 ECR을 보고 있으면 엔드포인트/권한이 엇갈릴 수 있음)
이미지 URI 예시(리전과 계정 확인):
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:20260224
4) 원인 B: 정적 imagePullSecret 토큰 만료
4-1) 만료된 시크릿 증상
- 이전에 잘 되던 배포가 어느 날부터 갑자기 실패
- 동일 노드에서 퍼블릭 이미지는 잘 당겨오는데 ECR만 실패
- 이벤트에
no basic auth credentials또는 런타임 로그에expired token
4-2) 즉시 복구: 시크릿 갱신
아래는 ECR 비밀번호를 다시 받아서 도커 레지스트리 시크릿을 갱신하는 전형적인 절차입니다.
AWS_REGION=ap-northeast-2
ACCOUNT_ID=123456789012
NAMESPACE=default
SECRET_NAME=ecr-pull
aws ecr get-login-password --region $AWS_REGION \
| kubectl create secret docker-registry $SECRET_NAME \
--docker-server=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \
--docker-username=AWS \
--docker-password-stdin \
-n $NAMESPACE \
--dry-run=client -o yaml \
| kubectl apply -f -
그리고 파드/디플로이먼트에 imagePullSecrets가 붙어 있는지 확인합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
imagePullSecrets:
- name: ecr-pull
containers:
- name: app
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:20260224
4-3) 근본 해결: “토큰을 시크릿에 박제”하지 않기
정적 시크릿은 결국 만료됩니다. 운영에서는 다음 중 하나로 전환하는 것을 권합니다.
- EKS 노드 IAM 권한으로 kubelet이 ECR 토큰을 자동 처리(가장 흔함)
- IRSA로 워크로드 단위로 ECR 읽기 권한 부여
- 클러스터에 ECR credential provider가 정상 구성되었는지 확인
이미 imagePullSecret로만 돌아가던 구성을 노드 IAM 기반으로 바꾸면, 시크릿 갱신 작업과 장애 포인트가 줄어듭니다.
5) 원인 C: IRSA를 쓰는데도 403이 나는 경우
IRSA(IAM Roles for Service Accounts)를 쓰면 “파드가 AssumeRoleWithWebIdentity로 권한을 얻어 ECR에 접근”하는 형태가 됩니다. 여기서 흔한 실수는 다음입니다.
- 파드에 지정한
serviceAccountName이 실제로 해당 IAM Role과 연결되어 있지 않음 - OIDC provider/Trust policy가 잘못됨
- ECR 권한은 Role에 줬지만, 실제 이미지 풀은 노드가 수행하고 있어 Role이 적용되지 않음
중요한 포인트는 이미지 풀 주체가 누구냐입니다.
- 전통적으로 이미지 풀은 kubelet(노드)이 수행합니다.
- 따라서 “파드에 IRSA를 달았는데 이미지 풀 403”이면, 기대한 권한 주체가 아닌 경우가 많습니다.
이때는 노드 IAM 역할에 ECR 권한을 주는 방식으로 먼저 안정화하고, 워크로드 단위 제어가 꼭 필요할 때만 IRSA 기반 이미지 풀 설계를 추가로 검토하는 편이 안전합니다.
6) 네트워크/엔드포인트 이슈: 403처럼 보이는 접근 실패
403이 항상 권한 문제는 아닙니다. 다음 케이스에서는 네트워크/엔드포인트 문제를 먼저 의심하세요.
- VPC 엔드포인트(PrivateLink)로 ECR을 쓰는데,
ecr.api또는ecr.dkr둘 중 하나만 만들었거나 보안그룹/라우팅이 누락됨 - 노드가 NAT 없이 프라이빗 서브넷에 있고, ECR 퍼블릭 엔드포인트로 나갈 길이 없음
- 리전이 다른 ECR을 가리키는 이미지 URI를 사용
ECR은 크게 두 엔드포인트가 관여합니다.
com.amazonaws.<region>.ecr.apicom.amazonaws.<region>.ecr.dkr
둘 다 필요할 수 있습니다(이미지 레이어 다운로드 경로 포함). 또한 S3 게이트웨이 엔드포인트가 간접적으로 필요해지는 구성도 있으니, 프라이빗 환경이라면 VPC 엔드포인트 구성을 재점검하세요.
7) 실전 트러블슈팅 체크리스트(재현 가능한 순서)
아래 순서대로 보면 불필요한 추측을 줄일 수 있습니다.
7-1) 파드 이벤트와 이미지 URI 확인
kubectl describe pod <POD_NAME> -n <NAMESPACE>
- 이미지 URI의 계정/리전/레포 이름이 맞는지
- 태그가 실제로 존재하는지(푸시 실패/태그 오타도
not found로 섞여 나옴)
7-2) 노드 IAM Role 확인
EKS 관리형 노드그룹이면 노드 IAM Role에 AmazonEC2ContainerRegistryReadOnly 같은 관리형 정책이 붙어 있는지부터 확인합니다(조직 정책에 따라 커스텀일 수 있음).
7-3) imagePullSecrets 사용 여부 확인
kubectl get deploy <DEPLOYMENT> -n <NAMESPACE> -o yaml
kubectl get secret ecr-pull -n <NAMESPACE> -o yaml
- 시크릿이 존재하는지
- 네임스페이스가 맞는지
- 디플로이먼트 템플릿에 참조가 있는지
7-4) 만료 토큰을 쓰는지 확인하고 제거/전환
- 단기: 시크릿 갱신
- 중기: 노드 IAM 기반으로 전환
7-5) 프라이빗 네트워크라면 VPC 엔드포인트 점검
ecr.api,ecr.dkr엔드포인트 존재- 보안그룹 인바운드/아웃바운드
- 서브넷 라우팅
8) 운영 팁: 재발 방지 패턴
8-1) 배포 파이프라인에서 “시크릿 갱신”을 자동화하지 말기
겉보기에는 GitOps나 CI에서 주기적으로 imagePullSecret을 갱신하면 해결되는 듯 보이지만, 결국 시크릿 관리가 복잡해지고 롤링 중 토큰 경합/전파 지연으로 또 다른 장애가 생깁니다. 가능하면 노드 IAM(또는 클러스터 표준 credential provider)로 단순화하세요.
8-2) 장애가 이미지 풀인지, 노드 상태인지 분리
이미지 풀 장애가 발생했는데 동시에 노드가 NotReady라면 원인이 섞입니다. 이 경우 노드 네트워킹(CNI)부터 안정화해야 이미지 풀도 정상화되는 경우가 많습니다. 필요하면 앞서 언급한 EKS kubelet NotReady - CNI plugin not initialized 해결을 먼저 확인하세요.
8-3) EKS에서 HTTP 레벨 증상과 혼동하지 않기
간혹 애플리케이션 장애(예: 헤더 과다로 인한 431)와 배포 장애가 같은 시점에 터져 원인 파악이 꼬입니다. 배포 단계에서 멈추는지(이미지 풀), 런타임에서 멈추는지(HTTP) 레이어를 분리해 보세요. 런타임 이슈라면 EKS에서 HTTP 431 해결 - 헤더·쿠키 과다 진단도 참고할 수 있습니다.
9) 결론: 403과 토큰 만료를 가장 빨리 끝내는 방법
- 이벤트에서
no basic auth credentials면imagePullSecret누락/오류 또는 토큰 만료 가능성이 큽니다. - 이벤트에서
is not authorized면 노드 IAM Role 또는 레포지토리 정책을 먼저 고치세요. - 정적
imagePullSecret은 언젠가 만료됩니다. 운영 안정성을 원하면 노드 IAM 기반(또는 표준 credential provider)으로 전환하는 것이 정답에 가깝습니다.
마지막으로, 문제를 “ECR 권한”으로만 단정하지 말고 리전/계정/엔드포인트/네트워크까지 한 번에 점검하면 ImagePullBackOff를 훨씬 빨리 종결할 수 있습니다.