본문 바로가기

Observability

OpenTelemetry란 무엇인가: 분산 트레이싱 기본 개념

반응형

마이크로서비스 환경에서 "이 요청이 어디서 느려지는가"를 확인하려면 서비스 간 요청 경로를 추적할 수 있어야 합니다. OpenTelemetry는 이 추적 데이터를 벤더 종속 없이 수집하는 오픈소스 표준입니다. Trace, Span, Context Propagation의 동작 원리를 이해하면 분산 시스템의 병목을 체계적으로 진단할 수 있습니다.

핵심 요약

  • OpenTelemetry(OTel)는 Metrics, Logs, Traces를 수집하는 CNCF Graduated 오픈소스 표준입니다. 2026년 5월 CNCF 졸업(Graduated) 단계에 도달했습니다.
  • 분산 트레이싱은 하나의 요청이 여러 서비스를 거치는 경로를 Trace ID로 연결하여 추적하는 기술입니다.
  • OpenTelemetry는 계측(Instrumentation)과 백엔드(Storage/Analysis)를 분리합니다. SDK로 데이터를 생성하고, Collector로 처리한 뒤, 원하는 백엔드(Jaeger, Datadog, Tempo 등)로 전송합니다.
  • W3C Trace Context 표준(traceparent 헤더)을 통해 서비스 간 Context를 전파합니다.
  • Collector는 Receiver → Processor → Exporter 파이프라인으로 구성되며, 배포 패턴(Agent, Gateway)에 따라 확장성과 운영 복잡도가 달라집니다.

1. 왜 필요한가

5개 마이크로서비스로 구성된 이커머스 시스템을 운영하고 있다고 가정합니다. 사용자가 "결제 완료까지 8초 이상 걸린다"고 신고합니다.

각 서비스의 로그를 확인하면:

  • API Gateway: 요청 수신 → 정상 (50ms)
  • Order Service: 주문 생성 → 정상 (100ms)
  • Inventory Service: 재고 확인 → 정상 (80ms)
  • Payment Service: 결제 요청 → ??? (로그에 에러 없음)
  • Notification Service: 알림 발송 → 정상 (200ms)

개별 서비스는 에러를 남기지 않지만, 전체 응답 시간은 8초입니다. "어디에서 시간이 소요되는가?"를 파악하려면 하나의 요청이 서비스를 거치는 전체 경로를 한 화면에서 볼 수 있어야 합니다.

이것이 분산 트레이싱이 해결하는 문제입니다.

그런데 트레이싱을 도입하려면 또 다른 문제가 생깁니다:

문제 설명
벤더 종속 Datadog SDK로 계측하면 나중에 Jaeger로 바꿀 때 전체 코드 수정 필요
팀별 도구 파편화 A팀은 Zipkin, B팀은 Jaeger를 사용하면 Trace가 연결되지 않음
일관성 부재 서비스마다 다른 형식으로 데이터를 내보내면 중앙에서 분석 불가

OpenTelemetry는 이 문제들을 해결합니다. 계측은 OTel SDK 하나로 통일하고, 백엔드는 자유롭게 선택하거나 교체할 수 있습니다.

2. OpenTelemetry란 무엇인가

OpenTelemetry(OTel)는 Observability 데이터(Metrics, Logs, Traces)를 생성, 수집, 전송하기 위한 오픈소스 표준 프레임워크입니다.

배경

OpenTelemetry는 두 개의 CNCF 프로젝트가 합쳐져 탄생했습니다:

  • OpenTracing (2016): 분산 트레이싱 API 표준
  • OpenCensus (2018): Google이 주도한 메트릭 + 트레이싱 라이브러리

두 프로젝트가 겹치는 영역이 많아 2019년에 합쳐져 OpenTelemetry가 되었습니다. 2026년 5월 21일 CNCF Graduated 프로젝트로 승격되어 프로덕션 사용 준비가 완료된 성숙 단계에 도달했습니다.

OpenTelemetry가 하는 것과 하지 않는 것

OTel이 하는 것 OTel이 하지 않는 것
텔레메트리 데이터 생성 (SDK) 데이터 저장
데이터 수집 및 처리 (Collector) 데이터 분석/시각화
표준 프로토콜 정의 (OTLP) 알림 발생
자동 계측 (Auto-instrumentation) 대시보드 제공
다양한 백엔드로 내보내기 (Exporter) 장애 대응 자동화

핵심 설계 원칙은 "계측과 백엔드의 분리"입니다. 애플리케이션 코드에서 데이터를 생성하는 부분(SDK)과, 그 데이터를 저장하고 분석하는 부분(Backend)을 독립적으로 선택할 수 있습니다.

지원 언어

OpenTelemetry SDK는 주요 언어를 지원합니다:

언어 Traces 상태 Metrics 상태 Logs 상태
Java Stable Stable Stable
Python Stable Stable Stable
Go Stable Stable Stable
JavaScript/Node.js Stable Stable Stable
.NET (C#) Stable Stable Stable
C++ Stable Stable Stable
Rust Stable Stable Stable
PHP Stable Stable Stable
Ruby Stable Stable Stable
Swift Stable Stable Experimental
Erlang/Elixir Stable Stable Experimental

Java, Python, .NET, PHP, JavaScript는 코드 수정 없이 자동 계측(Zero-code instrumentation)도 지원합니다.

3. 전체 아키텍처

OpenTelemetry 전체 아키텍처
OpenTelemetry 전체 아키텍처

OpenTelemetry의 전체 구조는 세 계층으로 나뉩니다:

계층별 역할

계층 구성 요소 역할
생성 (Instrumentation) SDK, Auto-instrumentation 애플리케이션에서 텔레메트리 데이터 생성
수집/처리 (Collection) Collector (Receiver → Processor → Exporter) 데이터를 받아서 가공하고 내보냄
저장/분석 (Backend) Jaeger, Prometheus, Grafana, Datadog 등 데이터를 저장하고 시각화/분석

이 구조의 장점:

  • 백엔드 교체가 자유로움: Jaeger에서 Grafana Tempo로 바꿔도 애플리케이션 코드를 수정하지 않음
  • 데이터 처리를 중앙화: Collector에서 샘플링, 필터링, 메타데이터 추가를 한 곳에서 관리
  • 언어에 구애받지 않음: Java, Python, Go 등 각 서비스 언어에 맞는 SDK를 사용하되, 출력 형식은 OTLP로 통일

4. 분산 트레이싱 핵심 개념

분산 트레이싱은 OpenTelemetry의 세 Signal(Traces, Metrics, Logs) 중 가장 먼저 안정화된 영역이며, 분산 시스템 디버깅의 핵심입니다.

Trace와 Span

용어 설명 비유
Trace 하나의 요청에 대한 전체 처리 경로 택배 배송의 전체 경로 (출발지 → 도착지)
Span 하나의 작업 단위 각 중간 경유지에서의 처리 (집하 → 허브 → 배달)
Root Span Trace의 첫 번째 Span (부모 없음) 최초 출발지
Child Span 다른 Span에 의해 생성된 Span 경유지에서 다음 경유지로 넘기는 과정

하나의 Trace는 여러 Span의 트리 구조입니다:

Trace (trace_id: abc-123)
├─ Span A: API Gateway (root span)
│   ├─ Span B: Order Service
│   │   ├─ Span D: DB Query (SELECT orders)
│   │   └─ Span E: Payment Service 호출
│   │       └─ Span F: 외부 PG API 호출
│   └─ Span C: Notification Service

Span의 구조

각 Span은 다음 정보를 포함합니다:

{
  "name": "POST /api/orders",
  "context": {
    "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "051581bf3cb55c13"
  },
  "parent_span_id": "e457b5a2e4d86bd1",
  "start_time": "2026-06-08T10:15:30.123Z",
  "end_time": "2026-06-08T10:15:30.456Z",
  "duration_ms": 333,
  "status": "OK",
  "kind": "SERVER",
  "attributes": {
    "http.method": "POST",
    "http.url": "/api/orders",
    "http.status_code": 201,
    "service.name": "order-service",
    "db.system": "postgresql"
  },
  "events": [
    {
      "name": "order.validated",
      "timestamp": "2026-06-08T10:15:30.200Z"
    }
  ]
}

주요 필드:

필드 설명
trace_id 요청 전체를 식별하는 128bit ID (모든 서비스가 공유)
span_id 개별 작업을 식별하는 64bit ID
parent_span_id 이 Span을 호출한 부모 Span의 ID
kind SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL 중 하나
attributes 키-값 쌍으로 된 메타데이터 (Semantic Conventions 기반)
events Span 실행 중 발생한 시점별 이벤트
status OK, ERROR, UNSET

Span Kind의 의미

Kind 설명 예시
SERVER 서버로서 요청을 수신 HTTP 서버가 요청을 처리
CLIENT 클라이언트로서 요청을 발신 HTTP 클라이언트가 다른 서비스 호출
PRODUCER 비동기 메시지를 생성 Kafka Producer가 메시지 발행
CONSUMER 비동기 메시지를 소비 Kafka Consumer가 메시지 처리
INTERNAL 내부 작업 (네트워크 호출 없음) 함수 내부 로직

5. Context Propagation: 서비스 간 추적 연결

Context Propagation 동작 원리
Context Propagation 동작 원리

분산 트레이싱에서 가장 중요한 메커니즘은 Context Propagation입니다. 서비스 A가 서비스 B를 호출할 때 "이 요청은 같은 Trace의 일부"라는 정보를 전달해야 합니다.

W3C Trace Context 표준

OpenTelemetry는 W3C Trace Context 표준을 기본 전파 형식으로 사용합니다. HTTP 요청 시 traceparent 헤더에 Context를 담아 전달합니다.

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             ^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^-^^
             |  |                                |                |
             |  trace-id (128bit)               span-id (64bit)  flags
             version                                              (sampled)
필드 설명
version 항상 00 (현재 버전)
trace-id 요청 전체를 식별하는 ID (32 hex chars)
span-id 현재 Span ID (16 hex chars) — 수신 서비스가 parent로 사용
flags 01 = sampled (수집 대상), 00 = not sampled

전파 과정 예시

  1. API Gateway: 요청 수신 → Trace ID가 없으면 새로 생성 → traceparent 헤더에 포함하여 다음 서비스 호출
  2. Order Service: traceparent 헤더에서 trace-id와 parent span-id를 추출 → 새 Span 생성 (parent = 수신한 span-id) → 다음 호출 시 자신의 span-id를 traceparent에 담아 전달
  3. Payment Service: 동일한 과정 반복

이렇게 모든 서비스가 같은 trace-id를 공유하면서 각자의 Span을 기록하면, 나중에 수집기에서 trace-id로 모든 Span을 조합하여 전체 경로를 재구성할 수 있습니다.

전파가 실패하는 경우

원인 증상 해결
미들웨어가 traceparent 헤더를 제거 Trace가 서비스 경계에서 끊어짐 프록시/게이트웨이에서 헤더 보존 설정
비동기 메시징(Kafka)에서 전파 누락 Consumer Span이 별도 Trace로 분리 Message 헤더에 traceparent 포함
서비스가 OTel SDK를 사용하지 않음 해당 서비스의 Span이 누락 SDK 적용 또는 프록시 레벨에서 Span 생성
레거시 시스템이 커스텀 헤더 사용 X-B3-TraceId와 traceparent 혼재 Bridge Propagator 설정
Tip
서비스 메시(Istio, Linkerd)를 사용하면 애플리케이션 코드 수정 없이 사이드카 프록시가 자동으로 traceparent 헤더를 전파합니다. 다만 애플리케이션 내부의 상세 Span(DB 쿼리, 비즈니스 로직)은 여전히 SDK로 계측해야 합니다.

6. OpenTelemetry Collector

Collector는 OpenTelemetry의 핵심 구성 요소입니다. 애플리케이션에서 생성한 텔레메트리 데이터를 수신하고, 가공하고, 원하는 백엔드로 내보내는 중간 에이전트 역할을 합니다.

왜 Collector를 사용하는가

SDK에서 직접 백엔드로 보내지 않고 Collector를 중간에 두는 이유:

이점 설명
백엔드 변경 시 코드 수정 불필요 Collector 설정만 바꾸면 됨
중앙 집중 처리 샘플링, 필터링, 메타데이터 추가를 한 곳에서 관리
버퍼링/재시도 백엔드 장애 시 데이터 유실 방지
다중 백엔드 전송 하나의 데이터를 여러 백엔드로 동시에 전송 가능
리소스 절약 배치 처리로 네트워크 호출 횟수 감소

Collector 파이프라인 구조

Collector 파이프라인 구조
Collector 파이프라인 구조

Collector는 세 가지 핵심 컴포넌트로 구성됩니다:

Receiver: 데이터를 수신하는 입구

Receiver 프로토콜 용도
otlp gRPC, HTTP OTel SDK에서 보내는 데이터 수신 (기본)
jaeger Thrift, gRPC 기존 Jaeger 에이전트에서 전환 시
prometheus Scrape Prometheus 메트릭 수집
filelog 파일 읽기 로그 파일 수집

Processor: 데이터를 가공하는 중간 처리

Processor 역할
batch 데이터를 묶어서 일괄 전송 (네트워크 효율화)
filter 조건에 맞지 않는 데이터 제거 (헬스체크 등)
tail_sampling 조건부 수집 (에러 100%, 정상 10%)
attributes 속성 추가/수정/삭제 (환경 정보, 팀 태그 등)
resource 리소스 정보 추가 (service.name, k8s.namespace 등)
memory_limiter 메모리 사용량 제한 (OOM 방지)

Exporter: 데이터를 내보내는 출구

Exporter 대상
otlp OTLP 호환 백엔드 (Tempo, SigNoz, Datadog 등)
prometheus Prometheus Remote Write
jaeger Jaeger 백엔드
loki Grafana Loki
debug 콘솔 출력 (디버깅용)

설정 예시

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

processors:
  batch:
    send_batch_size: 1024
    timeout: 5s
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  filter:
    traces:
      span:
        - 'attributes["http.route"] == "/health"'
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-traces
        type: latency
        latency: {threshold_ms: 3000}
      - name: default
        type: probabilistic
        probabilistic: {sampling_percentage: 10}

exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889

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

이 설정의 의미:

  • OTLP gRPC(4317)와 HTTP(4318)로 데이터 수신
  • 헬스체크 요청(/health)은 필터링으로 제거
  • 에러와 3초 이상 지연 Trace는 100% 수집, 나머지는 10% 샘플링
  • Traces는 Grafana Tempo로, Metrics는 Prometheus로 전송

7. 배포 패턴

배포 패턴: Agent + Gateway
배포 패턴: Agent + Gateway

Collector를 어떻게 배포하느냐에 따라 운영 복잡도와 확장성이 달라집니다.

Agent 패턴

각 노드(또는 Pod)에 Collector를 배치하는 방식입니다. Kubernetes에서는 DaemonSet으로 배포합니다.

# DaemonSet으로 Agent 배포
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-agent
spec:
  selector:
    matchLabels:
      app: otel-agent
  template:
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:0.115.0
          ports:
            - containerPort: 4317  # OTLP gRPC
            - containerPort: 4318  # OTLP HTTP

장점:

  • 로컬 네트워크로 데이터 전송 (지연 낮음)
  • 노드별 리소스 메타데이터 자동 수집 가능

단점:

  • 노드 수에 비례하여 Collector 인스턴스 증가
  • Tail-based Sampling 불가 (전체 Trace를 한 Agent가 볼 수 없음)

Gateway 패턴

중앙에 Collector 클러스터를 배치하고, 모든 서비스가 이곳으로 데이터를 전송합니다.

장점:

  • Tail-based Sampling 가능 (전체 Trace를 모아서 판단)
  • 중앙 집중 관리 (설정 변경을 한 곳에서)
  • 다중 백엔드 라우팅

단점:

  • 단일 장애점(SPOF) 위험 → 고가용성 구성 필요
  • 네트워크 홉 추가

Agent + Gateway 조합 (권장)

실무에서는 두 패턴을 조합합니다:

  1. Agent (DaemonSet): 각 노드에서 데이터 수집, 기본 배치 처리 후 Gateway로 전송
  2. Gateway (Deployment): 중앙에서 Tail-based Sampling, 메타데이터 보강, 다중 백엔드 라우팅

이 조합이 권장되는 이유:

  • Agent가 로컬에서 빠르게 수집하여 애플리케이션 성능에 미치는 영향을 최소화
  • Gateway에서 전체 Trace를 모아 지능적인 샘플링 결정
  • Gateway를 수평 확장하여 트래픽 증가에 대응

8. 실무 적용 시나리오: Kubernetes에서 OTel 도입

스타트업의 Kubernetes 환경(EKS)에서 5개 마이크로서비스를 운영한다고 가정합니다. 현재 로그만 CloudWatch로 수집하고 있고, 서비스 간 지연 문제가 자주 발생합니다.

단계별 도입 전략

1단계 — Auto-instrumentation으로 시작 (코드 수정 최소)

Java/Python/Node.js 서비스라면 OTel Auto-instrumentation Agent를 사용하여 코드 수정 없이 기본 Span을 생성할 수 있습니다.

# Kubernetes에서 Java 서비스에 Auto-instrumentation 적용
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-java: "true"
    spec:
      containers:
        - name: order-service
          image: order-service:v1.2
          env:
            - name: OTEL_SERVICE_NAME
              value: "order-service"
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: "http://otel-agent:4317"

이것만으로 HTTP 요청/응답, DB 쿼리, gRPC 호출에 대한 Span이 자동 생성됩니다.

2단계 — 핵심 비즈니스 로직에 Manual Span 추가

Auto-instrumentation은 프레임워크 수준의 Span만 생성합니다. "결제 검증 → 포인트 차감 → PG 호출"처럼 비즈니스 로직 내부를 추적하려면 수동으로 Span을 추가합니다.

# Python 예시: 결제 처리 내부 추적
from opentelemetry import trace

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

def process_payment(order_id, amount):
    with tracer.start_as_current_span("validate_payment") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("payment.amount", amount)
        validate_card()

    with tracer.start_as_current_span("call_pg_api") as span:
        span.set_attribute("pg.provider", "nice-pay")
        result = call_external_pg(amount)
        span.set_attribute("pg.response_code", result.code)
        return result

3단계 — 샘플링과 비용 최적화

트래픽이 많아지면 모든 Trace를 수집하는 것은 비용적으로 비현실적입니다. Gateway에서 Tail-based Sampling을 적용합니다:

  • 에러가 발생한 Trace: 100% 수집
  • 3초 이상 지연된 Trace: 100% 수집
  • 정상 Trace: 10% 샘플링

4단계 — 대시보드와 알림 연동

수집된 Trace 데이터를 기반으로:

  • Grafana에서 서비스 의존성 맵 시각화
  • p99 응답시간 알림 설정
  • 에러율 급증 시 관련 Trace로 바로 점프

9. Semantic Conventions: 표준화된 속성 이름

OpenTelemetry는 Span 속성(Attributes)의 이름을 표준화하여 서로 다른 언어, 서로 다른 팀이 만든 서비스의 데이터를 일관되게 분석할 수 있도록 합니다. 이를 Semantic Conventions라고 합니다.

주요 Semantic Conventions

HTTP 관련:

속성 설명 예시
http.request.method HTTP 메서드 GET, POST
url.full 전체 URL https://api.example.com/orders
http.response.status_code 응답 코드 200, 500
server.address 서버 호스트 api.example.com

데이터베이스 관련:

속성 설명 예시
db.system DB 종류 postgresql, mysql, redis
db.operation.name 작업 유형 SELECT, INSERT
db.collection.name 테이블/컬렉션 orders, users

Kubernetes 관련:

속성 설명 예시
k8s.namespace.name 네임스페이스 production
k8s.pod.name Pod 이름 order-service-7b4d5c-xkf2
k8s.deployment.name Deployment 이름 order-service
k8s.node.name 노드 이름 ip-10-0-1-42

Semantic Conventions를 따르면 서로 다른 서비스에서 온 데이터를 db.system = "postgresql"로 일관되게 필터링할 수 있습니다. 팀마다 database_type, db_kind, dbSystem 등 제각각 이름을 붙이면 분석이 어렵습니다.

10. OTLP: OpenTelemetry Protocol

OTLP(OpenTelemetry Protocol)는 텔레메트리 데이터를 전송하는 표준 프로토콜입니다. SDK → Collector, Collector → Backend 간 통신에 사용됩니다.

OTLP vs 다른 프로토콜

프로토콜 데이터 유형 특징
OTLP gRPC Traces, Metrics, Logs 바이너리 인코딩, 양방향 스트리밍, 성능 최적
OTLP HTTP Traces, Metrics, Logs HTTP/1.1 호환, 프록시 통과 용이
Jaeger Thrift Traces만 레거시, 점차 OTLP로 전환
Zipkin JSON Traces만 레거시, 경량
Prometheus Remote Write Metrics만 Prometheus 생태계 호환

OTLP의 장점:

  • Traces, Metrics, Logs를 하나의 프로토콜로 통합
  • Protobuf 기반 바이너리 인코딩으로 네트워크 효율성 높음
  • gRPC 사용 시 양방향 스트리밍과 헤더 압축 지원
Tip
새로 구성하는 환경이라면 OTLP gRPC를 기본으로 사용합니다. 로드밸런서나 프록시가 gRPC를 지원하지 않는 환경에서만 OTLP HTTP를 선택합니다. Jaeger, Zipkin 형식은 기존 시스템과의 호환이 필요한 경우에만 사용합니다.

11. 보안 고려사항

Security Note
Trace 데이터에는 요청 URL, 쿼리 파라미터, 헤더 값이 포함될 수 있습니다. 민감 정보가 Span 속성에 기록되지 않도록 설계 단계에서 정책을 수립해야 합니다.

민감 정보 노출 위험

위험 예시 대응
URL에 토큰 포함 /api/users?token=abc123이 url.full 속성에 기록됨 URL 파라미터 마스킹 또는 경로만 기록
요청 헤더에 인증 정보 Authorization 헤더가 Span 속성에 기록됨 민감 헤더 수집 제외 설정
DB 쿼리에 사용자 데이터 SELECT * FROM users WHERE email='user@email.com'이 db.statement에 기록됨 쿼리 난독화(Obfuscation) 적용
Span 속성에 PII 포함 user.email, user.phone을 커스텀 속성으로 기록 PII 속성 사용 금지 정책

Collector에서의 보안 처리

processors:
  attributes:
    actions:
      # 민감 속성 삭제
      - key: http.request.header.authorization
        action: delete
      - key: user.email
        action: delete
      # URL 쿼리 파라미터 제거
      - key: url.full
        action: hash  # 해시 처리

통신 보안

  • SDK → Collector: 클러스터 내부 통신이면 mTLS 또는 서비스 메시로 암호화
  • Collector → Backend: TLS 필수 (외부 SaaS 백엔드로 전송 시)
  • Collector 엔드포인트 접근 제어: NetworkPolicy로 허용된 Pod만 접근 가능하도록 제한

12. 비용/운영 고려사항

주의
분산 트레이싱의 데이터 볼륨은 트래픽에 비례하여 급증합니다. 초당 10,000 요청 × 5 서비스 × 평균 3개 Span = 초당 150,000 Span입니다. 샘플링 없이 100% 수집하면 저장 비용이 빠르게 증가합니다.

비용 구조

항목 비용 발생 요인 최적화 방향
Collector 리소스 CPU/메모리 사용량 배치 크기 조정, 불필요 데이터 필터링
네트워크 전송 Span 수 × Span 크기 샘플링, 속성 수 제한
백엔드 저장 수집된 Span 총량 × 보존 기간 샘플링 비율 조정, 보존 정책
쿼리 비용 조회 빈도와 쿼리 복잡도 인덱싱 최적화

샘플링 전략 비교

전략 장점 단점 적합 환경
Head-based (10%) 구현 간단, 예측 가능한 비용 에러 Trace를 놓칠 수 있음 트래픽이 많고 비용 민감
Tail-based (조건부) 중요한 Trace를 놓치지 않음 메모리 사용량 높음, Gateway 필요 에러/지연 분석이 중요
Head + Tail 조합 균형 잡힌 수집 구성 복잡도 증가 대부분의 운영 환경

운영 체크리스트

  • [ ] Collector에 memory_limiter 설정 (OOM 방지)
  • [ ] 백엔드 장애 시 Collector의 재시도 정책 확인
  • [ ] 샘플링 비율을 트래픽 변화에 맞게 조정
  • [ ] Span 속성 수와 크기 제한 (불필요하게 큰 데이터 방지)
  • [ ] Collector 자체의 모니터링 (자신의 메트릭을 Prometheus로 노출)
  • [ ] 보존 기간 정의: Trace 7~14일, 중요 Trace 30일

13. 기존 도구와의 관계

OpenTelemetry와 Jaeger/Zipkin

Jaeger와 Zipkin은 트레이싱 백엔드(저장/시각화)입니다. OpenTelemetry는 데이터 생성/수집 표준입니다. 경쟁이 아니라 보완 관계입니다.

[AS-IS] 서비스 → Jaeger SDK → Jaeger Backend
[TO-BE] 서비스 → OTel SDK → OTel Collector → Jaeger Backend (또는 Tempo, Datadog)

전환 시 장점:

  • Jaeger SDK를 OTel SDK로 교체하면 나중에 Tempo나 Datadog으로 백엔드를 바꿀 수 있음
  • Jaeger 자체도 OTLP 수신을 지원하므로 호환됨

OpenTelemetry와 Prometheus

Prometheus는 메트릭 수집/저장 도구입니다. OpenTelemetry는 Prometheus 형식의 메트릭도 수집할 수 있고(prometheus receiver), Prometheus로 내보낼 수도 있습니다(prometheus exporter).

  • 기존 Prometheus 환경에 트레이싱을 추가하고 싶다면: OTel SDK + Collector로 Trace를 수집하고, 메트릭은 기존 Prometheus 유지
  • 새 환경을 구축한다면: OTel SDK로 Metrics와 Traces를 모두 수집 → Collector에서 각각 Prometheus와 Tempo로 라우팅

OpenTelemetry와 Datadog/New Relic

상용 APM 도구들은 이미 OTLP 수신을 지원합니다. OTel SDK로 계측하고 Collector에서 Datadog Exporter로 전송하면 됩니다.

장점:

  • 나중에 비용 문제로 오픈소스 백엔드로 전환할 때 코드 수정 없음
  • 멀티 벤더 전략 가능 (Traces는 Datadog, Metrics는 Prometheus)

14. 자주 하는 실수

1. Collector 없이 SDK에서 직접 백엔드로 전송

초기에는 간단해 보이지만, 백엔드 변경 시 모든 서비스의 환경 변수를 수정해야 합니다. Collector를 중간에 두면 설정 한 곳만 바꾸면 됩니다.

2. 모든 속성을 무분별하게 추가

Span에 속성을 많이 추가할수록 데이터 크기가 커집니다. 분석에 실제로 필요한 속성만 추가하고, Semantic Conventions를 따릅니다.

3. 샘플링을 고려하지 않고 시작

개발 환경에서 100% 수집으로 시작했다가 프로덕션에서 트래픽이 늘어나면 비용이 폭발합니다. 처음부터 샘플링 정책을 설계합니다.

4. traceparent 전파를 확인하지 않음

SDK를 설치했지만 서비스 간 Trace가 연결되지 않는 경우가 많습니다. 프록시나 메시지 큐에서 traceparent 헤더가 전달되는지 확인해야 합니다.

5. Collector의 리소스 제한을 설정하지 않음

Collector에 memory_limiter를 설정하지 않으면, 백엔드 장애 시 데이터가 Collector에 쌓여 OOM이 발생합니다. 반드시 리소스 제한을 설정합니다.

6. Auto-instrumentation만으로 충분하다고 생각

Auto-instrumentation은 HTTP, DB, gRPC 같은 프레임워크 수준의 Span만 생성합니다. "결제 검증에 3초 걸렸다"를 확인하려면 비즈니스 로직에 Manual Span을 추가해야 합니다.

15. 정리

  • OpenTelemetry는 Observability 데이터를 벤더 중립적으로 수집하는 CNCF Graduated 표준입니다. 계측(SDK)과 백엔드(Storage)를 분리하여 도구 교체를 자유롭게 합니다.
  • 분산 트레이싱의 핵심은 Trace ID를 서비스 간 전파하여 하나의 요청 경로를 재구성하는 것입니다. W3C Trace Context(traceparent 헤더)가 표준 전파 형식입니다.
  • Collector는 Receiver → Processor → Exporter 파이프라인으로 구성됩니다. 샘플링, 필터링, 메타데이터 보강을 중앙에서 관리합니다.
  • 배포 패턴은 Agent(DaemonSet) + Gateway(Deployment) 조합이 운영 환경에서 적합합니다.
  • 비용 관리가 중요합니다. Tail-based Sampling(에러/지연 100% + 정상 10%)으로 중요한 데이터를 놓치지 않으면서 비용을 제어합니다.
  • 신규 서비스는 처음부터 OTel SDK로 계측하는 것이 유리합니다. 기존 서비스는 Auto-instrumentation으로 시작하고 점진적으로 Manual Span을 추가합니다.

관련 글

참고 문서

반응형