Published on

Kubernetes ErrImagePull x509 인증서 오류 해결

Authors

서버가 정상인데도 Pod가 ErrImagePull/ImagePullBackOff로 멈추고, 이벤트에 x509: certificate signed by unknown authority가 찍히는 순간부터 운영자는 시간이 급격히 소모됩니다. 특히 사설 레지스트리(하버/아티팩토리), 프록시가 낀 네트워크, 혹은 기업 보안장비가 TLS를 가로채는 환경에서는 “클러스터는 멀쩡한데 특정 노드에서만 실패” 같은 비대칭 증상이 흔합니다.

이 글은 Kubernetes 이미지 Pull 시점의 x509 오류를 “어디에서 TLS 검증이 일어나는지” 관점으로 쪼개서, 가장 빠른 진단 루트 → 원인별 해결 → 재발 방지 체크리스트 순서로 정리합니다.

증상 정리: 에러 문자열로 범위를 좁히기

대표적인 이벤트/로그 패턴은 아래와 같습니다.

  • Failed to pull image ... x509: certificate signed by unknown authority
  • x509: certificate has expired or is not yet valid
  • x509: certificate is valid for ..., not <registry-host>
  • tls: failed to verify certificate: x509 ...

핵심은 “누가” 레지스트리와 TLS 핸드셰이크를 하느냐입니다.

  • Docker 사용 노드: dockerd가 검증
  • containerd 사용 노드(EKS, 대부분의 최신 배포판): containerd가 검증
  • 노드가 아니라 Pod 내부에서 curl이 실패하는 경우도 있지만, ErrImagePull은 기본적으로 노드 런타임 레벨 문제입니다.

1분 진단: kubectl로 이벤트부터 확인

먼저 Pod 이벤트에서 정확한 레지스트리 호스트명과 에러를 확보합니다.

kubectl describe pod <pod> -n <ns>

출력 중 다음 섹션을 봅니다.

  • Events:
    • Failed to pull image ...
    • Back-off pulling image ...

여기서 레지스트리가 registry.example.com:5000인지, 혹은 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com인지에 따라 해결책이 달라집니다.

어디서 깨지는가: 노드에서 직접 확인

ErrImagePull이면 거의 항상 노드에서 레지스트리로 나가는 TLS가 실패합니다. 문제 재현을 위해 해당 Pod가 스케줄된 노드로 들어가 확인합니다.

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

노드에 접속한 뒤(SSH 혹은 SSM), 레지스트리 인증서를 직접 확인합니다.

# 레지스트리 인증서 체인 확인
openssl s_client -connect registry.example.com:5000 -showcerts </dev/null

# 호스트네임 검증까지 포함해 확인(권장)
curl -v https://registry.example.com:5000/v2/
  • openssl s_client에서 서버가 어떤 인증서 체인(중간 CA 포함)을 내리는지 확인
  • curl에서 시스템 CA 번들로 검증이 되는지 확인

여기서 curl도 같은 x509 에러가 나면, 거의 확정적으로 노드 OS의 CA 번들 문제 또는 프록시/보안장비의 MITM 인증서 미신뢰입니다.

원인 1) 사설 레지스트리 CA(사내 Root/Intermediate) 미설치

가장 흔한 케이스입니다.

  • 사설 레지스트리가 사내 CA로 발급된 인증서를 사용
  • 노드 OS는 해당 CA를 신뢰하지 않음
  • containerd/dockerd가 시스템 CA를 기반으로 검증하다 실패

해결 A: 노드 OS에 CA 추가 (가장 정석)

배포판별로 CA 설치 위치가 다릅니다.

Ubuntu/Debian

sudo cp corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt
sudo update-ca-certificates

# 확인
grep -R "Corp Root" -n /etc/ssl/certs | head

Amazon Linux 2 / RHEL 계열

sudo cp corp-root-ca.crt /etc/pki/ca-trust/source/anchors/corp-root-ca.crt
sudo update-ca-trust extract

# 확인
trust list | grep -i corp -n

이후 containerd/dockerd 재시작이 필요할 수 있습니다.

sudo systemctl restart containerd
# 또는
sudo systemctl restart docker

해결 B: containerd에 레지스트리별 CA 지정

노드 전체 CA를 건드리기 어렵거나, 특정 레지스트리만 별도 체인을 쓰는 경우 containerd에 호스트별 CA를 지정할 수 있습니다.

containerd는 보통 다음 경로를 사용합니다.

  • /etc/containerd/config.toml
  • /etc/containerd/certs.d/<registry-host>:<port>/hosts.toml

hosts.toml 방식이 관리에 유리합니다.

sudo mkdir -p /etc/containerd/certs.d/registry.example.com:5000
sudo tee /etc/containerd/certs.d/registry.example.com:5000/hosts.toml > /dev/null <<'EOF'
server = "https://registry.example.com:5000"

[host."https://registry.example.com:5000"]
  capabilities = ["pull", "resolve"]
  ca = "/etc/containerd/certs.d/registry.example.com:5000/ca.crt"
EOF

sudo cp corp-root-ca.crt /etc/containerd/certs.d/registry.example.com:5000/ca.crt
sudo systemctl restart containerd

> 환경에 따라 capabilitiespush가 필요할 수 있습니다.

원인 2) 레지스트리가 중간 인증서(Intermediate)를 누락

서버는 리프(leaf) 인증서만 주고, 중간 인증서를 제대로 제공하지 않으면 어떤 클라이언트는 체인 빌드에 실패합니다. 브라우저는 AIA로 중간 인증서를 가져와서 “되는 것처럼” 보이기도 하지만, 런타임은 실패할 수 있습니다.

진단

openssl s_client -connect registry.example.com:443 -showcerts </dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | wc -l

인증서가 1개만 나오거나, 기대하는 중간 CA가 없다면 레지스트리(혹은 앞단의 LB/Ingress) 설정 문제입니다.

해결

  • NGINX/Envoy/LB에서 fullchain.pem(leaf + intermediate)을 사용
  • Harbor/Artifactory 등 레지스트리 제품의 TLS 설정에서 체인을 포함하도록 구성

원인 3) 인증서 만료/시간 불일치 (Not Yet Valid 포함)

노드 시간이 틀어져도 x509: certificate has expired or is not yet valid가 발생합니다. 특히 프라이빗 서브넷에서 NTP가 막혀 있거나, 부팅 직후 시간이 튀는 경우에 재현됩니다.

진단

date -u

# systemd-timesyncd/chrony 상태 확인(환경별)
timedatectl status
chronyc tracking 2>/dev/null || true

해결

  • 노드가 NTP에 접근 가능하도록 라우팅/보안그룹/방화벽 점검
  • chrony 사용 시 내부 NTP 서버를 지정

원인 4) 인증서 SAN 불일치 (호스트명/도메인 문제)

x509: certificate is valid for ... not registry.example.com 형태라면,

  • 레지스트리에 접근하는 주소(예: registry.internal)와
  • 인증서 SAN에 들어있는 도메인(예: registry.example.com)

이 다릅니다.

해결

  • 이미지 레퍼런스의 레지스트리 호스트명을 인증서 SAN에 맞추기
  • 혹은 인증서를 재발급하여 SAN에 실제 접근 도메인을 포함
  • 내부 DNS(CNAME)로 우회하는 경우에도 SAN 포함이 필요

원인 5) 프록시/보안장비의 TLS 가로채기(MITM)

기업 네트워크에서 흔합니다.

  • 노드가 외부로 나갈 때 프록시가 TLS를 재서명
  • 노드가 프록시의 Root CA를 신뢰하지 않음
  • 결과적으로 레지스트리 Pull이 x509로 실패

진단 포인트

  • openssl s_client로 본 인증서 발급자가 공인 CA가 아니라 사내 프록시/보안장비로 표시
  • 같은 레지스트리도 사내망에서만 실패

해결

  • 노드 OS에 프록시 Root CA 설치(원인 1의 방법과 동일)
  • containerd에 프록시 CA를 레지스트리별로 지정
  • 프록시를 우회하도록 네트워크 정책/라우팅 구성(가능한 경우)

추가로, 프록시 환경에서는 인증서 문제 외에도 gRPC/HTTP2 레벨에서 문제가 증폭될 수 있습니다. 서비스 통신에서 UNAVAILABLERST_STREAM이 함께 보인다면 네트워크 계층 튜닝도 같이 확인하는 편이 좋습니다: Kubernetes gRPC UNAVAILABLE·RST_STREAM 원인과 Envoy·NGINX 대응

원인 6) EKS/Managed Node Group에서 “노드별 편차”가 나는 경우

EKS에서 특히 헷갈리는 지점은, 같은 클러스터인데 특정 노드그룹/특정 노드만 ErrImagePull이 나는 케이스입니다.

가능성이 높은 순서:

  1. 노드 AMI/OS 이미지가 달라 CA 번들이 다름
  2. UserData/부트스트랩 스크립트에서 CA 설치가 누락됨
  3. 특정 서브넷만 프록시 경로를 타거나, NAT/방화벽이 다름
  4. 노드그룹 롤링 도중 일부 노드만 최신 설정 적용

해결: UserData로 CA 설치를 “항상” 적용

Managed Node Group이라도 부트스트랩에 CA 설치를 넣어야 재발을 막을 수 있습니다. 예시는 Amazon Linux 2 기준입니다.

#!/bin/bash
set -euo pipefail

# 1) CA 배치
cat >/etc/pki/ca-trust/source/anchors/corp-root-ca.crt <<'EOF'
-----BEGIN CERTIFICATE-----
...생략...
-----END CERTIFICATE-----
EOF

# 2) CA 반영
update-ca-trust extract

# 3) containerd 재시작
systemctl restart containerd || true

EKS 운영 중 다른 인증/권한 이슈(예: IRSA 전면 실패)가 함께 터지면 원인이 섞여 보이기도 합니다. OIDC Provider 삭제로 IRSA가 전부 실패했던 복구 시나리오는 아래 글이 도움이 됩니다: EKS OIDC Provider 삭제로 IRSA 전부 실패했을 때 복구

(비권장) insecure registry 설정은 최후의 수단

급한 장애 대응에서 insecure_skip_verify 또는 insecure registry로 우회하고 싶은 유혹이 큽니다. 하지만 이는 공격 표면을 크게 늘리고, 공급망 보안 관점에서 매우 위험합니다.

그래도 내부 폐쇄망/임시 대응으로 꼭 필요하다면, 최소한 아래 원칙을 지키세요.

  • 특정 레지스트리 호스트에만 제한
  • 임시 조치임을 이슈/런북에 명확히 남기고 만료일 설정
  • 가능한 한 빨리 CA 배포로 정상화

containerd의 hosts.toml 예(정말 최후의 수단):

server = "https://registry.example.com:5000"

[host."https://registry.example.com:5000"]
  capabilities = ["pull", "resolve"]
  skip_verify = true

재발 방지 체크리스트

운영 환경에서 ErrImagePull x509는 “한 번 고치고 끝”이 아니라, 노드 교체/오토스케일/롤링 업데이트 때 재발합니다. 아래를 표준화하면 장애 확률이 크게 줄어듭니다.

  1. CA 배포 자동화: AMI 베이크(Packer) 또는 Node UserData로 강제
  2. 레지스트리 인증서 체인 점검: leaf + intermediate 포함(fullchain) 상시 검증
  3. 노드 시간 동기화 보장: NTP 접근 경로/보안그룹/프록시 정책 문서화
  4. 노드 런타임 표준화: containerd 버전/설정 경로 통일
  5. 네트워크 경로 가시화: 서브넷별 프록시/NAT 차이를 다이어그램으로 관리
  6. 진단 커맨드 런북화: kubectl describe, openssl s_client, curl -v를 템플릿으로 공유

빠른 결론: 가장 많이 먹히는 해결 순서

  • (1) kubectl describe pod로 레지스트리 호스트/정확한 x509 메시지 확보
  • (2) 문제 노드에서 openssl s_client로 “실제 내려오는 인증서 체인” 확인
  • (3) 노드 OS에 사내 Root/Intermediate CA 설치 후 containerd 재시작
  • (4) 여전히 실패하면 레지스트리의 중간 인증서 누락SAN 불일치를 점검
  • (5) 기업 프록시/MITM 환경이면 프록시 Root CA 배포가 정답

이미지 Pull은 클러스터의 첫 관문입니다. x509 오류를 단순히 “인증서 문제”로 뭉뚱그리기보다, 노드 런타임(containerd) 관점에서 체인/시간/SAN/프록시를 분해해 보면 대부분 10~20분 안에 원인까지 도달할 수 있습니다.