Private Registry SSL 장애 정복

Private Registry SSL 장애 정복

1. 배경 및 문제 상황

최근 프로젝트 운영 중 Kubernetes 환경에서 특정 Pod가 정상적으로 기동되지 못하고 ImagePullBackOff 오류를 반복적으로 발생시키는 상황이 발생하였습니다.

기존에 이미 실행 중이던 Pod들은 문제 없이 정상 동작하고 있었기 때문에 초기에는 Registry 자체 장애가 아닌 것으로 판단하였습니다.

하지만 새로운 Deployment 배포 이후 새롭게 생성되는 Pod들만 지속적으로 이미지 Pull 실패를 일으키고 있었고, 결국 신규 Pod는 정상적으로 기동되지 못하였습니다.

문제 원인을 확인하기 위해 kubelet 로그와 container runtime 로그를 분석한 결과, Private Registry와의 TLS 통신 과정에서 SSL 인증서 검증 실패가 발생하고 있다는 점을 확인하였습니다.

특히 “curl -v https://repo.example.com:11000/v2/” 명령을 통해 Registry 엔드포인트를 직접 호출해보는 과정에서 아래와 같은 오류 메시지를 발견하였습니다.

SSL certificate problem: unable to get local issuer certificate

이 메시지는 단순히 인증서가 만료되었다는 의미가 아니라, 해당 SSL 인증서를 발급한 CA(Certificate Authority)를 노드가 신뢰하지 못하고 있다는 의미입니다.

즉, Registry 서버는 정상적인 인증서를 사용하고 있었지만 Kubernetes 노드 내부의 신뢰 저장소(Trust Store)에 해당 인증기관 정보가 존재하지 않았기 때문에 TLS 검증이 실패한 상황이었습니다.

특히 Private Registry 환경에서는 내부망 전용 인증서 또는 사설 인증기관 기반 인증서를 사용하는 경우가 많습니다.

이 경우 운영자는 단순히 Registry만 구축하는 것이 아니라, Kubernetes Worker Node와 Container Runtime 환경에도 해당 인증기관 정보를 함께 등록해야 합니다.

하지만 초기 구축 단계에서는 이 부분이 누락되는 경우가 많으며, 신규 노드 증설이나 재배포 시점에 문제가 발생하는 사례가 빈번합니다.

또한 기존 Pod는 정상 동작하고 신규 Pod만 실패했던 이유는 이미지 캐시 때문입니다.

Container Runtime은 이미 로컬에 다운로드된 이미지를 재사용할 수 있기 때문에 기존 Pod는 Registry 접근 없이도 실행이 가능하였습니다.

반면 새롭게 생성되는 Pod는 Registry에 직접 접근하여 이미지를 Pull 해야 했고, 이 과정에서 SSL 검증 오류가 발생하였습니다.

이러한 문제는 Kubernetes 환경뿐 아니라 Docker, containerd, CRI-O, k3s 등 대부분의 Container Runtime 환경에서도 동일하게 발생할 수 있습니다.

따라서 SSL 인증 체계와 CA 신뢰 저장소 구조를 이해하는 것은 운영 환경 안정성을 위해 매우 중요합니다.

2. 문제 해결

(1) SSL 인증서 확인

문제 해결의 첫 번째 단계는 실제 Registry 서버가 어떤 인증서를 사용하고 있는지 확인하는 것입니다.

운영 환경에서는 인증서 체인이 여러 단계로 구성되는 경우가 많으며, 중간 인증서(Intermediate CA)가 누락되는 경우에도 동일한 문제가 발생할 수 있습니다.

먼저 openssl 명령어를 사용하여 인증서 Subject와 Issuer 정보를 확인합니다.

openssl s_client -connect repo.example.com:11000 /dev/null | openssl x509 -noout -subject –issuer

출력 예시는 다음과 같습니다.

subject=CN=*.example.com
issuer=C=GB; O=Sectigo Limited; CN=Sectigo Public Server Authentication CA DV R36

위 결과를 통해 실제 인증서를 발급한 기관이 Sectigo 계열 CA임을 확인할 수 있습니다.

이 과정은 단순히 인증서 만료 여부를 보는 것이 아니라, 어떤 인증기관을 신뢰해야 하는지 판단하기 위한 핵심 단계입니다.

다음 단계에서는 실제 인증서를 파일로 추출합니다.

openssl s_client -connect repo.example.com:11000 -showcerts /dev/null | sed -n 
'/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > 
SectigoPublicServerAuthenticationCA.crt

이 명령은 Registry 서버가 제공하는 전체 인증서 체인을 파일로 저장합니다.

환경에 따라 루트 인증서와 중간 인증서가 함께 포함될 수 있으며, 일부 환경에서는 여러 개의 인증서가 동시에 추출됩니다.

추출된 인증서 개수는 아래 명령으로 확인할 수 있습니다.

grep -c 'BEGIN CERTIFICATE' SectigoPublicServerAuthenticationCA.crt

인증서 체인이 여러 개일 경우에는 각 인증서가 모두 정상적으로 포함되어 있는지 반드시 확인해야 합니다.

특히 중간 인증서가 누락되면 브라우저에서는 정상 동작하더라도 Container Runtime에서는 검증 실패가 발생할 수 있습니다.

(2) CA 인증서 시스템 등록 (Ubuntu / Debian 계열)

가장 권장되는 해결 방법은 노드의 시스템 신뢰 저장소에 CA 인증서를 등록하는 것입니다.

이 방법은 TLS 검증을 유지하면서도 안정적으로 Registry와 통신할 수 있기 때문에 운영 환경에서 가장 안전한 방식입니다.

먼저 추출한 인증서를 시스템 CA 저장소 경로로 복사합니다.

sudo cp SectigoPublicServerAuthenticationCA.crt /usr/local/share/ca-certificates/

이후 update-ca-certificates 명령을 통해 시스템 신뢰 저장소를 갱신합니다.

sudo update-ca-certificates

이 작업이 완료되면 Ubuntu 또는 Debian 계열 시스템은 /etc/ssl/certs 내부에 심볼릭 링크를 자동 생성하고 전체 CA 번들을 재구성합니다.

정상 등록 여부는 아래 명령으로 확인할 수 있습니다.

openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt SectigoPublicServerAuthenticationCA.crt

정상적으로 등록되었다면 “OK” 메시지가 출력됩니다.

이후 containerd 또는 Docker Runtime을 재시작하면 대부분의 환경에서 Image Pull 문제가 해결됩니다.

운영 환경에서는 이 방식을 사용하는 것이 매우 중요합니다.

insecure 설정은 보안 검증 자체를 우회하는 방식이기 때문에 장기적으로 사용하면 MITM(Man-In-The-Middle) 공격에 취약해질 수 있습니다.

또한 Auto Scaling 환경이나 신규 Node Provisioning 환경에서는 cloud-init, Ansible, Terraform, Kubernetes bootstrap 스크립트 등을 통해 CA 인증서를 자동 배포하는 체계를 구축하는 것이 좋습니다.

(3) containerd Insecure 설정

일부 환경에서는 노드에 직접 SSH 접근이 불가능하거나 긴급 장애 상황으로 인해 빠른 복구가 우선시되는 경우가 있습니다.

이 경우 containerd 설정에서 SSL 검증을 임시로 비활성화할 수 있습니다.

먼저 Registry 전용 certs.d 디렉토리를 생성합니다.

mkdir -p /etc/containerd/certs.d/repo.example.com:11000

이후 hosts.toml 파일을 생성합니다.

server = https://repo.example.com:11000
[host."https://repo.example.com:11000"]
capabilities = ["pull", "resolve"]
skip_verify = true

여기서 skip_verify = true 옵션이 핵심입니다. 이 설정은 Registry 인증서 검증을 수행하지 않도록 강제합니다. 설정 반영 이후에는 반드시 containerd를 재시작해야 합니다.

sudo systemctl restart containerd

이 방식은 빠른 장애 복구에는 유용하지만 운영 환경에서 장기적으로 사용하는 것은 권장되지 않습니다.

특히 보안 감사 또는 금융/공공 환경에서는 insecure 설정 자체가 정책 위반이 될 수 있습니다.또한 Registry 인증서를 위조하더라도 검증 없이 연결이 허용될 수 있기 때문에 보안 위험성이 존재합니다.따라서 이 방식은 임시 조치 또는 개발 환경에서만 제한적으로 사용하는 것이 바람직합니다.

(4) k3s registries.yaml Insecure 설정

k3s 환경에서는 일반 Kubernetes와 달리 내부적으로 containerd 설정을 자동 관리합니다.따라서 직접 containerd 설정을 수정하더라도 k3s 재시작 시 덮어써질 수 있습니다.

이 경우에는 /etc/rancher/k3s/registries.yaml 파일을 사용해야 합니다.

mirrors:
 "repo.example.com:11000":
 endpoint:
  - "https://repo.example.com:11000"

configs:
 "repo.example.com:11000":
   auth:
    username: "your-username"
    password: "your-password"
   tls:
     insecure_skip_verify: true


이 설정은 k3s가 내부적으로 containerd 설정을 생성할 때 자동 반영됩니다.

설정 적용 후에는 k3s 서비스를 재시작합니다.

sudo systemctl restart k3s

특히 Edge 환경이나 경량 Kubernetes 환경에서는 k3s를 사용하는 사례가 많기 때문에 registries.yaml 구조를 이해하는 것이 중요합니다.

또한 운영 환경에서는 인증 정보(username/password)를 평문으로 저장하는 대신 Kubernetes Secret 또는 Vault 기반 인증 연계를 고려하는 것이 좋습니다.

3. 정리

이번 장애 대응 과정에서는 Private Registry 환경에서 발생할 수 있는 대표적인 SSL 인증 문제를 분석하고 해결하는 과정을 경험하였습니다.

문제의 핵심 원인은 Registry 서버 인증서를 발급한 CA가 Kubernetes Node의 신뢰 저장소에 존재하지 않았기 때문입니다.

기존 Pod는 이미지 캐시 덕분에 정상 동작하였지만 신규 Pod는 Registry 접근 과정에서 SSL 검증 실패가 발생하였습니다.

이를 해결하기 위해 먼저 openssl을 사용하여 인증서 발급자와 인증서 체인을 확인하였고, 실제 인증서를 추출하여 시스템 신뢰 저장소에 등록하는 방법을 적용하였습니다.

또한 긴급 상황 대응을 위해 containerd hosts.toml 기반 insecure 설정과 k3s registries.yaml 기반 insecure_skip_verify 설정 방법도 함께 확인하였습니다.

하지만 운영 환경에서는 반드시 CA 인증서를 정식 등록하여 TLS 검증 체계를 유지하는 것이 가장 중요합니다.

insecure 설정은 임시 장애 대응 수단으로만 활용해야 하며, 장기 사용은 보안 취약점으로 이어질 수 있습니다.

결국 안정적인 Kubernetes 운영을 위해서는 단순한 애플리케이션 배포뿐 아니라 SSL 인증 체계, 인증기관 신뢰 구조, Container Runtime 동작 방식까지 함께 이해하는 것이 매우 중요하다는 점을 확인할 수 있었습니다.

Jsia

Site footer