문제 상황#

EKS Staging 환경에서 Java 서비스들이 CrashLoopBackOff 상태로 시작 실패. 로그 분석 결과 OTEL (OpenTelemetry) agent가 OTEL Collector에 연결 실패하는 문제 발견.

환경#

  • Kubernetes: EKS 1.34
  • Istio: mTLS STRICT 모드 활성화
  • OTEL Agent: opentelemetry-javaagent v2.11.0
  • OTEL Collector: opentelemetry-collector (Deployment)
  • Namespace: staging-webs (앱), monitoring (OTEL Collector)

증상#

Pod 상태#

staging-webs  auth-guard-xxx   0/1   CrashLoopBackOff   17
staging-webs  order-core-xxx   0/1   CrashLoopBackOff   21
staging-webs  queue-xxx        0/1   CrashLoopBackOff   22
staging-webs  seat-xxx         0/1   CrashLoopBackOff   21

에러 로그#

[otel.javaagent] ERROR io.opentelemetry.exporter.internal.grpc.GrpcExporter -
Failed to export metrics. Server is UNAVAILABLE.
Make sure your collector is running and reachable from this network.
Full error message: upstream connect error or disconnect/reset before headers.
retried and the latest reset reason: remote connection failure,
transport failure reason: TLS_error:|268435703:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:TLS_error_end

원인 분석#

1. 정상적인 Istio mTLS 트래픽 흐름#

일반적인 서비스 간 통신:

┌─────────────────────────────────────────────────────────────────┐
│                    정상적인 mTLS 통신                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [Pod A]                              [Pod B]                   │
│  ┌────────────┐                      ┌────────────┐             │
│  │  App A     │                      │  App B     │             │
│  │    │       │                      │    ↑       │             │
│  │    ↓       │                      │    │       │             │
│  │  Envoy     │ ════ mTLS ════════>  │  Envoy     │             │
│  │  Sidecar   │   (자동 암호화)       │  Sidecar   │             │
│  └────────────┘                      └────────────┘             │
│                                                                 │
│  * App은 plain HTTP로 요청                                      │
│  * Envoy sidecar가 자동으로 mTLS 처리                           │
│  * 양쪽 모두 Istio sidecar가 있어서 문제없음                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2. OTEL Agent의 특수한 동작 방식#

OTEL Java agent는 JVM 내부에서 직접 네트워크 연결:

┌─────────────────────────────────────────────────────────────────┐
│                    OTEL Agent 통신 문제                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [Java Pod]                          [OTEL Collector Pod]       │
│  ┌────────────────────┐              ┌────────────┐             │
│  │  ┌──────────────┐  │              │  Collector │             │
│  │  │  Spring App  │  │              │    ↑       │             │
│  │  │      +       │──┼─ HTTP ──────>│    │       │  ❌ 실패    │
│  │  │  OTEL Agent  │  │   (직접)     │  Envoy     │             │
│  │  └──────────────┘  │              │  Sidecar   │             │
│  │         │          │              │  (mTLS     │             │
│  │         ↓          │              │   STRICT)  │             │
│  │     Envoy Sidecar  │              └────────────┘             │
│  │     (우회됨!)      │                                         │
│  └────────────────────┘                                         │
│                                                                 │
│  문제점:                                                        │
│  1. OTEL agent가 JVM 내부에서 직접 gRPC/HTTP 연결               │
│  2. Istio sidecar를 통하지 않음 (또는 불완전하게 통함)           │
│  3. OTEL Collector의 sidecar는 mTLS 기대                        │
│  4. 결과: TLS 핸드셰이크 실패                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3. 에러 메시지 해석#

TLS_error:|268435703:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER
코드의미
WRONG_VERSION_NUMBERTLS 프로토콜 버전 불일치
원인 1클라이언트가 plain HTTP 보냈는데 서버가 TLS 기대
원인 2클라이언트가 TLS 보냈는데 서버가 plain HTTP 기대
원인 3TLS 버전 불일치 (TLS 1.2 vs 1.3 등)

이 경우: OTEL agent가 plain HTTP(gRPC)를 보냈는데, Istio mTLS가 중간에 개입하면서 꼬임.


해결#

핵심 아이디어#

OTEL Collector만 mTLS 예외 처리 (PERMISSIVE 모드)

┌─────────────────────────────────────────────────────────────────┐
│                    해결 후 통신 흐름                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [Java Pod]                          [OTEL Collector Pod]       │
│  ┌────────────────────┐              ┌────────────┐             │
│  │  ┌──────────────┐  │              │  Collector │             │
│  │  │  Spring App  │  │              │    ↑       │             │
│  │  │      +       │──┼─ HTTP ──────>│    │       │  ✅ 성공    │
│  │  │  OTEL Agent  │  │              │  Envoy     │             │
│  │  └──────────────┘  │              │  Sidecar   │             │
│  │                    │              │ (PERMISSIVE│             │
│  │                    │              │  = HTTP OK)│             │
│  └────────────────────┘              └────────────┘             │
│                                                                 │
│  PeerAuthentication: PERMISSIVE                                 │
│  → mTLS도 허용, plain HTTP도 허용                               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

1. DestinationRule 추가#

클라이언트(앱) → OTEL Collector 연결 시 mTLS 비활성화

파일: common-charts/infra/monitoring/otel-collector/templates/destinationrule.yaml

# mTLS 예외 처리 - OTEL agent가 직접 연결하므로 DISABLE 필요
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: otel-collector-mtls-disable
  namespace: {{ .Release.Namespace }}
spec:
  host: otel-collector.{{ .Release.Namespace }}.svc.cluster.local
  trafficPolicy:
    tls:
      mode: DISABLE

역할:

  • 클라이언트 Envoy sidecar에게 “OTEL Collector로 갈 때는 mTLS 쓰지마” 지시
  • 트래픽이 암호화 없이 전송됨

2. PeerAuthentication 추가#

OTEL Collector가 plain HTTP 연결도 수락하도록 설정

파일: common-charts/infra/monitoring/otel-collector/templates/peerauthentication.yaml

# OTEL Collector - mTLS PERMISSIVE 설정
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: otel-collector
  namespace: {{ .Release.Namespace }}
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: opentelemetry-collector
  mtls:
    mode: PERMISSIVE
  portLevelMtls:
    # gRPC 포트 - OTEL agent가 연결하는 포트
    4317:
      mode: PERMISSIVE
    # HTTP 포트
    4318:
      mode: PERMISSIVE

역할:

  • OTEL Collector의 Envoy sidecar에게 “mTLS와 plain HTTP 둘 다 받아” 지시
  • 포트별 세부 설정 가능 (4317: gRPC, 4318: HTTP)

Istio mTLS 모드 비교#

모드설명사용 케이스
STRICTmTLS만 허용일반 서비스 간 통신 (기본값)
PERMISSIVEmTLS + plain HTTP 둘 다 허용마이그레이션, 특수 클라이언트
DISABLEmTLS 비활성화외부 서비스, 레거시

DestinationRule vs PeerAuthentication#

리소스적용 대상역할
DestinationRule클라이언트 (발신 측)“이 서비스로 갈 때 어떻게 연결할지”
PeerAuthentication서버 (수신 측)“어떤 연결을 받아들일지”

둘 다 필요한 이유:

  1. DestinationRule만 있으면: 클라이언트는 plain HTTP 보내지만, 서버가 거부
  2. PeerAuthentication만 있으면: 서버는 준비됐지만, 클라이언트가 여전히 mTLS 시도

적용 후 트래픽 정책#

트래픽 경로mTLS 설정
앱 ↔ 앱 (staging-webs 내)STRICT (유지)
앱 → OTEL CollectorDISABLE/PERMISSIVE (예외)
Prometheus → 앱 metricsSTRICT (유지)
외부 → Istio GatewayTLS (인증서)

배포 명령어#

1. 변경사항 커밋 & 푸시#

cd /Users/wonny/Documents/GitHub/303-goormgb-k8s-helm
git add -A
git commit -m "fix(otel): add mTLS exception for otel-collector"
git push

2. ArgoCD Sync (Bastion)#

# OTEL Collector 동기화 (mTLS 예외 적용)
argocd app sync staging-otel-collector --force

# 확인
kubectl get destinationrule -n monitoring
kubectl get peerauthentication -n monitoring

3. 앱 서비스 재시작#

# 모든 deployment 재시작
kubectl rollout restart deployment -n staging-webs

# 상태 확인
kubectl get pods -n staging-webs -w

검증 방법#

1. Pod 상태 확인#

kubectl get pods -n staging-webs
# 모든 Pod가 Running, READY 2/2 여야 함

2. OTEL Collector 연결 확인#

# Pod 로그에서 OTEL 에러 없어야 함
kubectl logs -n staging-webs -l app.kubernetes.io/name=auth-guard --tail=50 | grep -i otel

3. Trace 수집 확인#

Grafana → Tempo → 서비스 trace 확인


대안적 해결 방법#

방법 1: OTEL Collector sidecar 제거#

# OTEL Collector deployment에 annotation 추가
metadata:
  annotations:
    sidecar.istio.io/inject: "false"

장점: 간단함 단점: OTEL Collector가 mTLS 보호 안 됨

방법 2: OTEL Agent가 TLS 사용#

# 앱 values에서 OTEL 엔드포인트 변경
otel:
  collectorEndpoint: "https://otel-collector.monitoring.svc.cluster.local:4317"
  # + TLS 인증서 설정 필요

장점: 보안 유지 단점: 인증서 관리 복잡

방법 3: 현재 선택한 방법 (PERMISSIVE)#

장점:

  • 기존 설정 최소 변경
  • OTEL Collector만 예외 처리
  • 다른 서비스는 STRICT 유지

단점:

  • OTEL Collector로의 트래픽은 암호화 안 됨
  • (내부 네트워크라 큰 문제 아님)

관련 파일#

파일역할
staging/values/values-istio-security.yamlmTLS 전역 설정
common-charts/infra/monitoring/otel-collector/templates/destinationrule.yamlOTEL용 DestinationRule
common-charts/infra/monitoring/otel-collector/templates/peerauthentication.yamlOTEL용 PeerAuthentication
common-charts/apps/java-service/templates/deployment.yamlOTEL agent 설정
common-charts/apps/java-service/values.yamlOTEL 엔드포인트 기본값

교훈#

  1. Istio mTLS는 sidecar 기반: sidecar를 통하지 않는 트래픽은 문제 발생 가능
  2. OTEL agent는 특수 케이스: JVM 내부에서 직접 연결하므로 sidecar 우회
  3. PERMISSIVE는 마이그레이션용: 일반적으로 STRICT 권장, 특수 케이스만 예외
  4. DestinationRule + PeerAuthentication: 클라이언트와 서버 양쪽 설정 필요

참고 자료#