본문 바로가기
클라우드/opentelemetry

당신의 OpenTelemetry Collector는 왜 느릴까? 😮 Headless Service로 완벽 해결!

by gasbugs 2025. 11. 11.
반응형

안녕하세요! 오늘은 쿠버네티스 환경에서 OpenTelemetry(OTel) Collector를 운영하면서 많은 분들이 겪는 숨겨진 병목 현상과 그 해결책에 대해 이야기해보려고 합니다.

 

혹시 이런 경험 없으신가요? 🤔 분명 Collector 파드를 여러 개 띄워서 확장성을 확보했는데, 어쩐지 특정 파드에만 트래픽이 몰리고 부하가 심해지는 현상 말이죠. 열심히 scale-out 했는데 전혀 효과를 보지 못하는 답답한 상황!

 

이 문제의 원인은 바로 gRPC와 쿠버네티스 기본 서비스(Service)의 로드 밸런싱 방식의 불일치에 있습니다. 오늘 이 문제를 명쾌하게 해결하고, 여러분의 OTel Collector를 진정한 분산 시스템으로 만들어 줄 Deployment와 Headless Service 조합에 대해 깊이 파헤쳐 보겠습니다. 🚀


🤔 무엇이 문제일까? gRPC와 ClusterIP 서비스의 함정

일반적으로 쿠버네티스에서 여러 개의 파드를 묶어 외부로 노출할 때 Service를 사용합니다. 가장 기본적인 ClusterIP 타입의 서비스는 고유한 가상 IP를 할당받고, 이 IP로 들어오는 요청을 뒤에 있는 파드들에게 분배해줍니다.

 

여기까진 완벽해 보이죠. 하지만 OTel Collector가 사용하는 gRPC 프로토콜의 특성이 변수를 만듭니다.

 

gRPC는 HTTP/2 기반으로, 한번 맺은 TCP 연결을 오랫동안 유지하며 통신하는 특징이 있습니다. 그런데 ClusterIP 서비스의 로드 밸런싱은 연결(Connection) 레벨에서 이루어집니다.

 

이게 무슨 의미일까요?

  1. 애플리케이션(클라이언트)이 OTel Collector 서비스의 ClusterIP로 첫 연결을 시도합니다.
  2. 쿠버네티스 서비스는 이 연결 요청을 여러 Collector 파드 중 하나에게 전달합니다.
  3. 한번 연결이 맺어지면, gRPC는 그 연결을 계속 재사용하여 수많은 텔레메트리 데이터(요청)를 보냅니다.
  4. 결과적으로, 해당 클라이언트가 보내는 모든 데이터는 처음 연결된 단 하나의 파드에게만 쏟아지게 됩니다. 😱

결국 파드를 아무리 많이 늘려도, 클라이언트 수만큼만 부하가 분산될 뿐, 실제 트래픽이 고르게 분산되지 않는 '무늬만 분산' 상태가 되는 것이죠.

✨ 완벽한 해결책: Deployment + Headless Service

이 문제를 해결하기 위한 가장 이상적인 전략은 Deployment와 Headless Service를 함께 사용하는 것입니다. 이 조합은 gRPC의 특성을 역으로 활용하여, 클라이언트가 직접 로드 밸런싱을 수행하도록 만듭니다.

1. Collector 관리는 Deployment로! 🚀

Deployment는 파드의 개수(replicas)를 보장하고, 롤링 업데이트, 자동 복구 등 상태 없는(stateless) 애플리케이션을 안정적으로 운영하기 위한 핵심 컨트롤러입니다. OTel Collector를 Deployment로 관리하면, 트래픽 양에 따라 replicas 수를 조절하여 유연하게 확장/축소할 수 있습니다. 이것은 기본 중의 기본이죠!

# otel-collector-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
  labels:
    app: opentelemetry
    component: collector
spec:
  # 🔽 필요에 따라 파드 개수를 3개, 5개 등으로 조절!
  replicas: 3
  selector:
    matchLabels:
      app: opentelemetry
      component: collector
  template:
    metadata:
      labels:
        app: opentelemetry
        component: collector
    spec:
      containers:
      - name: otel-collector
        image: otel/opentelemetry-collector-contrib:latest # 실제 환경에 맞는 이미지 버전 사용
        ports:
        - name: grpc
          containerPort: 4317
          protocol: TCP

2. 로드 밸런싱의 핵심, Headless Service! 🧠

Headless Service는 ClusterIP 서비스와 달리 고유한 가상 IP를 갖지 않습니다. 대신, 서비스의 DNS 이름을 조회했을 때, 해당 서비스에 연결된 모든 파드들의 실제 IP 목록을 반환합니다.

이게 어떻게 마법을 부릴까요?

  1. Headless Service를 생성합니다. (clusterIP: None 설정이 핵심!)
  2. 애플리케이션의 OTel SDK는 Collector 엔드포인트로 Headless Service의 DNS 이름(otel-collector-headless:4317)을 설정합니다.
  3. SDK 내의 gRPC 라이브러리는 이 DNS 이름을 조회하고, 3개 파드의 IP 주소 목록 전체를 받게 됩니다.
  4. gRPC 클라이언트는 이 IP 목록을 가지고, round_robin과 같은 정책에 따라 매 요청(Request)마다 다른 파드로 직접 요청을 분산시킵니다.

이제 연결 레벨이 아닌, 진정한 요청 레벨의 로드 밸런싱이 가능해집니다!

아래는 Headless Service를 정의하는 YAML 파일입니다.

# otel-collector-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: otel-collector-headless
  labels:
    app: opentelemetry
    component: collector
spec:
  # 🔽 이 설정 하나가 모든 것을 바꿉니다!
  clusterIP: None
  selector:
    # Deployment가 관리하는 파드들을 선택합니다.
    app: opentelemetry
    component: collector
  ports:
    - name: grpc
      port: 4317
      targetPort: 4317
      protocol: TCP

애플리케이션 설정은 어떻게?

애플리케이션 코드나 환경 변수에서 OTel 익스포터의 엔드포인트를 Headless Service의 DNS 주소로 지정해주기만 하면 됩니다.

  • 엔드포인트 주소: otel-collector-headless:4317 (같은 네임스페이스일 경우)
  • 또는 FQDN: otel-collector-headless.<namespace>.svc.cluster.local:4317

이 간단한 설정 변경만으로 여러분의 시스템은 훨씬 더 똑똑하게 부하를 분산하기 시작할 겁니다.


🤔 다른 방법은 왜 별로일까요?

1. DaemonSet 🧐

DaemonSet은 클러스터의 모든 (또는 특정) 노드에 파드를 하나씩 배포하는 방식입니다. 각 노드의 로그나 메트릭을 수집하는 '에이전트' 역할에는 적합하지만, 여러 애플리케이션의 데이터를 한 곳으로 모으는 '게이트웨이' 역할에는 부적합합니다.

  • 문제점: 확장성이 노드 수에 묶입니다. 트래픽이 아무리 많아져도 노드를 증설하지 않는 한 Collector 수를 늘릴 수 없어 병목이 되기 쉽습니다.

2. Service Mesh (Istio, Linkerd 등) 🕸️

서비스 메쉬를 사용하면 애플리케이션 코드 수정 없이 gRPC 로드 밸런싱을 투명하게 구현할 수 있습니다. 분명 강력한 기능이죠.

  • 문제점: 너무 무겁습니다. OTel Collector의 로드 밸런싱 문제 하나를 해결하기 위해 서비스 메쉬라는 거대한 시스템을 도입하는 것은 배보다 배꼽이 더 큰 격입니다. 이미 서비스 메쉬를 전사적으로 사용하고 있는 환경이 아니라면 과도한 해결책입니다.

맺음말

쿠버네티스와 같은 분산 환경에서는 각 기술의 특성을 깊이 이해하고 최적의 조합을 찾는 것이 중요합니다. OpenTelemetry Collector와 gRPC의 경우, Deployment로 유연한 확장성을 확보하고 Headless Service로 클라이언트 측 로드 밸런싱을 활성화하는 것이 가장 효율적이고 간단하며 강력한 방법입니다.

이제 더 이상 하나의 Collector 파드만 힘겹게 일하게 두지 마세요. 이 아키텍처를 적용해서 안정적이고 확장성 높은 옵저버빌리티 파이프라인을 구축하시길 바랍니다! 💪

 

반응형