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

개발자 필독! 🤯 MSA 지옥에서 날 구해준 OpenTelemetry 완벽 가이드

by gasbugs 2025. 11. 9.

"내 코드는 분명 잘 돌아가는데, 왜 전체 서비스는 느릴까?" "A 서비스에서 B 서비스 호출할 때 어떤 파라미터가 넘어갔더라?"

마이크로서비스 아키텍처(MSA)가 복잡해질수록, 장애의 원인을 찾거나 성능 병목을 분석하는 것은 점점 더 어려워집니다. 마치 거대한 미로 속에서 길을 잃은 기분이죠. 😭

 

이런 '관측 가능성(Observability)'의 위기 속에서, OpenTelemetry (줄여서 Otel)는 우리에게 구원의 동아줄이 되어줍니다. 오늘은 MSA 지옥에서 길을 잃은 개발자들을 위해 OpenTelemetry의 모든 것을 A to Z로 파헤쳐 보겠습니다!


🤔 그래서 OpenTelemetry가 정확히 뭔가요?

OpenTelemetry는 관측 가능성 데이터를 생성하고 수집하는 방법을 표준화한 오픈소스 프레임워크입니다. 과거에는 각 벤더(Datadog, New Relic 등)나 기술(Jaeger, Prometheus)마다 데이터를 수집하는 방식이 제각각이었죠. Otel은 이 모든 것을 하나로 통일하여, 어떤 시스템을 사용하든 동일한 방식으로 데이터를 수집하고 전송할 수 있게 해주는 '표준 규격'이라고 생각하면 쉽습니다.

 

전체적인 흐름은 이렇습니다.

애플리케이션 (데이터 생성) ➡️ OpenTelemetry Collector (중간 처리) ➡️ 관측 가능성 백엔드 (Jaeger, Prometheus 등)

 

이 구조 덕분에 우리는 애플리케이션 코드를 거의 수정하지 않고도 데이터를 보내는 목적지를 자유롭게 바꿀 수 있는 유연성을 얻게 됩니다. ✨

 

1️⃣ OpenTelemetry의 3대 핵심 데이터: 신호 (Signals)

Otel은 시스템을 관찰하기 위해 세 가지 핵심 데이터, 즉 '신호'를 수집합니다.

🗺️ 트레이스 (Traces): 요청의 전체 여정 추적하기

사용자 요청이 시스템에 들어와서 나갈 때까지, 모든 서비스와 컴포넌트를 거치는 과정을 하나의 여정으로 묶어 보여줍니다. 마치 택배가 어디쯤 오고 있는지 배송 조회를 하는 것과 같아요.

  • Trace: 하나의 요청에 대한 전체 여정. (예: 상품 주문 요청 전체)
  • Span: 여정 속 하나의 작업 단위. (예: 로그인 확인, 재고 확인, 결제 API 호출)

Span의 구조 (예시)

{
  "name": "HTTP GET /api/products",
  "trace_id": "0af7651916cd43dd8448eb211c80319c",
  "span_id": "b7ad6b7169203331",
  "parent_span_id": "f3a4a75a9d201b5a",
  "start_time": "2025-11-09T11:41:00.123Z",
  "end_time": "2025-11-09T11:41:00.345Z",
  "attributes": {
    "http.method": "GET",
    "http.status_code": 200,
    "user.id": "user123"
  },
  "events": [
    {
      "name": "Cache miss",
      "timestamp": "2025-11-09T11:41:00.200Z"
    }
  ]
}

 

이 trace_id만 있으면, 여러 마이크로서비스에 흩어져 있는 작업(Span)들을 하나로 엮어 요청의 전체 흐름을 한눈에 파악할 수 있습니다.

 

📊 메트릭 (Metrics): 시스템의 건강 상태 측정하기

특정 시점의 시스템 상태를 숫자로 나타내는 데이터입니다. 자동차의 계기판을 생각하면 쉽습니다.

  • Counter: 계속 증가만 하는 값 (예: 총 요청 처리 수, 웹사이트 총 방문자 수)
  • UpDownCounter: 증가하거나 감소할 수 있는 값 (예: 현재 활성화된 유저 세션 수)
  • Histogram: 값의 분포를 측정 (예: API 요청의 95%는 100ms 안에 처리된다)
  • Gauge: 특정 시점의 값 (예: 현재 CPU 사용률, 메모리 사용량)

📝 로그 (Logs): 특정 사건의 상세 기록

우리에게 가장 익숙한 데이터죠. 특정 시점에 발생한 이벤트에 대한 텍스트 기록입니다. Otel 로그의 진정한 힘은 트레이스와의 연동에 있습니다.

Otel 로그의 핵심! 로그에 trace_id와 span_id가 포함됩니다. 덕분에 "이 에러 로그가 어떤 요청(Trace)의 어떤 작업(Span)에서 발생했지?"를 즉시 파악할 수 있습니다. 더 이상 로그 파일만 뒤지며 원인을 추측할 필요가 없죠!

2️⃣ 내 코드에 Otel 심기: API & SDK

그럼 이 데이터들을 어떻게 생성할까요? 바로 API와 SDK를 통해 애플리케이션에 '계측(Instrumentation)' 코드를 추가하는 것입니다.

  • API: "어떤 데이터를 수집할지"에 대한 명세서(설계도)입니다.
  • SDK: API 명세서를 실제로 구현한 언어별 도구(구현체)입니다.

이 둘이 분리되어 있어, 우리는 API 표준에 맞춰 코드만 작성하면 나중에 SDK 구현체를 바꾸더라도 코드를 수정할 필요가 없습니다.

계측 방법은 크게 두 가지입니다.

  1. 자동 계측 (Automatic Instrumentation) 🪄: 코드 수정 없이, 에이전트나 라이브러리를 추가하는 것만으로 유명 프레임워크(Spring, Django 등)나 라이브러리(HTTP 클라이언트, DB 드라이버)의 데이터를 자동으로 수집합니다. 가장 먼저 시도해야 할 간편하고 강력한 방법입니다!
  2. 수동 계측 (Manual Instrumentation) ✍️: "우리 회사만의 중요한 비즈니스 로직"처럼 자동 계측이 잡아내지 못하는 부분을 개발자가 직접 API를 호출하여 데이터를 생성합니다.

3️⃣ 만능 데이터 허브: OpenTelemetry Collector

애플리케이션에서 생성된 데이터를 백엔드로 바로 보내는 건 비효율적이고 위험합니다. 애플리케이션에 부하를 줄 수 있고, 백엔드를 바꾸려면 모든 애플리케이션 코드를 수정해야 하니까요.

이때 등장하는 것이 바로 Otel Collector입니다. Collector는 애플리케이션과 백엔드 사이에서 데이터를 수신, 처리, 내보내는 독립적인 프록시 역할을 합니다.

(Image Source: opentelemetry.io)

 

Collector는 레고 블록처럼 세 부분으로 구성됩니다.

  • 수신기 (Receivers) 📥: 다양한 형식(OTLP, Jaeger, Prometheus 등)의 데이터를 받아들입니다.
  • 처리기 (Processors) 🛠️: 데이터를 가공합니다. 불필요한 데이터를 필터링하거나, 민감한 정보를 마스킹하고, 데이터를 묶어서 효율적으로 처리(Batch)하는 등의 작업을 합니다.
  • 내보내기 (Exporters) 📤: 처리된 데이터를 최종 목적지(Jaeger, Prometheus, Datadog 등)로 내보냅니다.

💡 왜 Collector를 써야 할까요?

  • 관심사 분리: 애플리케이션은 데이터 '생성'에만 집중하고, Collector가 '처리 및 전송'을 책임져서 앱의 부담을 줄여줍니다.
  • 유연성: 데이터를 Jaeger와 Prometheus로 동시에 보내고 싶다면? Collector 설정 파일 한 줄만 바꾸면 끝! 앱 코드는 건드릴 필요가 없습니다.
  • 성능: Batch Processor를 사용하면 데이터를 모아서 한 번에 보내므로 네트워크 효율이 극대화됩니다. 프로덕션 환경에서는 SimpleSpanProcessor (스팬이 생길 때마다 즉시 전송) 대신 BatchSpanProcessor 사용이 강력히 권장되는 이유입니다.

Collector 설정 파일 예시 (config.yaml)

Collector가 어떻게 동작하는지는 이 설정 파일 하나로 모두 설명됩니다.

# config.yaml for OpenTelemetry Collector

receivers: # 데이터는 OTLP 프로토콜로 받는다
  otlp:
    protocols:
      grpc:
      http:

processors: # 받은 데이터는 일괄 처리하고, 메모리 사용량을 제한한다
  batch:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 75

exporters: # 처리된 데이터는 Jaeger로 보내고, 디버깅을 위해 콘솔에도 출력한다
  otlp/jaeger:
    endpoint: "jaeger-all-in-one:4317" # Jaeger 주소
    tls:
      insecure: true
  logging:
    loglevel: debug

service:
  pipelines: # 위 컴포넌트들을 파이프라인으로 연결한다
    traces: # 트레이스 데이터는 이렇게 처리할거야
      receivers: [otlp]
      processors: [batch, memory_limiter]
      exporters: [otlp/jaeger, logging]

4️⃣ 분산 시스템의 혈관: OTLP & Context Propagation

마지막으로, 이 모든 컴포넌트들이 어떻게 서로 대화하는지 알아봅시다.

  • OTLP (OpenTelemetry Protocol) 📡: Otel의 공식 데이터 전송 프로토콜입니다. 모든 Otel 컴포넌트가 알아들을 수 있는 '공용어' 역할을 합니다.
  • Context Propagation 🤝: 분산 추적의 핵심 기술입니다. A 서비스에서 B 서비스로 HTTP 요청을 보낼 때, 보이지 않는 곳에서 마법 같은 일이 일어납니다. 바로 traceparent 라는 HTTP 헤더에 trace_id와 같은 추적 정보를 실어 보내는 것이죠.

HTTP 헤더 예시

traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01


이 헤더 덕분에 B 서비스는 "아, 이 요청은 0af... 트레이스의 일부구나!"라고 인지하고, 자신의 작업(Span)을 같은 트레이스에 연결할 수 있습니다. 이 '문맥 전파'가 없다면 분산 추적은 불가능합니다.


마치며

OpenTelemetry는 단순히 또 하나의 라이브러리나 도구가 아닙니다. 복잡한 현대 분산 시스템의 내부를 투명하게 들여다볼 수 있게 해주는 표준이자 철학입니다.

 

Otel을 통해 우리는 흩어진 서비스들의 데이터를 하나로 엮어 전체적인 그림을 볼 수 있고, 문제가 발생했을 때 신속하게 원인을 파악하여 해결할 수 있습니다. 더 이상 미로 속에서 헤매지 마세요. OpenTelemetry와 함께 여러분의 시스템에 밝은 등대를 세워보시는 건 어떨까요? 🚀