[Daily morning study] OpenTelemetry 표준과 계측(Instrumentation) 개념

#daily morning study

Image


OpenTelemetry란

OpenTelemetry(OTel)는 분산 시스템의 관측 가능성(Observability)을 위한 오픈소스 표준 프레임워크다. CNCF(Cloud Native Computing Foundation)의 인큐베이팅 프로젝트로, 기존에 분리되어 있던 OpenCensus와 OpenTracing을 통합하면서 탄생했다.

핵심 목표는 벤더 중립적인 계측 API/SDK를 제공하는 것이다. 코드를 한 번 계측해두면 백엔드(Jaeger, Zipkin, Datadog, Grafana Tempo 등)를 바꿔도 애플리케이션 코드를 수정할 필요가 없다.


Observability의 3대 신호

OTel은 세 가지 텔레메트리 데이터를 다룬다.

신호설명예시
Traces분산 요청의 흐름 추적HTTP 요청이 서비스 A → B → DB를 거치는 경로
Metrics수치 측정 데이터요청 수, 응답 시간, 에러율
Logs이벤트 기록에러 메시지, 디버그 정보

기존에는 각 신호마다 별도의 라이브러리를 사용했지만, OTel은 하나의 SDK로 세 가지를 모두 처리한다.


핵심 개념

Trace와 Span

Trace는 하나의 요청이 시스템을 통과하는 전체 여정이다. 각 Trace는 고유한 traceId를 가진다.

Span은 Trace를 구성하는 개별 작업 단위다. 서비스 A가 서비스 B를 호출하는 것 하나하나가 Span이 된다.

Trace (traceId: abc123)
├── Span: API Gateway (0ms ~ 150ms)
│   ├── Span: Auth Service (5ms ~ 30ms)
│   └── Span: User Service (35ms ~ 120ms)
│       └── Span: DB Query (50ms ~ 100ms)

각 Span은 다음 정보를 포함한다.

  • spanId: 고유 식별자
  • parentSpanId: 부모 Span 식별자
  • startTime / endTime
  • status: OK, ERROR, UNSET
  • attributes: 키-값 메타데이터
  • events: 타임스탬프가 있는 로그 항목

Context Propagation

분산 환경에서 Trace가 서비스 경계를 넘나들려면 Context를 전달해야 한다. OTel은 W3C Trace Context 표준을 사용한다.

HTTP 헤더로 전달되는 형태:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             version-traceId-parentSpanId-flags
tracestate: vendor=value

Instrumentation 종류

자동 계측(Auto Instrumentation): 코드 변경 없이 에이전트나 바이트코드 조작으로 계측한다. Java 에이전트(-javaagent:opentelemetry-javaagent.jar)나 Python의 opentelemetry-instrument 명령이 대표적이다.

수동 계측(Manual Instrumentation): 코드에 직접 OTel API를 호출해서 더 세밀한 Span과 속성을 추가한다.

from opentelemetry import trace

tracer = trace.get_tracer("my-service")

def process_order(order_id: str):
    with tracer.start_as_current_span("process_order") as span:
        span.set_attribute("order.id", order_id)
        
        # 비즈니스 로직
        result = validate_order(order_id)
        
        if not result.is_valid:
            span.set_status(trace.StatusCode.ERROR, "Order validation failed")
            span.record_exception(ValueError(f"Invalid order: {order_id}"))
        
        return result

OTel 아키텍처

애플리케이션 (SDK로 계측)
        │
        ▼
OTel Collector (수집 · 처리 · 내보내기)
        │
   ┌────┴────┐
   ▼         ▼
Jaeger    Prometheus
(Traces)  (Metrics)

OTel Collector

Collector는 에이전트와 게이트웨이 두 가지 모드로 배포한다.

에이전트 모드: 각 노드에 DaemonSet으로 배포해서 로컬 데이터를 수집한다.

게이트웨이 모드: 중앙 집중형으로 배포해서 여러 에이전트로부터 데이터를 받아 처리한다.

Collector의 파이프라인은 세 단계로 구성된다.

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  resource:
    attributes:
      - key: environment
        value: production
        action: insert

exporters:
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

OTLP (OpenTelemetry Protocol)

OTel이 정의한 전용 전송 프로토콜이다. gRPC와 HTTP/protobuf 두 가지를 지원한다.

  • gRPC: 기본 포트 4317, 스트리밍 지원으로 효율적
  • HTTP/JSON: 기본 포트 4318, 방화벽 환경에 적합

기존 솔루션과의 비교

항목OTelJaeger SDKPrometheus Client
벤더 중립OX (Jaeger 전용)X (Prometheus 전용)
신호 통합Traces + Metrics + LogsTraces 전용Metrics 전용
표준화CNCF 표준독자 표준독자 표준
마이그레이션용이어려움어려움

Kubernetes 환경에서의 OTel

Kubernetes에서는 OTel Operator를 활용하면 계측 배포가 편리해진다.

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: my-instrumentation
spec:
  exporter:
    endpoint: http://otel-collector:4317
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: "0.1"   # 10% 샘플링
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest

Pod에 어노테이션만 추가하면 자동 계측이 적용된다.

annotations:
  instrumentation.opentelemetry.io/inject-java: "true"

샘플링 전략

모든 요청을 추적하면 비용과 성능 부담이 크다. OTel은 두 가지 샘플링 방식을 제공한다.

헤드 샘플링(Head Sampling): 요청 시작 시점에 추적 여부를 결정한다. 빠르지만 에러 요청을 놓칠 수 있다.

테일 샘플링(Tail Sampling): 요청 완료 후 전체 Trace를 보고 결정한다. 에러나 느린 요청을 선별적으로 보존할 수 있지만 메모리 사용량이 크다.

# Tail sampling processor 예시
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: keep-errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: keep-slow-traces
        type: latency
        latency: { threshold_ms: 500 }
      - name: probabilistic-sampling
        type: probabilistic
        probabilistic: { sampling_percentage: 5 }

정리

OTel이 중요한 이유는 표준화다. 벤더 종속 없이 계측 코드를 한 번만 작성하면 되고, 백엔드를 자유롭게 교체할 수 있다. 현재 Java, Python, Go, Node.js, .NET 등 주요 언어의 SDK가 GA 상태이며, 클라우드 네이티브 환경의 사실상 표준으로 자리잡고 있다.