- Published on
K8s ImagePullBackOff - ErrImagePull·401 빠른 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡한데 Pod만 계속 ImagePullBackOff로 멈춰 있으면, 대부분은 이미지 자체 문제가 아니라 레지스트리 인증/권한/네트워크 문제입니다. 특히 이벤트에 ErrImagePull과 함께 401 Unauthorized가 보이면 “K8s가 이미지를 못 받는 이유”가 거의 좁혀집니다. 이 글은 현장에서 바로 적용 가능한 순서로 진단하고, 가장 흔한 실수(Secret은 만들었는데 ServiceAccount에 안 붙임, namespace 착각, ECR 토큰 만료 등)를 빠르게 제거하는 데 초점을 둡니다.
> 참고: 429(Too Many Requests)로 PullBackOff가 나는 케이스는 원인이 다르니 별도로 다루는 글을 보는 게 빠릅니다. EKS ImagePullBackOff 429 Too Many Requests 해결
1) 증상 확인: ImagePullBackOff와 401을 “이벤트”로 확정
kubectl get pods만 보면 원인이 안 보입니다. describe의 Events를 먼저 확인하세요.
kubectl -n <ns> get pod <pod>
kubectl -n <ns> describe pod <pod>
Events에서 아래와 비슷한 패턴이 나오면 레지스트리 인증/권한 이슈입니다.
Failed to pull image ...: rpc error: code = Unknown desc = failed to pull and unpack image ...: unauthorized: authentication requiredfailed to resolve reference ...: unexpected status code [manifests ...]: 401 Unauthorizedpull access denied, repository does not exist or may require authorization
여기서 중요한 포인트:
ImagePullBackOff는 재시도 백오프 상태이고, 실제 원인은 그 직전의ErrImagePull메시지에 있습니다.repository does not exist문구가 섞여 있어도 실제로는 권한 부족(401/403)인 경우가 흔합니다.
2) 원인 분류: 401이 나는 대표 시나리오 6가지
401은 “인증이 안 됨”이지만, K8s 관점에서 원인은 꽤 다양합니다.
2.1 imagePullSecret이 없거나 namespace가 다름
Secret을 만들었는데 Pod가 있는 namespace가 아니라 다른 곳에 만들면 그대로 401이 납니다.
2.2 Secret 타입/키가 틀림 (dockerconfigjson)
kubernetes.io/dockerconfigjson 타입이 아니거나, key가 .dockerconfigjson이 아니면 kubelet이 인증정보로 인식하지 못합니다.
2.3 Pod spec에 secret을 안 붙였거나, ServiceAccount에 누락
Deployment에 imagePullSecrets를 넣지 않았고, 기본 ServiceAccount에도 등록이 안 되어 있으면 401.
2.4 이미지 레퍼런스가 잘못됨 (tag/registry host)
사소하지만 치명적입니다.
my-registry.com/team/app:tagvsmy-registry.com/team/app:TAGdocker.io생략 시 의도치 않게 Docker Hub로 해석
2.5 ECR/GCR/ACR 등 “토큰 만료형” 레지스트리
예: ECR은 로그인 토큰이 만료됩니다(기본 12시간). 토큰을 Secret로 박아두면 시간 지나서 401이 뜹니다.
2.6 노드/런타임 단에서 프록시·CA·TLS 문제 → 인증 실패로 보이는 경우
사설 레지스트리 + 사설 CA 환경에서 인증서 신뢰 문제가 401/403처럼 보이기도 합니다(실제론 x509 에러가 더 흔하지만, 프록시가 중간에서 401로 변환하는 경우도 있음).
3) 가장 빠른 해결 루틴(체크리스트)
아래 순서대로 보면 보통 10~15분 내로 원인이 잡힙니다.
describe pod이벤트에서 정확한 registry host와 status code 확인- 이미지가 실제로 존재하는지(레지스트리 UI/CLI) 확인
- Secret이 같은 namespace에 있는지 확인
- Pod/SA에
imagePullSecrets가 연결돼 있는지 확인 - (ECR 등) 토큰 만료형이면 자동 갱신 방식으로 전환
- 노드에서 네트워크/DNS/TLS 확인
4) kubectl로 상태를 “정확히” 확인하는 명령들
4.1 어떤 ServiceAccount로 뜨는지
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.serviceAccountName}'
비어 있으면 default입니다.
4.2 Pod에 imagePullSecrets가 직접 붙어있는지
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.imagePullSecrets}'
4.3 ServiceAccount에 imagePullSecrets가 붙어있는지
kubectl -n <ns> get sa <sa-name> -o yaml
여기 imagePullSecrets:가 없으면, Pod에 직접 넣지 않는 이상 인증정보를 못 씁니다.
4.4 Secret 타입과 데이터 키 확인
kubectl -n <ns> get secret <secret-name> -o jsonpath='{.type}'
# 기대값: kubernetes.io/dockerconfigjson
kubectl -n <ns> get secret <secret-name> -o jsonpath='{.data}'
# .dockerconfigjson 키가 있어야 함
5) 프라이빗 레지스트리 기본 해법: docker-registry Secret 생성
가장 표준적인 방식입니다.
kubectl -n <ns> create secret docker-registry regcred \
--docker-server=my-registry.example.com \
--docker-username='<username>' \
--docker-password='<password-or-token>' \
--docker-email='devnull@example.com'
이후 Pod에 직접 붙이거나, ServiceAccount에 기본으로 붙입니다.
5.1 Deployment에 imagePullSecrets 추가 (가장 명시적)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: my-ns
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: my-registry.example.com/team/app:1.2.3
imagePullSecrets:
- name: regcred
5.2 ServiceAccount에 imagePullSecrets 연결 (운영에서 선호)
여러 워크로드에 공통 적용하기 좋습니다.
kubectl -n my-ns patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "regcred"}]}'
특정 SA를 쓰는 경우엔 default 대신 해당 SA를 패치하세요.
kubectl -n my-ns patch serviceaccount app-sa \
-p '{"imagePullSecrets": [{"name": "regcred"}]}'
6) EKS + ECR에서 401이 나는 핵심 포인트
ECR은 크게 두 가지 방식이 있습니다.
- (비권장) ECR 로그인 토큰을 Secret로 저장: 시간이 지나면 만료되어 401
- (권장) 노드 IAM 권한 또는 IRSA/컨트롤러 기반으로 자동 인증
6.1 노드 IAM Role에 ECR Pull 권한이 있는지
노드가 이미지를 당기는 주체(kubelet)라서, 일반적으로는 노드 IAM Role이 ecr:GetAuthorizationToken, ecr:BatchGetImage 등을 가져야 합니다.
정책 예시(최소권한은 환경에 맞게 조정):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
}
]
}
6.2 다른 AWS 계정의 ECR(크로스 어카운트)인 경우
401/403이 특히 많이 납니다. 이때는
- ECR Repository Policy에서 Pull 계정을 허용했는지
- 이미지 URI가 올바른 계정/리전인지
를 확인해야 합니다.
6.3 ECR 토큰을 Secret로 굳이 써야 한다면(임시 대응)
운영 권장 방식은 아니지만, 긴급 복구용으로는 가능합니다. 다만 주기적으로 갱신해야 합니다.
AWS_REGION=ap-northeast-2
ACCOUNT_ID=123456789012
aws ecr get-login-password --region $AWS_REGION | \
kubectl -n my-ns create secret docker-registry ecrcred \
--docker-server=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \
--docker-username=AWS \
--docker-password-stdin \
--dry-run=client -o yaml | kubectl apply -f -
그리고 imagePullSecrets: [ecrcred]를 연결합니다.
> 토큰 만료로 반복 장애가 나면, “왜 주기적으로 깨지는지”를 장애 보고서에 명확히 남기고 자동화(갱신 Job/컨트롤러) 또는 IAM 기반 인증으로 전환하세요.
7) GKE/Artifact Registry, ACR 등에서의 공통 함정
- Workload Identity / Managed Identity를 쓰는 환경에서, 갑자기 401이 나면 대개 권한 바인딩이 누락되었거나(서비스 계정 매핑), 이미지가 다른 프로젝트/테넌트로 이동한 케이스입니다.
- “클러스터는 접근 가능한데 레지스트리만 401”이면 네트워크보다 IAM/ACL일 확률이 높습니다.
공통적으로 확인할 것:
- 레지스트리의 리포지토리 권한(프로젝트/리소스 레벨)
- K8s SA ↔ 클라우드 SA 매핑
- 이미지 경로(리전/프로젝트) 오타
8) TLS/프록시/사설 CA 환경에서의 실전 팁
사내 레지스트리(예: Harbor) + 사설 인증서 환경에서는 다음이 자주 문제입니다.
- 노드 OS에 CA가 설치되어 있지 않음
- containerd가 별도 trust store를 사용
- 프록시가 레지스트리 인증을 가로채며 401로 반환
진단 팁:
- 노드에서 직접
curl -v https://my-registry.../v2/를 실행해 TLS/인증 흐름 확인 - containerd 사용 시
/etc/containerd/certs.d/<registry>/hosts.toml설정 확인(환경별) - 사설 CA 배포를 노드 부트스트랩에 포함
이 영역은 클러스터/런타임마다 차이가 커서, 이벤트에 x509: certificate signed by unknown authority가 보이면 401보다 먼저 TLS 신뢰부터 해결해야 합니다.
9) 재발 방지: 배포 파이프라인에서 “당기기 검증”을 자동화
운영에서 중요한 건 “한 번 고치는 것”보다 “다시는 안 터지게 하는 것”입니다.
- CI에서 이미지 푸시 후, 같은 자격증명으로
docker pull(또는crane pull)을 수행해 권한/태그 존재를 검증 - GitOps(Argo CD 등) 사용 시, Sync 실패가 이미지 Pull 문제로 이어질 수 있어 이벤트 기반 알람을 구성
Argo CD 운영 중이라면, 동기화 실패/헬스 디그레이드 진단 글도 함께 보면 장애 범위를 줄일 수 있습니다.
또한 ImagePullBackOff는 CrashLoopBackOff와 동시에 보이는 경우도 있어(이미지는 받아왔지만 실행이 죽는 경우), 둘을 구분하는 체크리스트를 갖고 있으면 좋습니다.
10) 결론: 401은 “Secret/권한/연결” 3가지만 보면 끝난다
정리하면, ErrImagePull + 401은 다음 3축으로만 압축해도 해결이 빨라집니다.
- Secret: 올바른 타입/키로, 올바른 namespace에 존재하는가?
- 권한: 레지스트리(IAM/Repo Policy)에서 Pull 권한이 실제로 있는가?
- 연결: Pod 또는 ServiceAccount에 imagePullSecrets가 연결되어 있는가?
이 3가지를 describe pod 이벤트와 함께 교차 확인하면, 대부분의 ImagePullBackOff는 “원인 파악 → 수정 → 재배포”까지 한 번에 끝낼 수 있습니다.