운영 중인 서비스 메쉬 환경에서 갑작스럽게 503 Service Unavailable 에러가 솟구치는 상황은 모든 엔지니어의 악몽입니다. 특히 로그에 upstream connect error or disconnect/reset before headers. reset reason: connection termination이라는 메시지가 찍힌다면, 이는 단순한 애플리케이션 오류가 아닙니다. 최근 사내 결제 시스템의 마이크로서비스 간 통신에서 이 현상이 발생하여 트래픽의 약 15%가 드랍되는 장애를 겪었습니다. 이 글에서는 Istio mTLS 핸드셰이크 과정에서 발생한 인증서 만료 문제를 추적한 과정과, 이를 영구적으로 해결하기 위해 도입한 자동화 전략을 공유합니다.
증상 분석 및 환경 스펙
문제의 환경은 AWS EKS(Kubernetes v1.28) 위에 구축된 대규모 클러스터로, 약 200여 개의 마이크로서비스가 Istio v1.19 기반의 서비스 메쉬로 연결되어 있었습니다. 평소 5,000 TPS 정도를 처리하던 주문(Order) 서비스가 재고(Inventory) 서비스로 요청을 보낼 때 간헐적인 연결 끊김이 발생했습니다.
초기 분석 시 애플리케이션 힙 메모리나 CPU 스로틀링은 정상 범위였습니다. 그러나 Envoy 사이드카의 액세스 로그를 분석한 결과, 목적지 서비스(Inventory) 도달 전에 연결이 종료되고 있음을 확인했습니다. 이는 전형적인 서비스 메쉬 트러블슈팅 시나리오 중 하나인 mTLS(상호 TLS) 인증 실패의 징후였습니다.
[2025-01-XXT10:00:00.000Z] "POST /api/v1/inventory HTTP/1.1" 503 UC upstream_reset_before_response_started{connection_termination} - "Order-Service" ...
Istio는 기본적으로 모든 서비스 간 통신을 mTLS로 암호화합니다. 이때 각 파드(Pod)에 주입된 Envoy 프록시는 istiod(Control Plane)로부터 발급받은 인증서를 사용해 상호 신원을 검증합니다. 만약 이 인증서가 만료되었거나, 소스(Source)와 데스티네이션(Destination) 간의 인증서 체인이 일치하지 않으면 연결은 즉시 차단됩니다. 우리는 k8s 보안 규정상 STRICT 모드를 사용하고 있었기에, 평문 통신으로의 폴백(fallback)도 불가능한 상황이었습니다.
실패한 접근: 단순 파드 재시작과 정책 완화
장애 발생 직후, 급한 불을 끄기 위해 kubectl rollout restart deployment inventory-service 명령어로 파드를 재배포했습니다. 이 조치는 일시적으로 효과가 있었습니다. 파드가 새로 생성되면서 istiod로부터 새로운 인증서를 받아왔기 때문입니다.
하지만 정확히 24시간 후 동일한 문제가 재발했습니다. 근본적인 원인은 istiod 자체가 발급하는 워크로드 인증서의 자동 갱신 로직(Rotation)이 특정 노드에서 데몬셋(DaemonSet) 형태의 노드 에이전트와 통신 문제로 실패하고 있었기 때문입니다. 또한, 디버깅을 위해 잠시 PeerAuthentication을 PERMISSIVE로 변경하여 mTLS를 우회하려는 시도도 했으나, 이는 제로 트러스트 보안 정책 위반으로 즉시 롤백해야 했습니다. 단순 재시작은 '기술 부채'만 쌓을 뿐 해결책이 아니었습니다.
Istio 디버깅 및 Cert-Manager 통합 솔루션
이 문제를 근본적으로 해결하기 위해 두 단계의 접근을 취했습니다. 첫째는 istioctl을 활용한 정확한 인증서 상태 진단이고, 둘째는 인증서 관리의 신뢰성을 높이기 위해 외부 CA(Cert-Manager)를 연동하는 것입니다.
1단계: istioctl을 이용한 인증서 정합성 검증
문제 발생 시 가장 먼저 수행해야 할 명령어는 istioctl proxy-status와 proxy-config secret입니다. 이를 통해 현재 Envoy가 메모리에 들고 있는 인증서의 만료 시간을 확인할 수 있습니다.
// 1. 동기화 상태 확인 (SYNCED가 아니면 Control Plane 통신 문제)
$ istioctl proxy-status
NAME CDS LDS EDS RDS ISTIOD
order-service-7f6d...2.default SYNCED SYNCED SYNCED SYNCED istiod-6c8...
// 2. 특정 파드의 로드된 인증서 상세 조회
$ istioctl proxy-config secret order-service-7f6d...2.default
RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
default Cert Chain ACTIVE true 139205... 2025-05-12T10:00:00Z 2025-05-11T10:00:00Z
ROOTCA CA ACTIVE true 829103... 2035-05-10T10:00:00Z 2025-05-10T10:00:00Z
위 명령어를 통해 NOT AFTER 시간이 현재 시간보다 과거이거나 임박했다면 갱신 실패가 원인입니다. 만약 Istio 디버깅 과정에서 상태가 STALE로 나온다면 istiod와 Envoy 간의 xDS 연결이 불안정한 것입니다.
2단계: Cert-Manager를 이용한 인증서 파이프라인 구축
Istio의 내장 CA(Citadel) 대신, cert-manager를 연동하면 인증서 수명 주기 관리를 Kubernetes 네이티브 리소스로 제어할 수 있으며, 모니터링이 훨씬 수월해집니다. 이를 위해 Istio CSR(Certificate Signing Request) 에이전트를 설치하여 cert-manager가 Istio 워크로드의 인증서를 발급하도록 구성했습니다.
아래는 cert-manager를 Istio의 Root CA로 설정하기 위한 Issuer 및 IstioOperator 설정 예시입니다.
// Namespace: cert-manager
// istio-system 네임스페이스에 대한 인증서 발급 권한을 가진 Issuer 정의
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-root-issuer
namespace: istio-system
spec:
selfSigned: {}
---
// 실제 워크로드 인증서 서명에 사용될 CA 인증서 생성
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: istio-ca
namespace: istio-system
spec:
isCA: true
commonName: istio-system
secretName: istio-ca-secret
issuerRef:
name: selfsigned-root-issuer
kind: Issuer
group: cert-manager.io
---
// IstioOperator 설정 (istio-csr 연동)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: istio-control-plane
spec:
values:
global:
# Istio가 자체 CA 대신 외부 CA 인증서를 마운트하도록 설정
caAddress: cert-manager-istio-csr.cert-manager.svc:443
components:
pilot:
k8s:
env:
# istiod가 내장 CA를 비활성화하도록 설정
- name: ENABLE_CA_SERVER
value: "false"
위 설정에서 핵심은 ENABLE_CA_SERVER를 false로 설정하여 istiod가 직접 인증서를 발급하지 않도록 막고, caAddress를 통해 모든 CSR 요청을 cert-manager 쪽으로 라우팅하는 것입니다. 이를 통해 인증서 갱신 로직이 실패할 경우 cert-manager의 CertificateRequest 리소스 상태를 통해 즉각적인 알림(Alert)을 받을 수 있습니다.
| 비교 항목 | 기존 (Istiod Built-in CA) | 개선 (Cert-Manager + Istio CSR) |
|---|---|---|
| 가시성 | 로그 확인 필요 (블랙박스) | K8s 리소스(Certificate)로 상태 조회 가능 |
| 장애 대응 | 문제 발생 후 파드 재시작 | 갱신 실패 시 Prometheus Alert 발생 |
| 보안성 | 자체 서명된 루트 CA 사용 | Vault 등 외부 PKI 연동 용이 |
개선된 아키텍처 적용 후, 인증서 갱신 실패로 인한 다운타임은 0건으로 감소했습니다. 특히 cert-manager의 메트릭을 통해 인증서 만료 30일 전, 15일 전 경고를 설정함으로써 선제적인 대응이 가능해졌습니다. 이는 단순한 버그 픽스가 아니라, 운영 프로세스를 '반응형'에서 '예방형'으로 전환한 사례입니다. 만약 비슷한 고민을 하고 계시다면 Kubernetes 보안 모범 사례 포스팅도 함께 참고하시면 도움이 될 것입니다.
Check Istio-CSR Documentation주의사항 및 엣지 케이스
이 솔루션을 적용할 때 주의해야 할 몇 가지 엣지 케이스가 있습니다.
- Clock Skew (시간 동기화): Kubernetes 노드 간의 시스템 시간이 동기화되지 않으면(NTP 문제), 방금 발급받은 인증서가 "아직 유효하지 않음(Not Before)" 상태로 인식되어 mTLS 핸드셰이크가 즉시 실패할 수 있습니다. 노드 모니터링에 NTP 오프셋 항목을 반드시 포함해야 합니다.
- Istio 업그레이드 호환성: Istio의 마이너 버전이 올라갈 때마다
istio-csr과의 호환성을 확인해야 합니다. 특정 버전에서는 사이드카 인젝터의 템플릿 변경이 필요할 수 있습니다. - 콜드 스타트 지연: cert-manager의 웹훅이 응답하지 않으면, 새로운 파드가
Pending상태에서 멈출 수 있습니다. cert-manager 자체의 고가용성(HA) 구성이 필수적입니다.
결론
Istio 환경에서의 upstream connect error는 네트워크 문제가 아닌 인증/인가 계층의 문제일 가능성이 높습니다. Istio mTLS 구조를 정확히 이해하고 istioctl을 통한 분석 능력을 갖추는 것이 해결의 첫걸음입니다. 더 나아가 인증서 관리를 cert-manager와 같은 전문 도구에 위임함으로써, 복잡한 서비스 메쉬 환경에서도 k8s 보안과 가용성을 동시에 확보할 수 있습니다. 이제 인증서 만료로 인한 새벽 호출에서 해방되시길 바랍니다.
Post a Comment