Published on

K8s ImagePullBackOff - registry auth·CA 10분 진단

Authors

서버가 멀쩡해 보이는데 Pod만 ImagePullBackOff로 멈추면, 대부분 원인은 두 갈래로 수렴합니다.

  1. Registry 인증(auth) 문제: imagePullSecrets, 토큰 만료, 잘못된 Secret 타입/네임스페이스, IRSA/ECR 권한 등
  2. TLS/CA 문제: 사설 레지스트리 인증서 체인 누락, 프록시/미들박스, 노드의 trust store 불일치 등

이 글은 kubectl 몇 줄과 노드/런타임 관점의 확인으로 10분 내에 auth vs CA를 분리하고, 가장 흔한 케이스를 빠르게 복구하는 절차를 제공합니다.

> 참고: x509 계열로 확정되면 아래 글도 함께 보면 원인 좁히기가 빨라집니다. EKS Pod에서 x509 unknown authority 오류 해결

0. 증상 정리: ImagePullBackOff는 “결과”일 뿐

ImagePullBackOff는 kubelet이 이미지를 당기려다 실패했고, 재시도 백오프(backoff)에 들어갔다는 상태입니다. 실제 원인은 이벤트에 적나라하게 찍힙니다.

가장 먼저 할 일은 Pod 이벤트에서 실패 메시지 원문을 확보하는 겁니다.

# 1) 이벤트 포함 상세 출력
kubectl -n <ns> describe pod <pod>

# 2) 이벤트만 빠르게 필터링
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 30

여기서 흔히 보는 메시지 패턴:

  • 인증 계열
    • Failed to pull image ... unauthorized: authentication required
    • denied: requested access to the resource is denied
    • pull access denied
  • CA/TLS 계열
    • x509: certificate signed by unknown authority
    • tls: failed to verify certificate
    • certificate is valid for ..., not <registry-host> (SAN 불일치)
  • 네트워크/이름해석 계열(이번 글의 범위 밖이지만 자주 섞임)
    • dial tcp ... i/o timeout
    • no such host

이제부터는 authCA를 각각 5분 내로 확인하는 루틴으로 나눕니다.

1. 1분 컷: “어떤 노드에서” 실패하는지부터 확인

동일 이미지/동일 Secret인데도 어떤 노드에선 되고 어떤 노드에선 안 되면, 대개 노드 런타임 설정(프록시/CA/credential helper) 문제입니다.

kubectl -n <ns> get pod <pod> -o wide
# NODE 컬럼 확인

노드가 특정 AZ/노드그룹에 편중되어 있으면, 해당 노드그룹 AMI/부트스트랩 설정 차이를 의심하세요.

2. Auth 5분 진단: imagePullSecrets와 레지스트리 권한

2.1 ServiceAccount에 imagePullSecrets가 붙어 있는지

가장 흔한 실수는 Secret은 만들었는데 SA에 연결을 안 한 경우입니다.

# Pod가 사용하는 ServiceAccount 확인
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.serviceAccountName}{"\n"}'

# 해당 SA에 imagePullSecrets 설정 확인
kubectl -n <ns> get sa <sa-name> -o yaml

SA에 아래가 없다면, Pod spec에 직접 붙였거나(혹은 아예 누락)입니다.

imagePullSecrets:
  - name: regcred

> 팁: 운영에서는 Pod마다 붙이기보다 SA에 기본으로 붙이는 편이 실수 확률이 낮습니다.

2.2 Secret 타입/내용이 올바른지 (dockerconfigjson)

K8s가 기대하는 타입은 보통 이겁니다.

  • kubernetes.io/dockerconfigjson
kubectl -n <ns> get secret regcred -o jsonpath='{.type}{"\n"}'
kubectl -n <ns> get secret regcred -o yaml

올바른 Secret 생성 예시:

kubectl -n <ns> create secret docker-registry regcred \
  --docker-server=<registry.example.com> \
  --docker-username=<user> \
  --docker-password='<password-or-token>' \
  --docker-email=<email>

그리고 Pod/Deployment에 연결:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      serviceAccountName: app-sa
      imagePullSecrets:
        - name: regcred
      containers:
        - name: app
          image: registry.example.com/team/app:1.2.3

2.3 네임스페이스 함정: Secret은 “같은 ns”에 있어야 함

imagePullSecretsPod과 같은 네임스페이스에서만 참조됩니다. kube-system에 만들어두고 default에서 당기려 하면 실패합니다.

kubectl get secret regcred -A | grep regcred

2.4 ECR 같은 클라우드 레지스트리: “노드/파드 권한” 문제 분리

ECR(또는 GCR/ACR)에서 unauthorized가 나면 두 가지가 섞입니다.

  • 토큰을 Secret으로 박아둔 경우: 만료되면 바로 ImagePullBackOff
  • 노드 IAM(Role) 기반으로 당겨야 하는데 권한이 없음: ecr:GetAuthorizationToken, ecr:BatchGetImage

EKS에서 특히 흔한 패턴:

  • Managed NodeGroup IAM Role에 ECR read 권한이 빠짐
  • Private ECR + VPC 엔드포인트/라우팅 문제(이건 네트워크 축)

권한 이슈는 이벤트에 denied/unauthorized로 나오지만, 정확한 정책 누락은 클라우드 로그/정책 확인이 필요합니다.

3. CA/TLS 5분 진단: x509, SAN, 체인, 런타임 trust

이 구간은 에러 메시지가 꽤 직설적입니다.

  • x509: certificate signed by unknown authorityCA 체인 신뢰 실패
  • certificate is valid for A, not B인증서 SAN/CN에 레지스트리 호스트명이 없음
  • remote error: tls: handshake failureTLS 버전/암호군/중간장비 가능성

3.1 Pod 이벤트에서 x509 문구 확인

kubectl -n <ns> describe pod <pod> | sed -n '/Events:/,$p'

여기서 x509가 보이면, auth보다 먼저 CA/trust를 의심하는 게 시간 절약입니다.

3.2 노드에서 레지스트리 TLS를 직접 확인 (openssl)

가능하면 실패가 발생한 해당 노드에 접속해서 확인합니다.

# 레지스트리 인증서 체인/호스트 매칭 확인
openssl s_client -connect <registry.example.com>:443 -servername <registry.example.com> -showcerts </dev/null

확인 포인트:

  • Verify return code: 0 (ok) 인가?
  • 체인이 중간 인증서까지 포함되어 전달되는가?
  • 인증서 Subject Alternative Name에 <registry.example.com>이 있는가?

3.3 containerd/Docker가 신뢰하는 CA는 “노드 OS/런타임 설정”에 달림

Kubelet은 이미지를 직접 당기지 않고, 노드의 컨테이너 런타임(containerd/Docker/CRI-O)에 위임합니다. 즉 노드가 신뢰하지 않는 CA면 무조건 실패합니다.

  • 사내 프라이빗 레지스트리(자체 CA)
  • TLS를 프록시가 가로채는 환경(기업망 MITM)

이 경우 해결은 보통 둘 중 하나입니다.

  1. 노드 OS trust store에 CA 추가
  2. containerd에 레지스트리별 CA 설정

containerd 기준(경로는 배포판/버전에 따라 다름) 예시:

# 예: /etc/containerd/certs.d/<registry-host>/hosts.toml
sudo mkdir -p /etc/containerd/certs.d/registry.example.com

cat <<'EOF' | sudo tee /etc/containerd/certs.d/registry.example.com/hosts.toml
server = "https://registry.example.com"

[host."https://registry.example.com"]
  capabilities = ["pull", "resolve"]
  ca = "/etc/ssl/certs/company-root-ca.crt"
EOF

sudo systemctl restart containerd

> 운영 팁: 노드그룹 AMI/부트스트랩(UserData)에 반영해 노드 교체 시에도 자동 적용되게 만드세요.

3.4 “임시 회피”로 insecure registry를 쓰면 안 되는 이유

긴급 장애에서 insecure_skip_verify 같은 설정을 떠올리기 쉽지만, 이는 공급망 공격에 취약해집니다. 최소한 사내 CA를 배포하는 방식으로 해결하는 게 정석입니다.

4. 재현/분리 테스트: 같은 노드에서 curl vs crictl

원인이 애매할 때는 같은 노드에서 네트워크/TLS와 런타임을 분리해 봅니다.

4.1 curl로 TLS만 확인

curl -Iv https://registry.example.com/v2/
  • 여기서도 x509가 나면: 노드 OS trust 문제
  • curl은 OK인데 pull만 실패하면: containerd trust 설정/인증 문제 가능

4.2 crictl로 실제 pull 확인

# 노드에서
sudo crictl pull registry.example.com/team/app:1.2.3

crictl에서 동일 에러가 나면 Kubernetes 오브젝트 문제가 아니라 노드 런타임/인증서/자격증명 문제로 확정할 수 있습니다.

5. 가장 흔한 원인 TOP 7과 빠른 처방

  1. imagePullSecrets 누락

    • 처방: SA 또는 Pod spec에 imagePullSecrets 추가
  2. Secret이 다른 네임스페이스에 존재

    • 처방: 동일 ns에 Secret 재생성
  3. dockerconfigjson 타입/키가 잘못됨

    • 처방: kubectl create secret docker-registry로 재생성
  4. 레지스트리 주소가 다름(호스트/포트/프로토콜)

    • 처방: Secret의 --docker-server와 image reference의 host를 정확히 일치
  5. ECR 토큰을 Secret으로 고정해 만료

    • 처방: 노드 IAM 기반 또는 External Secrets/컨트롤러로 자동 갱신
  6. 사설 CA 미배포로 x509 unknown authority

    • 처방: 노드 trust store/containerd certs.d에 CA 추가 (권장)
  7. 인증서 SAN 불일치(예: registry는 cert에 registry.internal만 있음)

    • 처방: 올바른 FQDN이 SAN에 포함된 인증서 재발급 또는 접근 도메인 정정

6. 운영 체크리스트: “10분 안에 끝내는” 순서 고정

아래 순서를 그대로 따르면, 대부분 10분 내에 결론이 납니다.

  1. kubectl describe pod에러 원문 확보 (1분)
  2. get pod -o wide특정 노드 편향 여부 확인 (1분)
  3. 인증 에러면
    • SA/Pod의 imagePullSecrets 확인
    • Secret 타입/네임스페이스 확인 (3분)
  4. x509/TLS 에러면
    • 노드에서 openssl s_client, curl -Iv로 체인/SAN 확인
    • containerd CA 설정/노드 trust store 반영 (5분)

EKS 환경에서 노드 상태/리소스 문제로 파드가 뜨지 못하는 케이스도 자주 섞이니, 이미지 풀 이전 단계에서 막힌 상황이라면 아래 글도 함께 점검하세요.

7. 마무리: 결론은 “이벤트 문자열”과 “노드 신뢰체인”

ImagePullBackOff는 Kubernetes의 고장이라기보다, **레지스트리 접근 경로(인증/네트워크/TLS)**가 어딘가에서 끊겼다는 신호입니다. 진단의 핵심은 두 가지입니다.

  • Pod 이벤트의 실패 메시지에서 auth vs x509를 즉시 분기
  • 같은 노드에서 curl/opensslcrictl pullOS trust vs 런타임 pull을 분리

이 루틴을 팀의 런북으로 고정해두면, 새벽 장애에서도 “레지스트리 인증서냐, 시크릿이냐”로 헤매는 시간을 크게 줄일 수 있습니다.