[Daily morning study] OpenTelemetry 표준과 계측(Instrumentation) 개념
#daily morning study
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/endTimestatus: OK, ERROR, UNSETattributes: 키-값 메타데이터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, 방화벽 환경에 적합
기존 솔루션과의 비교
| 항목 | OTel | Jaeger SDK | Prometheus Client |
|---|---|---|---|
| 벤더 중립 | O | X (Jaeger 전용) | X (Prometheus 전용) |
| 신호 통합 | Traces + Metrics + Logs | Traces 전용 | 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 상태이며, 클라우드 네이티브 환경의 사실상 표준으로 자리잡고 있다.