Published on

K8s ImagePullBackOff - ErrImagePull·401 빠른 해결

Authors

서버가 멀쩡한데 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 required
  • failed to resolve reference ...: unexpected status code [manifests ...]: 401 Unauthorized
  • pull 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:tag vs my-registry.com/team/app:TAG
  • docker.io 생략 시 의도치 않게 Docker Hub로 해석

2.5 ECR/GCR/ACR 등 “토큰 만료형” 레지스트리

예: ECR은 로그인 토큰이 만료됩니다(기본 12시간). 토큰을 Secret로 박아두면 시간 지나서 401이 뜹니다.

2.6 노드/런타임 단에서 프록시·CA·TLS 문제 → 인증 실패로 보이는 경우

사설 레지스트리 + 사설 CA 환경에서 인증서 신뢰 문제가 401/403처럼 보이기도 합니다(실제론 x509 에러가 더 흔하지만, 프록시가 중간에서 401로 변환하는 경우도 있음).

3) 가장 빠른 해결 루틴(체크리스트)

아래 순서대로 보면 보통 10~15분 내로 원인이 잡힙니다.

  1. describe pod 이벤트에서 정확한 registry hoststatus code 확인
  2. 이미지가 실제로 존재하는지(레지스트리 UI/CLI) 확인
  3. Secret이 같은 namespace에 있는지 확인
  4. Pod/SA에 imagePullSecrets가 연결돼 있는지 확인
  5. (ECR 등) 토큰 만료형이면 자동 갱신 방식으로 전환
  6. 노드에서 네트워크/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 Roleecr: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로 반환

진단 팁:

  1. 노드에서 직접 curl -v https://my-registry.../v2/를 실행해 TLS/인증 흐름 확인
  2. containerd 사용 시 /etc/containerd/certs.d/<registry>/hosts.toml 설정 확인(환경별)
  3. 사설 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축으로만 압축해도 해결이 빨라집니다.

  1. Secret: 올바른 타입/키로, 올바른 namespace에 존재하는가?
  2. 권한: 레지스트리(IAM/Repo Policy)에서 Pull 권한이 실제로 있는가?
  3. 연결: Pod 또는 ServiceAccount에 imagePullSecrets가 연결되어 있는가?

이 3가지를 describe pod 이벤트와 함께 교차 확인하면, 대부분의 ImagePullBackOff는 “원인 파악 → 수정 → 재배포”까지 한 번에 끝낼 수 있습니다.