- Published on
쿠버네티스 ImagePullBackOff 401 해결법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡한데 Pod만 ImagePullBackOff로 멈추고, 이벤트에는 401 Unauthorized가 찍히는 상황은 쿠버네티스 운영에서 가장 흔한 배포 장애 중 하나입니다. 특히 프라이빗 레지스트리, ECR 같은 토큰 기반 레지스트리, 네임스페이스별 imagePullSecrets 분리, 노드 런타임 변경(containerd) 같은 조건이 겹치면 원인이 한 번에 안 보입니다.
이 글은 401이 의미하는 바를 정확히 쪼개고, 어떤 명령으로 무엇을 확인해야 하는지, 그리고 가장 안전하게 고치는 패턴을 정리합니다.
관련해서 더 빠른 체크리스트가 필요하면 다음 글도 함께 보세요.
- K8s ImagePullBackOff - ErrImagePull·401 빠른 해결
- EKS 환경에서 노드 상태 이상까지 함께 의심되면: EKS kubelet NotReady - PLEG is not healthy 7가지
1) 증상 정리: ImagePullBackOff와 401의 조합이 의미하는 것
ImagePullBackOff: 이미지 풀링이 실패했고 kubelet이 재시도 백오프 상태로 들어갔다는 뜻401 Unauthorized: 레지스트리가 인증 실패를 반환했다는 뜻
중요한 포인트는 401이 항상 “비밀번호가 틀렸다”가 아니라는 점입니다.
- 잘못된 자격 증명(아이디, 토큰, 패스워드)
- 자격 증명은 맞지만 권한이 없는 리포지토리 접근
- 토큰 만료(ECR의 대표 케이스)
- 레지스트리 URL 또는 이미지 레퍼런스가 달라서 다른 레지스트리로 인증을 시도
imagePullSecrets가 Pod가 뜨는 네임스페이스에 없거나 Pod에 연결되지 않음- ServiceAccount에
imagePullSecrets가 붙어있지 않아 특정 Pod에서만 실패
먼저 이벤트를 정확히 봐야 합니다.
kubectl -n <namespace> describe pod <pod-name>
MDX 환경에서는 부등호가 빌드 에러를 낼 수 있으니 위의 <namespace>, <pod-name> 같은 부분은 반드시 인라인 코드로 유지하세요.
Events 섹션에서 보통 다음 같은 메시지가 나옵니다.
Failed to pull image "...": rpc error: code = Unknown desc = Error response from daemon: unauthorized: authentication requiredfailed to resolve reference ...: unexpected status code 401 Unauthorized
여기서 레지스트리 호스트명이 무엇인지(예: registry.example.com, 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com)를 먼저 고정합니다. 원인 분류의 출발점입니다.
2) 가장 먼저 확인할 5가지 (체크리스트)
2.1 이미지 레퍼런스가 정확한가
의외로 401의 상당수는 “다른 레지스트리로 인증을 시도”하는 케이스입니다.
myrepo/app:tag처럼 호스트 없이 쓰면 Docker Hub로 간주될 수 있음registry.company.com/team/app와registry.company.com/teams/app같은 경로 오타latest태그가 실제로 private 정책에 의해 제한
배포 YAML에서 실제로 어떤 이미지가 지정됐는지 확인합니다.
kubectl -n <namespace> get deploy <deploy-name> -o jsonpath='{.spec.template.spec.containers[*].image}'
2.2 같은 네임스페이스에 imagePullSecret이 존재하는가
Secret은 네임스페이스 스코프입니다. default에 만들어 놓고 다른 네임스페이스에서 쓰면 100퍼 실패합니다.
kubectl -n <namespace> get secret
kubectl -n <namespace> get secret <secret-name> -o yaml
타입이 다음인지 확인하세요.
kubernetes.io/dockerconfigjson
2.3 Pod에 imagePullSecrets가 실제로 연결됐는가
Secret이 있어도 Pod 스펙에 연결하지 않으면 kubelet은 그 Secret을 쓰지 않습니다.
kubectl -n <namespace> get pod <pod-name> -o jsonpath='{.spec.imagePullSecrets[*].name}'
Deployment 템플릿에 없다면, ServiceAccount로 우회 연결했을 수도 있으니 아래도 확인합니다.
kubectl -n <namespace> get sa <serviceaccount-name> -o yaml
2.4 노드에서 레지스트리에 네트워크 접근이 되는가
네트워크 차단이면 보통 401이 아니라 타임아웃이지만, 프록시나 미들웨어가 401을 반환하는 환경도 있습니다.
노드에서 직접 확인이 가장 확실하지만, 보안상 노드 접속이 어렵다면 임시 디버그 Pod로 확인합니다.
kubectl -n <namespace> run net-debug --image=alpine:3.19 --restart=Never -- sleep 3600
kubectl -n <namespace> exec -it net-debug -- sh
Pod 내부에서 다음을 확인합니다.
apk add --no-cache curl
curl -I https://registry.example.com/v2/
정상이라면 보통 401 또는 200이 오는데, 여기서 중요한 건 “응답이 실제 레지스트리에서 오는지”입니다. WAF, 프록시, 사설 게이트웨이가 끼어 있다면 헤더나 인증 방식이 달라집니다.
2.5 노드 런타임(containerd)와 인증 포맷이 맞는가
대부분의 경우 쿠버네티스 Secret만 올바르면 런타임이 알아서 처리하지만, 일부 사내 레지스트리나 미러 구성에서는 containerd 설정(/etc/containerd/config.toml)과 충돌이 날 수 있습니다.
이 경우는 클러스터 표준 구성(이미지 미러, 프록시, 인증 플러그인)을 확인해야 합니다.
3) 표준 해결법 1: docker-registry Secret을 올바르게 만들기
가장 일반적인 프라이빗 레지스트리(사내 Harbor, Nexus, GitLab Registry 등)는 docker-registry 타입 Secret로 해결합니다.
3.1 Secret 생성
kubectl -n <namespace> create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=<username> \
--docker-password=<password-or-token> \
--docker-email=<email>
--docker-server는 이미지에 쓰는 호스트와 정확히 일치해야 합니다.- 토큰 기반이면
--docker-password에 토큰을 넣습니다.
3.2 Deployment에 연결
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: myns
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/team/app:1.0.0
적용 후 재배포합니다.
kubectl -n <namespace> apply -f deploy.yaml
kubectl -n <namespace> rollout restart deploy <deploy-name>
4) 표준 해결법 2: ServiceAccount에 imagePullSecrets 붙여서 운영 단순화
네임스페이스의 모든 워크로드가 같은 레지스트리를 쓴다면, 각 Deployment마다 imagePullSecrets를 반복하지 말고 ServiceAccount에 붙이는 편이 운영이 단순합니다.
kubectl -n <namespace> patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "regcred"}]}'
이후 default ServiceAccount를 쓰는 Pod들은 별도 설정 없이 Secret을 사용합니다.
주의할 점:
- 이미 생성된 Pod에는 즉시 반영되지 않을 수 있으니 재시작이 필요합니다.
- 워크로드별로 다른 권한이 필요하면 ServiceAccount를 분리하세요.
5) 레지스트리별 자주 터지는 401 원인과 처방
5.1 AWS ECR: 토큰 만료와 IRSA, 노드 IAM 권한
ECR은 docker login 토큰이 주기적으로 만료됩니다. 쿠버네티스 Secret에 ECR 토큰을 “고정값”으로 넣으면 시간이 지나면서 401이 다시 발생합니다.
권장 패턴은 다음 중 하나입니다.
- 노드 IAM Role에
ecr:GetAuthorizationToken,ecr:BatchGetImage,ecr:GetDownloadUrlForLayer등을 부여하고 kubelet이 자동으로 풀게 구성 - 혹은 ECR credential helper, 혹은 External Secrets와 CronJob으로 Secret을 갱신(운영 복잡도 증가)
EKS라면 보통 노드 IAM Role 권한 누락이 흔합니다. 노드 역할에 ECR 읽기 권한이 있는지 확인하세요.
5.2 GCR, Artifact Registry: Workload Identity 또는 JSON 키
GCP 쪽은 JSON 키를 Secret로 넣는 방식도 가능하지만, 장기적으로는 Workload Identity가 안정적입니다. JSON 키는 유출 리스크와 키 로테이션 부담이 큽니다.
5.3 ACR: Managed Identity 또는 SP 권한
AKS에서는 ACR과의 연결에서 Managed Identity 권한이 빠지면 401이 납니다. 또한 ACR 리포지토리 권한 범위가 구독, 리소스 그룹 정책에 의해 제한될 수 있습니다.
5.4 Docker Hub: 레이트 리밋과 인증 실패 혼동
Docker Hub는 인증 없이 풀링할 때 레이트 리밋이 걸리면 401 또는 429처럼 보이는 형태로 실패할 수 있습니다. 사내에서 NAT IP를 공유하면 더 빨리 터집니다.
- 사내 NAT 환경에서 특정 외부 서비스가 차단되는 사례는 다음 글의 접근 방식이 유사합니다: EKS Pod만 외부 API 403 - NAT IP·WAF로 해결
6) 디버깅을 빠르게 만드는 로그 포인트
6.1 kubelet 이벤트 중심으로 원인 좁히기
Pod 이벤트 외에 노드 이벤트도 봅니다.
kubectl describe node <node-name>
또한 컨테이너 런타임이 containerd라면, 노드에서 containerd 로그나 kubelet 로그에 더 자세한 인증 실패 이유가 남습니다.
6.2 “특정 노드에서만” 실패하는 경우
같은 Deployment인데 어떤 Pod는 뜨고 어떤 Pod는 401로 죽는다면, 대개 아래 중 하나입니다.
- 노드별로 프록시 설정이 다름
- 노드별로 DNS가 다르게 해석(다른 레지스트리로 붙음)
- 노드 AMI 또는 런타임 설정 차이
- 노드 IAM Role 또는 워커 노드 그룹별 권한 차이(EKS에서 흔함)
이 때는 실패한 Pod가 올라간 노드를 확인하고, 해당 노드에만 공통점이 있는지 비교합니다.
kubectl -n <namespace> get pod <pod-name> -o wide
7) 재발 방지 운영 팁
7.1 네임스페이스 온보딩 템플릿에 Secret과 SA 포함
새 네임스페이스를 만들 때마다
regcredSecret 생성- 기본 ServiceAccount 패치
를 자동화하면 휴먼 에러가 크게 줄어듭니다. GitOps를 쓰면 Kustomize나 Helm values로 표준화하세요.
7.2 이미지 레퍼런스 규칙 강제
- 반드시
registry-host/namespace/image:tag형태를 강제 latest금지
이 두 가지만으로도 “다른 레지스트리로 인증 시도” 문제를 상당히 제거합니다.
7.3 Secret 로테이션 전략
토큰 만료형 레지스트리(ECR 등)는 “Secret을 고정값으로 박아두는 방식” 자체가 장애를 예약합니다.
- 가능하면 노드 또는 워크로드 아이덴티티 기반으로 전환
- 불가하면 Secret 자동 갱신(External Secrets, CronJob)과 알람을 붙이기
8) 결론: 401은 인증만이 아니라 “연결 대상” 문제일 수 있다
ImagePullBackOff와 401을 보면 대부분은 imagePullSecrets를 떠올리지만, 실무에서는 다음 순서로 보는 것이 가장 빠릅니다.
- 이미지 레퍼런스의 레지스트리 호스트가 정확한가
- Secret이 같은 네임스페이스에 존재하는가
- Pod 또는 ServiceAccount에 Secret이 연결됐는가
- 레지스트리별 인증 특성(ECR 토큰 만료, IAM 권한)을 고려했는가
- 특정 노드에서만 재현된다면 노드별 설정 차이를 의심했는가
위 순서대로 점검하면, 대부분의 401 Unauthorized는 10분 안에 원인 분리가 가능합니다.