OpenTelemetry

이 문서는 OpenTelemetry(OTel)를 처음부터 운영까지 한 번에 익히는 교재형 정리다. 개념 → 신호(Signals) → 데이터 모델 → 계측(Instrumentation) → Collector → 샘플링 → 백엔드 → 실전 설정 순으로 읽으면 된다. 코드 예시는 Python 기준이다.

0. 목차

  1. 관측 가능성(Observability)과 OpenTelemetry란?
  2. 등장 배경과 표준화 역사
  3. 전체 아키텍처 한눈에 보기
  4. 3대 신호 ① 트레이스(Traces)
  5. 3대 신호 ② 메트릭(Metrics)
  6. 3대 신호 ③ 로그(Logs)
  7. Context 전파와 W3C Trace Context
  8. Resource와 Semantic Conventions
  9. 계측(Instrumentation): 자동 vs 수동
  10. OpenTelemetry Collector
  11. 샘플링(Sampling) 전략
  12. 백엔드(저장·시각화) 선택
  13. Python 실전 예제
  14. 운영 베스트 프랙티스
  15. 자주 하는 실수 / FAQ
  16. 용어 사전

1. 관측 가능성(Observability)과 OpenTelemetry란?

관측 가능성(Observability)이란 시스템의 외부 출력(텔레메트리 데이터)만으로 내부 상태를 추론할 수 있는 능력을 말한다. “서버가 느리다”가 아니라 “어떤 요청이, 어느 서비스의, 어떤 함수에서, 왜 느려졌는가”를 데이터로 답할 수 있어야 관측 가능한 시스템이다.

OpenTelemetry(OTel)는 이 텔레메트리 데이터를 생성·수집·전송하는 방법을 표준화한 오픈소스 프로젝트다. CNCF(Cloud Native Computing Foundation)의 인큐베이팅 프로젝트로, Kubernetes 다음으로 활발한 프로젝트다.

핵심: OTel은 저장소나 대시보드가 아니다. 데이터를 “만들어서 내보내는” 표준 + 도구 모음이다. 저장·조회·시각화는 Jaeger, Prometheus, Grafana, Datadog 같은 백엔드의 몫이다.

관측 가능성의 3대 기둥 (Three Pillars)

기둥 질문에 답함 특성
Traces (트레이스) “이 요청이 어디서 시간을 썼나?” 요청 1건의 전체 여정 (분산 추적)
Metrics (메트릭) “전반적으로 얼마나 빠르고 건강한가?” 시간에 따라 집계된 수치
Logs (로그) “정확히 그 순간 무슨 일이 있었나?” 이벤트 단위의 상세 기록

OTel의 가장 큰 가치는 이 세 신호를 하나의 표준·하나의 Context로 묶는다는 점이다. 트레이스 ID로 로그를 찾고, 메트릭의 이상 지점에서 바로 트레이스로 점프하는 “상관관계(correlation)”가 가능해진다.


2. 등장 배경과 표준화 역사

예전에는 벤더마다 SDK가 달라서, Datadog을 쓰다가 New Relic으로 바꾸면 코드를 전부 다시 계측해야 했다. 이 “벤더 종속(lock-in)”을 깨려고 두 표준이 경쟁했다.

연도 사건
2016 OpenTracing 등장 (분산 추적 API 표준)
2017 Google 주도 OpenCensus 등장 (추적 + 메트릭 + 라이브러리)
2019 두 프로젝트가 OpenTelemetry로 통합 (CNCF Sandbox)
2021 Trace 사양 안정화(1.0), OTLP 프로토콜 표준 확정
2023~ Metrics·Logs 안정화, Collector GA, Profiling 신호 추가 진행

외우는 한 줄: OpenTelemetry = OpenTracing + OpenCensus. 그래서 “API/계측 철학”은 OpenTracing, “다중 신호 + 라이브러리”는 OpenCensus 유전자를 물려받았다.


3. 전체 아키텍처 한눈에 보기

[ 당신의 애플리케이션 ]
   │  OTel API      ← 계측 코드가 호출하는 인터페이스 (벤더 중립)
   │  OTel SDK      ← 실제 구현체 (샘플링·배치·내보내기 담당)
   │
   ▼ OTLP (gRPC/HTTP)
[ OpenTelemetry Collector ]   ← 선택사항이지만 사실상 표준
   ├─ Receivers   (데이터 수신: OTLP, Prometheus, Jaeger…)
   ├─ Processors  (배치·필터·속성변경·tail 샘플링…)
   └─ Exporters   (데이터 송신: 백엔드로)
   │
   ▼
[ 백엔드 / 저장·시각화 ]
   Traces  → Jaeger, Grafana Tempo, Zipkin
   Metrics → Prometheus, Mimir, Cortex
   Logs    → Loki, Elasticsearch
   통합    → Grafana, Datadog, New Relic, Honeycomb …

핵심 컴포넌트 분해

컴포넌트 역할
API 계측 코드가 의존하는 인터페이스. 라이브러리 저자는 API에만 의존하면 됨.
SDK API의 실제 구현. 샘플링·리소스·Processor·Exporter 설정의 본체.
OTLP OpenTelemetry Protocol. 신호를 전송하는 표준 와이어 포맷(gRPC/HTTP+protobuf).
Collector 텔레메트리의 “중앙 우체국”. 수신→가공→전송을 담당하는 독립 바이너리.
Exporter 데이터를 특정 목적지 포맷으로 변환해 내보내는 플러그인.
Instrumentation 코드에서 신호를 생성하는 계측. 자동/수동 두 종류.

4. 3대 신호 ① 트레이스(Traces)

트레이스는 요청 1건이 여러 서비스를 거치는 전체 여정이다. 분산 시스템 디버깅의 핵심.

4.1 Trace와 Span

  • Trace: 하나의 요청 전체. 고유한 trace_id(16바이트)로 식별.
  • Span: 트레이스 안의 작업 단위 하나(예: DB 쿼리, HTTP 호출, 함수 실행). 고유 span_id(8바이트)를 가짐.
  • Span들은 부모-자식 관계로 트리를 이룬다. 맨 위 Span이 Root Span.
Trace (trace_id = abc123…)
└─ Span: HTTP GET /order          [Root, 250ms]   SpanKind=SERVER
   ├─ Span: validate_user          [20ms]          SpanKind=INTERNAL
   ├─ Span: SELECT * FROM orders   [80ms]          SpanKind=CLIENT
   └─ Span: POST payment-service   [140ms]         SpanKind=CLIENT
      └─ Span: charge_card         [120ms]         (다른 서비스의 SERVER span)

4.2 Span의 구성 요소

필드 설명 예시
name 작업 이름 (낮은 카디널리티 권장) GET /order/{id}
trace_id / span_id 식별자 16/8바이트 hex
parent_span_id 부모 Span. 없으면 Root
start / end time 시작·종료 타임스탬프 → 차이가 duration
SpanKind Span 종류 SERVER / CLIENT / PRODUCER / CONSUMER / INTERNAL
Attributes 키-값 메타데이터 http.status_code=200
Events Span 내 시점 이벤트(로그성) exception, cache.miss
Status 결과 상태 Unset / Ok / Error
Links 다른 Span과의 연결(인과관계 외) 배치 처리 시 원본 메시지들

4.3 SpanKind가 중요한 이유

SpanKind는 백엔드가 서비스 토폴로지 지도를 그릴 때 사용한다. CLIENT span과 상대 서비스의 SERVER span을 이어 “A 서비스 → B 서비스” 화살표를 만든다.

Kind 의미
SERVER 들어온 요청을 처리 (HTTP 핸들러 등)
CLIENT 나가는 동기 요청 (HTTP 호출, DB 쿼리)
PRODUCER 비동기 메시지 발행 (Kafka produce)
CONSUMER 비동기 메시지 소비 (Kafka consume)
INTERNAL 서비스 내부 작업 (기본값)

5. 3대 신호 ② 메트릭(Metrics)

메트릭은 시간에 따라 집계된 수치다. “초당 요청 수”, “p99 지연시간”, “에러율” 같은 대시보드·알림의 재료다. 개별 이벤트가 아니라 집계값이라 저장 비용이 매우 싸다.

5.1 계측기(Instrument) 종류

Instrument 방향 동기/비동기 용도
Counter 증가만(단조) 동기 요청 수, 처리 바이트
UpDownCounter 증감 가능 동기 큐 길이, 활성 연결 수
Histogram 분포 동기 요청 지연시간, 응답 크기
Gauge 현재값 동기(신규) 현재 온도, 현재 메모리
Observable Counter 증가만 비동기(콜백) CPU 누적 시간
Observable UpDownCounter 증감 비동기(콜백) OS 메모리 사용량
Observable Gauge 현재값 비동기(콜백) 주기적으로 읽는 게이지 값

동기 vs 비동기: 동기 계측기는 코드 흐름 중 직접 add()/record() 호출. 비동기(Observable)는 수집 시점에 콜백이 호출되어 현재 값을 “읽어온다”. 폴링성 지표(메모리·CPU)는 비동기가 적합.

5.2 Temporality(시간성)와 Aggregation

메트릭을 내보낼 때 누적 방식이 두 가지다. 백엔드에 맞춰 선택해야 한다.

Temporality 의미 궁합
Cumulative 시작 이후 누적값을 매번 보고 Prometheus (pull 모델)
Delta 직전 보고 이후의 증분만 보고 Datadog, StatsD류 (push 모델)

Aggregation은 측정값을 어떻게 합칠지의 규칙이다. Histogram은 Explicit Bucket Histogram(고정 경계)과 Exponential Histogram(자동 가변 버킷, 더 효율적)을 지원한다.


6. 3대 신호 ③ 로그(Logs)

로그는 가장 오래된 신호지만 OTel에서는 가장 늦게 표준화됐다. OTel의 로그 전략은 독특하다 — 새 로깅 프레임워크를 만들지 않는다. 대신 기존 로깅(Python logging, Log4j 등)에 다리(bridge)를 놓아 OTLP로 흘려보낸다.

6.1 OTel 로그의 핵심 가치 = 상관관계

OTel 로그 레코드에는 현재 활성 Span의 trace_id·span_id가 자동으로 주입된다. 그래서:

  • 느린 트레이스를 발견 → 같은 trace_id의 로그를 바로 조회
  • 에러 로그 발견 → 해당 요청의 전체 트레이스로 점프

이 “trace ↔ log 점프”가 OTel 로그를 쓰는 가장 큰 이유다.

6.2 로그 레코드 구조

필드 설명
Timestamp 발생 시각
SeverityText / SeverityNumber 로그 레벨 (DEBUG~FATAL, 1~24 정규화)
Body 로그 메시지 본문 (문자열 또는 구조화 객체)
Attributes 구조화 필드 (key-value)
TraceId / SpanId / TraceFlags 트레이스 상관관계 (자동 주입)
Resource 어떤 서비스/호스트에서 나왔는지

7. Context 전파와 W3C Trace Context

분산 추적의 마법은 Context 전파(propagation)에 있다. 서비스 A가 서비스 B를 호출할 때, A의 trace_id를 B에게 넘겨줘야 같은 트레이스로 이어진다. 이걸 HTTP 헤더로 실어 보낸다.

7.1 W3C Trace Context 표준 헤더

업계 표준은 W3C Trace Context로, traceparent 헤더를 쓴다.

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             │  │                                │                │
          version   trace-id (16B)         parent-id (8B)    trace-flags
                                                              (01 = sampled)
부분 의미
version 형식 버전 (현재 00)
trace-id 이 요청의 전체 트레이스 ID
parent-id 호출하는 쪽의 현재 span ID (받는 쪽엔 부모가 됨)
trace-flags 샘플링 여부 비트 등

tracestate 헤더는 벤더별 추가 정보를 키-값으로 싣는 보조 헤더다.

7.2 Baggage

Baggage는 트레이스를 따라 흐르는 사용자 정의 키-값이다. 예: user.tier=premium을 진입점에서 넣으면, 하위 모든 서비스가 그 값을 읽을 수 있다.

주의: Baggage는 모든 다운스트림 HTTP 헤더에 실려 나간다. 민감정보(PII)나 큰 값을 넣지 말 것. 또한 Baggage는 자동으로 Span attribute가 되지 않는다 — 필요하면 명시적으로 복사해야 한다.


8. Resource와 Semantic Conventions

8.1 Resource

Resource는 “이 텔레메트리를 만든 주체가 누구인가”를 설명하는 속성 묶음이다. 모든 신호에 공통으로 붙는다.

service.name        = order-service     ← 가장 중요! 없으면 unknown_service
service.version     = 1.4.2
service.namespace   = shop
deployment.environment = production
host.name           = ip-10-0-1-23
k8s.pod.name        = order-7d9f-x2k

1순위 규칙: service.name은 반드시 설정하라. 이것이 백엔드에서 서비스를 구분하는 기준이다.

8.2 Semantic Conventions(시맨틱 규약)

속성 이름을 제멋대로 지으면(statusCode vs status_code vs http_code) 백엔드가 알아듣지 못한다. OTel은 표준 속성 이름 사전인 Semantic Conventions를 정의한다.

영역 표준 속성 예시
HTTP http.request.method, http.response.status_code, url.path
DB db.system, db.namespace, db.query.text
Messaging messaging.system, messaging.destination.name
RPC rpc.system, rpc.service, rpc.method
예외 exception.type, exception.message, exception.stacktrace

표준 이름을 쓰면 자동 대시보드·자동 토폴로지가 “그냥 작동한다”. 이것이 OTel을 쓰는 큰 이유 중 하나다.


9. 계측(Instrumentation): 자동 vs 수동

구분 자동 계측 수동 계측
방식 라이브러리/에이전트가 자동 래핑 개발자가 코드로 span·metric 생성
커버리지 HTTP·DB·gRPC 등 인기 라이브러리 비즈니스 로직 등 무엇이든
노력 거의 없음 (의존성 추가/에이전트 부착) 코드 작성 필요
예 (Python) opentelemetry-instrument python app.py tracer.start_as_current_span()

실전 권장: 자동 계측으로 80%를 공짜로 얻고, 핵심 비즈니스 구간만 수동 계측으로 보강하라. 둘은 같은 Context를 공유하므로 자연스럽게 한 트레이스로 합쳐진다.


10. OpenTelemetry Collector

Collector는 OTel 생태계의 중앙 우체국이다. 앱은 Collector로만 보내고, “어디에 어떻게 저장할지”는 Collector 설정에서 결정한다. 백엔드를 바꿔도 앱 코드는 불변.

10.1 파이프라인 = Receiver → Processor → Exporter

단계 역할 예시
Receiver 데이터 수신 otlp, prometheus, jaeger, filelog
Processor 가공·필터·배치 batch, memory_limiter, attributes, tail_sampling
Exporter 데이터 송신 otlp, prometheus, debug, 벤더별

10.2 Collector 설정 예시 (otel-collector-config.yaml)

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  batch:
    timeout: 5s
    send_batch_size: 1024

exporters:
  otlp/tempo:                 # 트레이스 → Grafana Tempo
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheus:                 # 메트릭 → Prometheus 노출
    endpoint: 0.0.0.0:8889
  debug:                      # 콘솔 디버그 출력
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers:  [otlp]
      processors: [memory_limiter, batch]
      exporters:  [otlp/tempo]
    metrics:
      receivers:  [otlp]
      processors: [memory_limiter, batch]
      exporters:  [prometheus]
    logs:
      receivers:  [otlp]
      processors: [memory_limiter, batch]
      exporters:  [debug]

필수 Processor 2종: memory_limiter(OOM 방어)와 batch(전송 효율). 운영 Collector라면 거의 항상 넣는다. 순서는 memory_limiter를 가장 앞에 둔다.

10.3 배포 패턴

패턴 설명 장점
Agent (사이드카/데몬셋) 각 호스트·Pod 옆에 1개씩 낮은 지연, 호스트 메타데이터 수집 용이
Gateway (중앙 집중) 독립 클러스터로 1~N개 tail 샘플링·중앙 정책·확장 용이
Agent + Gateway 둘을 결합 (현업 표준) 각 장점 결합

배포판은 두 가지: Core(핵심 컴포넌트만)와 Contrib(벤더·커뮤니티 컴포넌트 다수 포함). 보통 Contrib를 쓴다.


11. 샘플링(Sampling) 전략

모든 트레이스를 100% 저장하면 비용이 폭발한다. 샘플링으로 대표 표본만 남긴다.

방식 결정 시점 특징
Head 샘플링 트레이스 시작 시 빠르고 단순. 에러 트레이스를 놓칠 수 있음
└ TraceIdRatioBased 시작 시 예: 10%만 유지. 가장 흔함
└ ParentBased 시작 시 부모의 샘플링 결정을 따름 (전파 일관성)
Tail 샘플링 트레이스 완료 후 Collector에서. “에러나 느린 트레이스만 유지” 가능. 비용·메모리 큼

실전: 기본은 ParentBased + TraceIdRatioBased(예: 10%). “에러·느린 요청은 무조건 보고 싶다”면 Collector의 tail_sampling processor로 정책 기반 샘플링을 건다.

# Collector tail_sampling 예시: 에러 또는 1초 초과는 100% 유지
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow
        type: latency
        latency: { threshold_ms: 1000 }
      - name: baseline-10pct
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }

12. 백엔드(저장·시각화) 선택

신호 오픈소스 상용(SaaS)
Traces Jaeger, Grafana Tempo, Zipkin Datadog, Honeycomb, New Relic, Lightstep
Metrics Prometheus, Mimir, Cortex, Thanos Datadog, Grafana Cloud
Logs Loki, Elasticsearch/OpenSearch Datadog, Splunk
통합 UI Grafana Datadog, New Relic

오픈소스 정석 조합: Tempo(트레이스) + Prometheus(메트릭) + Loki(로그) + Grafana(통합 대시보드). 이 4종을 흔히 “Grafana LGTM 스택”이라 부른다.

OTel의 핵심 이점: 앱은 OTLP로만 내보내고, 백엔드는 Collector 설정만 바꿔 갈아끼울 수 있다. 벤더 종속이 사라진다.


13. Python 실전 예제

13.1 설치

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install   # 설치된 라이브러리용 자동계측 패키지 일괄 설치

13.2 자동 계측 (코드 수정 0줄)

export OTEL_SERVICE_NAME="order-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"

opentelemetry-instrument python app.py

Flask·FastAPI·requests·psycopg2 등 인기 라이브러리가 자동으로 span을 만든다.

13.3 수동 트레이스 계측

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

def process_order(order_id: str):
    with tracer.start_as_current_span("process_order") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("order.items", 3)
        try:
            charge_payment(order_id)
            span.add_event("payment.charged")
            span.set_status(trace.StatusCode.OK)
        except Exception as e:
            span.record_exception(e)          # 예외를 span event로 기록
            span.set_status(trace.StatusCode.ERROR, str(e))
            raise

13.4 수동 메트릭 계측

from opentelemetry import metrics

meter = metrics.get_meter(__name__)

order_counter = meter.create_counter(
    "orders.processed",
    unit="1",
    description="처리된 주문 수",
)
latency_hist = meter.create_histogram(
    "order.duration",
    unit="ms",
    description="주문 처리 지연시간",
)

def process_order(order_id):
    start = time.monotonic()
    ...
    order_counter.add(1, {"order.status": "success"})
    latency_hist.record((time.monotonic() - start) * 1000,
                        {"order.status": "success"})

13.5 로그 ↔ 트레이스 상관관계

# 자동계측 사용 시 LoggingInstrumentor가 trace_id/span_id를 로그에 주입
export OTEL_PYTHON_LOG_CORRELATION=true

# 출력 예:
# 2026-06-15 12:00:01 INFO [trace_id=4bf92f… span_id=00f067…] order charged

14. 운영 베스트 프랙티스

  1. service.name은 반드시 설정. 안 하면 unknown_service로 묶여 분석 불가.
  2. Span 이름은 저카디널리티로. GET /user/123(❌) → GET /user/{id}(✅). ID는 attribute로.
  3. 고카디널리티 값은 메트릭 라벨에 넣지 말 것. user_id 같은 값은 메트릭 시계열을 폭발시킨다(cardinality explosion). 트레이스 attribute로.
  4. Collector를 통해 내보내라. 앱이 백엔드에 직접 연결하지 말고 Collector 경유 → 백엔드 교체·재시도·버퍼링이 쉬워진다.
  5. batch + memory_limiter는 기본 장착.
  6. Semantic Conventions를 따르라. 표준 속성명을 써야 자동 대시보드가 작동.
  7. PII/비밀값을 attribute·baggage에 넣지 말 것. 필요하면 Collector의 attributes processor로 마스킹·삭제.
  8. 샘플링은 ParentBased로 일관성 유지 + 에러/지연은 tail 샘플링으로 보존.
  9. 설정은 환경변수로. OTEL_* 표준 환경변수는 모든 언어 SDK에서 공통.

15. 자주 하는 실수 / FAQ

증상 원인 / 해결
트레이스가 서비스 경계에서 끊긴다 Context 전파 누락. 자동계측 미적용 또는 비표준 전송. propagator 확인.
모든 서비스가 unknown_service OTEL_SERVICE_NAME 미설정.
메트릭 시계열이 수백만 개로 폭발 고카디널리티 라벨(user_id, request_id). 라벨에서 제거.
데이터가 백엔드에 안 보임 엔드포인트·포트(4317 gRPC/4318 HTTP)·TLS·방화벽 확인. Collector의 debug exporter로 수신 여부 먼저 확인.
OTLP gRPC vs HTTP 헷갈림 gRPC=4317, HTTP/protobuf=4318. 엔드포인트와 프로토콜을 일치시킬 것.
Collector가 OOM으로 죽음 memory_limiter 미설정 또는 한도 과다. 한도를 컨테이너 메모리의 ~80%로.

16. 용어 사전

용어
OTel OpenTelemetry의 약칭
OTLP OpenTelemetry Protocol. 표준 전송 포맷(gRPC/HTTP+protobuf)
Signal(신호) 텔레메트리 데이터 종류: Traces / Metrics / Logs / (Profiles)
Span 트레이스 내 작업 단위 하나
Trace 한 요청의 전체 여정 (span 트리)
Resource 텔레메트리 생성 주체 설명 속성(service.name 등)
Instrumentation 신호를 생성하는 계측 (자동/수동)
Collector 수신·가공·전송을 담당하는 독립 컴포넌트
Exporter 특정 목적지로 데이터를 내보내는 플러그인
Propagation Context를 서비스 간에 전달하는 것 (W3C Trace Context)
Baggage 트레이스를 따라 흐르는 사용자 정의 키-값
Sampling 저장할 트레이스 표본을 고르는 것 (head/tail)
Cardinality 라벨 값의 가짓수. 높으면 메트릭 비용 폭발
Semantic Conventions 표준 속성 이름 사전

참고 링크

Scroll to Top