#OTel 운영 플레이북

버전: 0.33.0 최종 업데이트: 2026-03-15 적용 대상: ranvier-inspector 카테고리: 가이드


#1. 목적

이 플레이북은 기술적 OTel 통합 (M149, 검증된 스모크 테스트)과 실제 조직에서의 운영 배포 사이의 격차를 해소합니다. 자격증명 매핑, 정책 적용, 벤더 구성, 환경 전략, 보안 강화, 멀티 테넌트 격리, 문제 해결을 다룹니다.

전제조건: otel_interop_matrix.md에 설명된 기본 OTel 상호운용성 검증을 완료했어야 합니다. 이 플레이북은 Ranvier 애플리케이션이 이미 ranvier-inspector를 통해 span을 생성하고 있다고 가정합니다.


#2. 자격증명 매핑

#2.1 패턴 개요

Service Account  →  OTLP Auth Header  →  Collector  →  Backend

Ranvier는 OTLP (gRPC 또는 HTTP/protobuf)를 통해 트레이스를 내보냅니다. 인증은 Collector exporter 수준에서 설정되며 — Ranvier 애플리케이션 코드에서가 아닙니다. 이를 통해 비밀 관리가 애플리케이션 배포와 분리됩니다.

#2.2 API 키 / Bearer 토큰 매핑

권장 패턴: 환경 변수 주입

# otel-collector.yaml — exporter 섹션
exporters:
  otlp/backend:
    endpoint: "https://otlp.backend.example.com:4317"
    headers:
      Authorization: "${BACKEND_OTLP_TOKEN}"   # 런타임에 주입
    tls:
      insecure: false

토큰 주입 방법:

  • Kubernetes Secret → Collector 디플로이먼트에 환경 변수로 마운트
  • Docker Composeenv_file: 또는 secrets: 블록
  • CI/CD → 배포 시 Collector 컨테이너에 주입

소스 컨트롤에 커밋되는 설정 파일에 토큰을 절대 포함하지 마세요.

#2.3 환경별 계정 분리

환경 서비스 계정 토큰 소스
dev svc-ranvier-dev .env.local (커밋하지 않음)
staging svc-ranvier-staging CI 시크릿 OTEL_STAGING_TOKEN
prod svc-ranvier-prod Vault / KMS / 클라우드 시크릿 매니저

#2.4 멀티 백엔드 자격증명 격리

여러 백엔드로 전송하는 경우 (예: 프로덕션에는 Datadog, 스테이징 디버그에는 Jaeger):

exporters:
  otlphttp/datadog:
    endpoint: "https://trace.agent.datadoghq.com"
    headers:
      DD-API-KEY: "${DATADOG_API_KEY}"
  otlp/jaeger:
    endpoint: "jaeger:4317"
    tls:
      insecure: true

자격증명 유출을 방지하기 위해 백엔드별로 별도의 파이프라인을 사용하세요.


#3. 수정(Redaction) 정책 선택

Ranvier의 ranvier-inspector 확장은 OTLP exporter 수준에서 적용되는 세 가지 수정 모드를 지원합니다:

모드 설명 사용 사례
public 모든 사용자 식별 가능 필드 제거 외부 공개 내보내기 (CDN 로그, 서드파티 SaaS)
internal 서비스/트레이스 메타데이터 유지, PII 제거 내부 관측 플랫폼
strict 모든 것 유지 (원시 span) 로컬 개발, Jaeger 스모크 테스트

#3.1 모드 선택

Collector에서 환경 변수로 설정합니다:

processors:
  attributes/redact_public:
    actions:
      - key: user.id
        action: delete
      - key: user.email
        action: delete
      - key: http.request.header.authorization
        action: delete

또는 Ranvier의 inspector 확장을 통해 설정합니다:

// ranvier-inspector 설정에서
Inspector::builder()
    .redaction_mode(RedactionMode::Public)   // 또는 Internal, Strict

#3.2 프로덕션에서의 적용

심층 방어를 위해 두 경계에서 수정을 적용합니다:

  1. 애플리케이션 수준 (ranvier-inspector): span 생성 시 제거
  2. Collector 수준 (attribute 프로세서): 내보내기 전 최종 게이트

이를 통해 애플리케이션 수준 필터가 잘못 구성된 경우에도 우발적 유출을 방지합니다.

#3.3 검증된 수정 경로

Ranvier → OTel Collector (redaction processor) → OTLP exporter

근거: docs/03_guides/otel_collector_otlp_redaction_smoke.md


#4. 환경 기반 구성

#4.1 구성 전략

dev       → stdout/console exporter (네트워크 의존성 없음)
staging   → OTel Collector → Jaeger (로컬 Docker)
prod      → OTel Collector → Backend SaaS (Datadog / New Relic)

#4.2 Dev (로컬 콘솔)

# config/otel.dev.toml
[otel]
exporter = "console"
sampler  = "always_on"
service_name = "${SERVICE_NAME:-my-service}-dev"

Collector가 필요 없습니다. 개발 중에 span이 stdout에 표시됩니다.

#4.3 Staging (Docker Compose를 통한 Jaeger)

# docker-compose.staging.yml
services:
  jaeger:
    image: jaegertracing/all-in-one:1.55
    ports:
      - "16686:16686"  # UI
      - "4317:4317"    # OTLP gRPC
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.96.0
    volumes:
      - ./docs/03_guides/otel_collector_jaeger_config.yaml:/etc/otelcol/config.yaml
    depends_on: [jaeger]

참조 구성: docs/03_guides/otel_collector_jaeger_config.yaml

#4.4 프로덕션 (SaaS 백엔드)

docs/03_guides/otel_vendor_configs/에 있는 벤더별 구성을 사용합니다.

프로덕션 주요 원칙:

  • 항상 TLS 사용 (insecure: false)
  • 복원력을 위해 retry_on_failuresending_queue 설정
  • 적절한 비율로 sampler = "parentbased_traceidratio" 사용 (예: 높은 트래픽 서비스에서 0.1)
# 권장 프로덕션 샘플러
sampler:
  type: parentbased_traceidratio
  argument: "0.1"   # 루트 span의 10% 샘플링

#5. 벤더별 빠른 참조

벤더 엔드포인트 인증 설정 파일
Datadog https://trace.agent.datadoghq.com DD-API-KEY 헤더 otel_collector_datadog_class_backend_config.yaml
New Relic https://otlp.nr-data.net:4317 api-key 헤더 otel_vendor_configs/new_relic.yaml
Honeycomb https://api.honeycomb.io:443 x-honeycomb-team 헤더 otel_vendor_configs/honeycomb.yaml
Jaeger jaeger:4317 (gRPC, 로컬) 없음 (내부) otel_collector_jaeger_config.yaml
Tempo tempo:4317 (gRPC, 로컬) 없음 (내부) otel_collector_tempo_config.yaml

빠른 시작 안내는 docs/03_guides/otel_vendor_configs/README.md를 참조하세요.


#6. 보안 강화

#6.1 토큰 로테이션

  1. 백엔드 API 키를 일정에 따라 로테이션 (30–90일)
  2. 지원되는 경우 단기 토큰 사용 (OIDC 워크로드 아이덴티티)
  3. Vault / AWS Secrets Manager / GCP Secret Manager에 시크릿 저장
  4. OTLP 인증 헤더를 절대 로깅하지 않음 — Collector 로그 수준을 warn으로 설정

#6.2 TLS 구성

모든 프로덕션 OTLP 연결은 TLS를 사용해야 합니다:

exporters:
  otlp/backend:
    tls:
      insecure: false
      ca_file: /etc/ssl/certs/ca-certificates.crt   # 또는 벤더 CA 번들

자체 호스팅 백엔드 (Jaeger/Tempo)의 스테이징 환경에서는 mTLS를 권장합니다:

tls:
  cert_file: /certs/client.crt
  key_file: /certs/client.key
  ca_file: /certs/ca.crt

#6.3 네트워크 정책 (Kubernetes)

애플리케이션 파드에서 Collector로만 송신을 제한합니다:

# NetworkPolicy: app → collector만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-otel-collector
spec:
  podSelector:
    matchLabels:
      app: ranvier-service
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: otel-collector
      ports:
        - protocol: TCP
          port: 4317

#7. 멀티 테넌트 관측 격리

여러 테넌트가 동일한 Ranvier 배포를 사용하는 경우:

#7.1 속성 기반 격리

모든 span에 tenant.id 속성을 태그합니다:

// Ranvier transition에서
#[transition]
async fn handle(_: (), _: &(), bus: &mut Bus) -> Outcome<String, Error> {
    let tenant = bus.read::<TenantContext>().map(|t| t.id.clone()).unwrap_or_default();
    tracing::Span::current().set_attribute("tenant.id", tenant);
    Outcome::Next("ok".to_string())
}

#7.2 Collector 수준 라우팅

tenant.id를 기반으로 span을 다른 백엔드로 라우팅합니다:

processors:
  filter/tenant_a:
    spans:
      include:
        match_type: strict
        attributes:
          - key: tenant.id
            value: "tenant-a"

exporters:
  otlp/tenant_a:
    endpoint: "https://tenant-a.observability.example.com:4317"
    headers:
      Authorization: "${TENANT_A_TOKEN}"

#7.3 데이터셋 격리 (Honeycomb / New Relic)

Honeycomb과 New Relic 모두 API 키를 통해 기본적으로 데이터셋/팀 수준 격리를 지원합니다. 테넌트별 API 키를 사용하고 위의 Collector 라우팅 패턴으로 주입합니다.


#8. 문제 해결 런북

#8.1 Span이 표시되지 않음

증상 예상 원인 조치
Jaeger UI에 출력 없음 Collector 미실행 docker ps / podman ps — Collector 시작
Collector 실행 중이나 span 없음 OTLP 엔드포인트 잘못 설정 OTEL_EXPORTER_OTLP_ENDPOINT 환경 변수 확인
Collector 로그에 span 표시되나 백엔드에 없음 백엔드 인증 실패 API 키 확인, curl로 테스트
샘플러가 모든 span 드롭 traceidratio가 너무 낮음 디버깅을 위해 1.0으로 설정
# 빠른 연결 테스트 (gRPC)
grpcurl -plaintext localhost:4317 list

# 빠른 연결 테스트 (HTTP/protobuf)
curl -I http://localhost:4318/v1/traces

#8.2 수정(Redaction)이 작동하지 않음

  • ranvier-inspector 버전 ≥ 0.26.0 확인
  • 애플리케이션 수준과 Collector 수준 수정이 모두 구성되어 있는지 확인
  • Collector 디버그 로그를 일시적으로 활성화: service.telemetry.logs.level: debug

#8.3 높은 카디널리티 / 샘플링

높은 트래픽 서비스 (>1000 RPS)의 경우 부모 기반 비율 샘플링을 사용합니다:

OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.05   # 5%

#8.4 Collector의 메모리 / CPU 급증

  • memory_limiter 프로세서 활성화 (항상 파이프라인의 첫 번째)
  • batch 프로세서에 send_batch_size: 1024timeout: 10s 사용
  • 로드 밸런서 뒤에서 Collector를 수평 확장
processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 75
    spike_limit_percentage: 20
  batch:
    send_batch_size: 1024
    timeout: 10s

#8.5 시계 스큐 / Span 순서 문제

Jaeger/Tempo에서 span이 순서가 맞지 않게 표시되는 경우:

  • 모든 서비스가 NTP 동기화를 사용하는지 확인
  • OTEL_SDK_DISABLED=false를 설정하고 컨테이너의 시계 소스를 확인

#9. 참조 링크

문서 목적
`otel_interop_matrix.md` 검증된 상호운용성 경로 (M149)
`otel_collector_smoke_baseline.md` 디버그 exporter 스모크 테스트
`otel_collector_jaeger_smoke.md` Jaeger 스모크 테스트
`otel_collector_tempo_smoke.md` Tempo 스모크 테스트
`otel_collector_datadog_class_smoke.md` Datadog 클래스 릴레이 스모크 테스트
`otel_collector_otlp_redaction_smoke.md` 수정 어댑터 스모크 테스트
`otel_vendor_configs/README.md` 벤더 설정 카탈로그
ranvier/examples/otel-ops-demo/ 정책 적용 데모