Skip to content
Published on

OpenTelemetry Collector 운영 완벽 가이드 — 파이프라인 구성부터 백엔드 연동까지

Authors
  • Name
    Twitter
OpenTelemetry Collector Operations Guide

들어가며

마이크로서비스 아키텍처가 보편화되면서 관측 가능성(Observability)은 선택이 아닌 필수가 되었다. 수십에서 수백 개의 서비스가 상호 통신하는 분산 환경에서 Traces, Metrics, Logs라는 세 가지 텔레메트리 신호를 효율적으로 수집하고 처리하는 것은 안정적인 서비스 운영의 기본 전제 조건이다.

OpenTelemetry Collector는 CNCF(Cloud Native Computing Foundation) 졸업 프로젝트인 OpenTelemetry의 핵심 컴포넌트로, 벤더에 종속되지 않는 텔레메트리 파이프라인을 제공한다. 애플리케이션이 생성한 텔레메트리 데이터를 수신(Receiver)하고, 가공(Processor)하고, 원하는 백엔드로 내보내는(Exporter) 역할을 담당한다.

2026년 현재 OpenTelemetry는 사실상 관측 가능성 계측의 업계 표준이 되었다. Jaeger v2는 내부적으로 OpenTelemetry Collector 기반으로 재설계되었고, Zipkin Exporter는 2025년부터 공식적으로 폐기 예정(Deprecated) 상태로 전환되었다. 이 흐름에서 Collector의 올바른 운영 방법을 이해하는 것은 플랫폼 엔지니어와 SRE에게 핵심 역량이다.

이 글에서는 OpenTelemetry Collector의 아키텍처, 파이프라인 설정, 다양한 백엔드 연동, Kubernetes 배포 전략, 샘플링 기법, 성능 튜닝, 트러블슈팅, 장애 복구까지 운영에 필요한 모든 내용을 실전 예제와 함께 총정리한다.


OpenTelemetry 아키텍처와 Collector의 역할

전체 아키텍처 개요

OpenTelemetry 프레임워크는 크게 세 가지 계층으로 구성된다.

  1. SDK/API 계층: 각 언어별 SDK가 애플리케이션 코드에서 텔레메트리 데이터를 생성한다.
  2. Collector 계층: 생성된 텔레메트리 데이터를 수신, 가공, 라우팅한다.
  3. Backend 계층: 최종 저장소(Jaeger, Tempo, Datadog 등)에서 데이터를 저장하고 시각화한다.
[애플리케이션 + OTel SDK]
        |
        | OTLP (gRPC/HTTP)
        v
+========================+
|   OpenTelemetry        |
|   Collector            |
|                        |
|  Receiver -> Processor |
|          -> Exporter   |
+========================+
    |         |         |
    v         v         v
 [Jaeger]  [Tempo]  [Datadog]

Collector는 이 아키텍처에서 중앙 허브 역할을 수행한다. 애플리케이션은 OTLP(OpenTelemetry Protocol)로 데이터를 전송하기만 하면 되고, 어떤 백엔드를 사용할지는 Collector 설정에서 결정한다. 이를 통해 백엔드 교체 시 애플리케이션 코드 변경 없이 Collector 설정만 수정하면 된다.

Core vs Contrib 배포판

OpenTelemetry Collector는 두 가지 배포판을 제공한다.

항목CoreContrib
컴포넌트 범위핵심 Receiver/Processor/Exporter만 포함커뮤니티 기여 컴포넌트 다수 포함
바이너리 크기약 50MB약 200MB 이상
보안 노출 면적작음넓음
적합한 환경커스텀 빌드 기반 프로덕션PoC, 테스트, 초기 도입

프로덕션 환경에서는 OpenTelemetry Collector Builder(OCB)를 사용하여 필요한 컴포넌트만 포함한 커스텀 바이너리를 빌드하는 것이 보안과 리소스 효율 양면에서 권장된다.


분산 트레이싱 도구 비교: OpenTelemetry vs Jaeger vs Zipkin

Collector 구성을 다루기 전에, 주요 분산 트레이싱 도구의 위상 변화를 먼저 정리한다.

항목OpenTelemetryJaegerZipkin
프로젝트 상태CNCF Graduated (활발히 개발 중)CNCF Graduated (v2 전환 완료)독립 프로젝트 (유지보수 모드)
주요 언어Go (Collector), 다중 언어 SDKGoJava
역할계측 프레임워크 + 수집 파이프라인트레이스 저장/쿼리/시각화 백엔드트레이스 저장/쿼리/시각화 백엔드
프로토콜OTLP (gRPC, HTTP/protobuf)OTLP, Thrift (Legacy)HTTP/JSON, Thrift
텔레메트리 범위Traces + Metrics + LogsTraces만Traces만
Kubernetes 지원Operator, Helm Chart, DaemonSet/DeploymentHelm Chart, OperatorHelm Chart
백엔드 연동벤더 중립 (모든 백엔드 지원)자체 UI + Elasticsearch/Cassandra/ClickHouse자체 UI + Elasticsearch/Cassandra
샘플링Head-based + Tail-based (Collector)Remote Sampling API확률적 샘플링
2026년 권장계측 표준으로 사용OTLP 기반 v2로 전환 권장신규 도입 비권장 (Deprecated 예정)

핵심 요약: OpenTelemetry는 계측과 수집의 표준, Jaeger v2는 OTLP 네이티브 백엔드, Zipkin은 레거시 마이그레이션 대상이다. 2025년부터 OpenTelemetry SDK의 Zipkin Exporter가 공식 폐기 예정으로 전환되었으므로, 신규 프로젝트에서는 OTLP 기반 아키텍처를 채택하는 것이 바람직하다.


Receiver-Processor-Exporter 파이프라인 구성

파이프라인 구조 이해

OpenTelemetry Collector의 설정 파일은 네 가지 최상위 섹션으로 구성된다.

  • receivers: 데이터 수신 방법 정의
  • processors: 데이터 가공/변환/필터링 규칙 정의
  • exporters: 데이터 전송 대상 정의
  • service: 위 컴포넌트들을 조합하여 실제 파이프라인을 활성화

중요한 점은 receivers, processors, exporters 섹션에서 컴포넌트를 정의하더라도 service.pipelines에서 참조하지 않으면 활성화되지 않는다는 것이다.

기본 파이프라인 설정 예제

다음은 OTLP Receiver로 트레이스를 수신하고, 배치 처리 후 OTLP Exporter로 전송하는 기본 설정이다.

# 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
    spike_limit_mib: 128
  batch:
    send_batch_size: 8192
    timeout: 200ms
    send_batch_max_size: 0

exporters:
  otlp:
    endpoint: tempo.monitoring.svc.cluster.local:4317
    tls:
      insecure: true
  debug:
    verbosity: detailed

extensions:
  health_check:
    endpoint: 0.0.0.0:13133
  zpages:
    endpoint: 0.0.0.0:55679

service:
  extensions: [health_check, zpages]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp, debug]

이 설정에서 핵심적인 부분은 프로세서의 순서이다. memory_limiter를 항상 첫 번째 프로세서로 배치해야 한다. 메모리 제한을 초과하면 데이터를 일찍 거부하여 Collector의 OOM(Out of Memory) 크래시를 방지하기 위함이다. batch는 반드시 memory_limiter와 샘플링 프로세서 뒤에 배치하여, 데이터 드롭이 완료된 후에 배치를 구성해야 한다.


Receiver 상세 설정

OTLP Receiver

가장 기본적이고 권장되는 Receiver이다. gRPC(4317)와 HTTP(4318) 두 가지 프로토콜을 지원한다.

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
        max_recv_msg_size_mib: 4
        keepalive:
          server_parameters:
            max_connection_idle: 11s
            max_connection_age: 30s
      http:
        endpoint: 0.0.0.0:4318
        cors:
          allowed_origins:
            - 'https://*.example.com'

Jaeger Receiver

레거시 Jaeger 클라이언트와의 호환성을 위해 사용한다. Jaeger v2로의 마이그레이션 과도기에 유용하다.

receivers:
  jaeger:
    protocols:
      grpc:
        endpoint: 0.0.0.0:14250
      thrift_http:
        endpoint: 0.0.0.0:14268
      thrift_compact:
        endpoint: 0.0.0.0:6831

Prometheus Receiver

Prometheus 스크래핑 방식으로 메트릭을 수집한다. Kubernetes 서비스 디스커버리와 연동이 가능하다.

receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: 'otel-collector-internal'
          scrape_interval: 15s
          static_configs:
            - targets: ['0.0.0.0:8888']
        - job_name: 'kubernetes-pods'
          kubernetes_sd_configs:
            - role: pod
          relabel_configs:
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
              action: keep
              regex: true
            - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
              action: replace
              target_label: __address__
              regex: (.+)
              replacement: $$1

Processor 상세 설정

Memory Limiter Processor

Collector의 메모리 사용량을 제어하여 OOM 크래시를 방지하는 필수 프로세서이다. 모든 파이프라인에서 첫 번째 프로세서로 배치해야 한다.

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 1024 # 하드 리밋 (1GB)
    spike_limit_mib: 256 # 스파이크 허용치 (하드 리밋의 약 20%)

설정값 산정 가이드:

  • limit_mib: 컨테이너 메모리 제한의 약 80%로 설정한다. 컨테이너 메모리 제한이 2GB라면 limit_mib: 1638 정도가 적절하다.
  • spike_limit_mib: limit_mib의 약 20%로 설정한다. 소프트 리밋은 limit_mib - spike_limit_mib로 계산된다.
  • GOMEMLIMIT 환경변수: Collector 컨테이너의 GOMEMLIMIT을 하드 리밋의 80%(즉, 컨테이너 메모리 제한의 약 64%)로 설정하면 Go 런타임의 GC가 더 효율적으로 동작한다.

Batch Processor

텔레메트리 데이터를 배치로 묶어 네트워크 요청 횟수를 줄이고, 직렬화 CPU 오버헤드를 낮추는 프로세서이다.

processors:
  batch:
    send_batch_size: 8192 # 이 개수에 도달하면 즉시 전송
    timeout: 200ms # 크기에 상관없이 이 시간이 지나면 전송
    send_batch_max_size: 16384 # 배치 최대 크기 상한 (0이면 무제한)
  • send_batch_size는 트리거 역할이지 배치 크기 상한이 아니다. 실제 배치 크기를 제한하려면 send_batch_max_size를 반드시 설정해야 한다.
  • 메모리 압박이 있을 때는 send_batch_sizetimeout을 줄여 배치를 더 빠르게 플러시한다.

Filter Processor

불필요한 텔레메트리 데이터를 조기에 드롭하여 백엔드 비용을 절감한다.

processors:
  filter:
    error_mode: ignore
    traces:
      span:
        - 'attributes["http.route"] == "/healthz"'
        - 'attributes["http.route"] == "/readyz"'
        - 'name == "health_check"'
    metrics:
      metric:
        - 'name == "rpc.server.duration" and resource.attributes["service.name"] == "debug-svc"'

위 설정은 헬스체크 경로의 트레이스와 특정 서비스의 불필요한 메트릭을 필터링하여 백엔드로 전송하지 않는다. 고트래픽 환경에서 텔레메트리 비용을 크게 절감할 수 있다.

Attributes Processor

스팬이나 메트릭에 속성을 추가, 수정, 삭제하는 프로세서이다.

processors:
  attributes:
    actions:
      - key: environment
        value: production
        action: upsert
      - key: db.statement
        action: delete
      - key: http.request.header.authorization
        action: delete

민감정보(DB 쿼리, 인증 토큰 등)를 삭제하는 데 특히 유용하다.


다양한 백엔드 연동

Jaeger (OTLP)

Jaeger v2는 OTLP를 네이티브로 지원하므로 별도의 Jaeger Exporter가 아닌 OTLP Exporter를 사용한다.

exporters:
  otlp/jaeger:
    endpoint: jaeger-collector.monitoring.svc.cluster.local:4317
    tls:
      insecure: true
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 300s
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 5000

Grafana Tempo

Tempo 역시 OTLP를 네이티브로 지원한다. gRPC 또는 HTTP 프로토콜을 선택할 수 있다.

exporters:
  otlp/tempo:
    endpoint: tempo-distributor.monitoring.svc.cluster.local:4317
    tls:
      insecure: true
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 10000
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s

Datadog

Datadog Exporter는 collector-contrib에 포함되어 있으며, API Key를 통해 인증한다.

exporters:
  datadog:
    api:
      key: ${env:DD_API_KEY}
      site: datadoghq.com
    traces:
      span_name_as_resource_name: true
    metrics:
      histograms:
        mode: distributions
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 5000

멀티 백엔드 파이프라인 구성

하나의 Collector에서 여러 백엔드로 동시에 데이터를 전송하는 구성이다.

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, filter, attributes, batch]
      exporters: [otlp/tempo, datadog]
    metrics:
      receivers: [otlp, prometheus]
      processors: [memory_limiter, batch]
      exporters: [prometheusremotewrite]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]

각 Exporter는 데이터의 복사본을 받으므로, 하나의 Exporter 장애가 다른 Exporter에 영향을 주지 않는다. 단, 모든 Exporter가 동일한 데이터를 처리하므로 Receiver와 Processor 부하는 공유된다.


Kubernetes 환경 배포 전략

DaemonSet vs Deployment

Kubernetes 환경에서 Collector를 배포하는 두 가지 주요 패턴이 있다.

항목DaemonSet (Agent)Deployment (Gateway)
배포 단위모든 노드에 1개씩지정된 Replica 수만큼
역할로컬 텔레메트리 수집, 초기 가공중앙 집중 처리, 집계, 최종 전송
네트워크 부하노드 내 통신 (낮음)크로스 노드 통신 (높음)
Tail Sampling부적합 (트레이스 분산됨)적합 (모든 스팬 집중)
클러스터 메트릭수집 시 데이터 중복 위험중복 없이 수집 가능
장애 영향 범위해당 노드만전체 파이프라인
스케일링노드 추가 시 자동HPA로 수동/자동 스케일링

프로덕션 권장 패턴은 DaemonSet(Agent) + Deployment(Gateway) 계층 구조이다. Agent가 각 노드에서 로컬 수집과 1차 가공을 수행하고, Gateway가 집계, Tail Sampling, 최종 백엔드 전송을 담당한다.

DaemonSet Agent 매니페스트

# otel-agent-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector-agent
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: otel-collector-agent
  template:
    metadata:
      labels:
        app: otel-collector-agent
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:0.120.0
          args: ['--config=/etc/otelcol/config.yaml']
          env:
            - name: GOMEMLIMIT
              value: '400MiB'
            - name: K8S_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          ports:
            - containerPort: 4317 # OTLP gRPC
              hostPort: 4317
              protocol: TCP
            - containerPort: 4318 # OTLP HTTP
              hostPort: 4318
              protocol: TCP
            - containerPort: 13133 # Health Check
              protocol: TCP
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          livenessProbe:
            httpGet:
              path: /
              port: 13133
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /
              port: 13133
            initialDelaySeconds: 5
            periodSeconds: 5
          volumeMounts:
            - name: config
              mountPath: /etc/otelcol
      volumes:
        - name: config
          configMap:
            name: otel-agent-config

Gateway Deployment 매니페스트

# otel-gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector-gateway
  namespace: monitoring
spec:
  replicas: 3
  selector:
    matchLabels:
      app: otel-collector-gateway
  template:
    metadata:
      labels:
        app: otel-collector-gateway
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:0.120.0
          args: ['--config=/etc/otelcol/config.yaml']
          env:
            - name: GOMEMLIMIT
              value: '3200MiB'
          ports:
            - containerPort: 4317
              protocol: TCP
            - containerPort: 13133
              protocol: TCP
          resources:
            requests:
              cpu: 1000m
              memory: 2Gi
            limits:
              cpu: 2000m
              memory: 4Gi
          livenessProbe:
            httpGet:
              path: /
              port: 13133
          readinessProbe:
            httpGet:
              path: /
              port: 13133
          volumeMounts:
            - name: config
              mountPath: /etc/otelcol
      volumes:
        - name: config
          configMap:
            name: otel-gateway-config
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector-gateway
  namespace: monitoring
spec:
  selector:
    app: otel-collector-gateway
  ports:
    - name: otlp-grpc
      port: 4317
      targetPort: 4317
    - name: otlp-http
      port: 4318
      targetPort: 4318
  type: ClusterIP

Agent 설정에서는 Gateway Service를 Exporter 엔드포인트로 지정한다.

# Agent의 exporter 설정
exporters:
  otlp/gateway:
    endpoint: otel-collector-gateway.monitoring.svc.cluster.local:4317
    tls:
      insecure: true
    sending_queue:
      enabled: true
      queue_size: 2000
    retry_on_failure:
      enabled: true

Gateway 접속 시 반드시 Kubernetes Service를 통해 연결해야 한다. Pod IP 직접 참조는 Pod 재시작 시 연결이 끊어지며, 로드 밸런싱도 수행되지 않는다.


샘플링 전략

Head-based Sampling

SDK 수준에서 트레이스 시작 시점에 샘플링 결정을 내리는 방식이다.

장점: 구현이 단순하고, 리소스 절약이 즉각적이다.

단점: 트레이스 전체를 보지 않고 결정하므로, 에러가 포함된 트레이스를 놓칠 수 있다.

# SDK 설정에서 TraceIdRatioBased Sampler 사용
# 10%의 트레이스만 샘플링
processors:
  probabilistic_sampler:
    sampling_percentage: 10

Tail-based Sampling

트레이스의 모든(또는 대부분의) 스팬이 도착한 후에 샘플링 결정을 내리는 방식이다. Collector의 Tail Sampling Processor에서 수행한다.

장점: 에러, 고지연 등 특정 조건의 트레이스를 100% 보존할 수 있다.

단점: 트레이스의 모든 스팬이 한 곳에 모여야 하므로 Gateway Collector에서만 사용해야 하며, 메모리 소비가 크다.

processors:
  tail_sampling:
    decision_wait: 30s
    num_traces: 100000
    expected_new_traces_per_sec: 1000
    policies:
      # 정책 1: 에러가 포함된 트레이스는 100% 샘플링
      - name: errors-policy
        type: status_code
        status_code:
          status_codes:
            - ERROR
      # 정책 2: 지연이 500ms를 초과하는 트레이스는 100% 샘플링
      - name: latency-policy
        type: latency
        latency:
          threshold_ms: 500
      # 정책 3: 나머지 트레이스는 5%만 샘플링
      - name: probabilistic-policy
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

Tail Sampling 운영 주의사항:

  • DaemonSet Agent에서는 절대 사용하지 않는다. 트레이스의 스팬들이 여러 노드에 분산되어 도착하므로, 각 Agent가 트레이스의 일부분만 보게 되어 올바른 샘플링 결정을 내릴 수 없다.
  • decision_wait는 트레이스의 모든 스팬이 도착할 시간적 여유를 의미한다. 너무 짧으면 불완전한 트레이스 기반으로 결정하고, 너무 길면 메모리 소비가 급증한다.
  • num_traces는 동시에 메모리에 유지하는 트레이스 수의 상한이다. 이를 초과하면 가장 오래된 트레이스가 강제로 결정된다.

권장 샘플링 전략 조합

환경권장 전략설명
트래픽이 적은 서비스전수 수집 (100%)모든 트레이스를 보존하여 디버깅 용이성 확보
일반 프로덕션Head 10% + Tail 에러/지연비용 절감과 중요 트레이스 보존의 균형
고트래픽 서비스Head 1% + Tail 에러/지연대량 트래픽에서 비용 최적화
규제/감사 대상전수 수집 + 장기 저장컴플라이언스 요건 충족

성능 튜닝과 고가용성 구성

핵심 성능 파라미터

# 성능 최적화 설정 예시
processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 1638 # 컨테이너 메모리 2GB의 ~80%
    spike_limit_mib: 328 # limit_mib의 ~20%
  batch:
    send_batch_size: 10000
    timeout: 500ms
    send_batch_max_size: 20000

exporters:
  otlp/backend:
    endpoint: backend:4317
    sending_queue:
      enabled: true
      num_consumers: 20 # 병렬 전송 워커 수
      queue_size: 10000 # 큐 크기 (배치 단위)
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 300s
    timeout: 30s

GOMEMLIMIT 설정

Go 런타임의 GC 효율을 높이기 위해 GOMEMLIMIT 환경변수를 설정한다.

# 컨테이너 메모리 제한이 2GB일 때
# GOMEMLIMIT = limit_mib * 0.8 = 1638 * 0.8 ~ 1310MiB
export GOMEMLIMIT=1310MiB
컨테이너 메모리limit_mibspike_limit_mibGOMEMLIMIT
512Mi41082328MiB
1Gi820164656MiB
2Gi16383281310MiB
4Gi32766552621MiB

고가용성(HA) 구성

Gateway Collector의 고가용성을 위한 핵심 전략은 다음과 같다.

  1. 최소 3개 Replica: Deployment의 replicas를 최소 3으로 설정한다.
  2. Pod Anti-Affinity: 동일 노드에 Gateway Pod가 집중되지 않도록 설정한다.
  3. PDB(PodDisruptionBudget): 동시에 종료되는 Pod 수를 제한한다.
  4. HPA: CPU/메모리 기반 자동 스케일링을 구성한다.
# PodDisruptionBudget 설정
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: otel-gateway-pdb
  namespace: monitoring
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: otel-collector-gateway

트러블슈팅 가이드

일반적인 문제와 해결 방법

1. 데이터가 백엔드에 도달하지 않는 경우

# 1단계: Collector 로그 확인
kubectl logs -n monitoring deploy/otel-collector-gateway --tail=100

# 2단계: zPages에서 파이프라인 상태 확인
kubectl port-forward -n monitoring svc/otel-collector-gateway 55679:55679
# 브라우저에서 http://localhost:55679/debug/tracez 접속

# 3단계: debug exporter를 임시 추가하여 데이터 흐름 확인
# exporters에 debug: { verbosity: detailed } 추가 후
# pipeline에 debug exporter 추가

2. Collector 메모리 사용량 급증

  • memory_limiter가 설정되어 있는지 확인한다.
  • GOMEMLIMIT이 적절히 설정되어 있는지 확인한다.
  • Tail Sampling의 num_traces 값이 너무 크지 않은지 확인한다.
  • Exporter의 sending_queue.queue_size가 과도하지 않은지 확인한다.

3. Exporter 큐 포화

# Collector 내부 메트릭으로 큐 상태 확인
curl http://localhost:8888/metrics | grep otelcol_exporter_queue
# otelcol_exporter_queue_size: 현재 큐 크기
# otelcol_exporter_queue_capacity: 큐 최대 용량
# queue_size가 capacity에 근접하면 큐 포화 상태

원인: 백엔드가 데이터를 충분히 빠르게 처리하지 못하거나, Collector에 과도한 트래픽이 유입됨.

해결: num_consumers를 늘리거나, 백엔드의 처리 용량을 확인하거나, 필터 프로세서로 불필요한 데이터를 조기 드롭한다.

4. 프로세서 순서 오류

잘못된 프로세서 순서는 미묘한 문제를 야기한다.

실수증상올바른 순서
batch를 memory_limiter 앞에 배치메모리 급증 후 OOMmemory_limiter를 항상 첫 번째로
attributes를 batch 뒤에 배치속성 변경이 적용되지 않음attributes는 batch 앞에
tail_sampling을 DaemonSet에 배치불완전한 샘플링 결정Gateway에서만 tail_sampling 사용
resourcedetection을 마지막에 배치리소스 속성 누락resourcedetection을 앞쪽에 배치

핵심 모니터링 메트릭

Collector 자체의 운영 상태를 모니터링하기 위해 다음 내부 메트릭을 추적해야 한다.

메트릭설명경보 기준
otelcol_receiver_accepted_spans수신 성공 스팬 수급격한 감소 시 경보
otelcol_receiver_refused_spans수신 거부 스팬 수0이 아닌 값이 지속되면 경보
otelcol_exporter_sent_spans전송 성공 스팬 수accepted 대비 크게 차이나면 경보
otelcol_exporter_send_failed_spans전송 실패 스팬 수0이 아닌 값이 지속되면 경보
otelcol_exporter_queue_size현재 큐 크기capacity의 80% 초과 시 경보
otelcol_processor_dropped_spans프로세서에서 드롭된 스팬예상치 초과 시 경보

실패 사례와 복구 절차

사례 1: Gateway 전체 장애 (전체 Replica 다운)

증상: 모든 텔레메트리 데이터가 유실됨. Agent의 Exporter 큐가 포화되어 데이터 드롭 시작.

원인: 잘못된 설정 변경 배포, OOM 연쇄 발생, Node 장애 등.

복구 절차:

  1. 즉시 이전 정상 ConfigMap으로 롤백한다.
  2. Gateway Deployment의 replicas가 충분한지 확인한다.
  3. Pod Anti-Affinity가 설정되어 있는지 검증한다.
  4. Agent 측의 sending_queue와 retry_on_failure 설정을 확인한다. 큐에 쌓인 데이터는 Gateway 복구 후 자동으로 재전송된다.
  5. 재발 방지를 위해 PDB를 설정하고, ConfigMap 변경 시 Canary 배포를 적용한다.
# ConfigMap 롤백
kubectl rollout undo configmap/otel-gateway-config -n monitoring
# 또는 이전 버전의 ConfigMap을 직접 적용
kubectl apply -f otel-gateway-config-backup.yaml

# Gateway Deployment 재시작
kubectl rollout restart deployment/otel-collector-gateway -n monitoring

# 복구 상태 확인
kubectl rollout status deployment/otel-collector-gateway -n monitoring

사례 2: 백엔드 장애로 인한 데이터 유실

증상: Exporter 큐 포화, otelcol_exporter_send_failed_spans 급증.

원인: 백엔드(Tempo, Jaeger 등)가 다운되거나 응답이 느려짐.

복구 절차:

  1. 백엔드의 상태를 먼저 확인하고 복구한다.
  2. Collector는 retry_on_failure 설정에 의해 자동으로 재시도한다.
  3. 큐 포화로 드롭된 데이터는 복구 불가능하므로, 장기적으로 큐 크기를 늘리거나 Kafka 등의 버퍼 계층을 도입한다.

사례 3: memory_limiter 미설정으로 인한 OOM

증상: Collector Pod가 OOMKilled 상태로 반복 재시작됨.

원인: memory_limiter 프로세서 미설정 또는 부적절한 값 설정.

복구 절차:

  1. memory_limiter를 모든 파이프라인의 첫 번째 프로세서로 추가한다.
  2. limit_mib를 컨테이너 메모리 제한의 80%로 설정한다.
  3. GOMEMLIMIT 환경변수를 설정한다.
  4. 컨테이너 리소스 제한이 워크로드에 비해 너무 작지 않은지 검토한다.

운영 체크리스트

초기 배포 체크리스트

  • memory_limiter 프로세서가 모든 파이프라인의 첫 번째 프로세서로 설정되어 있는가
  • GOMEMLIMIT 환경변수가 설정되어 있는가
  • health_check Extension이 활성화되어 있는가
  • livenessProbe와 readinessProbe가 설정되어 있는가
  • Exporter에 sending_queue와 retry_on_failure가 활성화되어 있는가
  • 컨테이너 리소스 requests/limits가 적절히 설정되어 있는가
  • PodDisruptionBudget이 설정되어 있는가

프로세서 순서 체크리스트

  • memory_limiter가 첫 번째 프로세서인가
  • resourcedetection이 memory_limiter 다음에 위치하는가 (사용 시)
  • filter/attributes 등 데이터 가공 프로세서가 batch 앞에 위치하는가
  • tail_sampling이 Gateway에서만 사용되고 있는가 (사용 시)
  • batch가 마지막 프로세서인가

모니터링 체크리스트

  • Collector 내부 메트릭(8888 포트)을 Prometheus로 수집하고 있는가
  • Exporter 큐 포화 경보가 설정되어 있는가
  • 수신 거부(refused) 메트릭 경보가 설정되어 있는가
  • 전송 실패(send_failed) 메트릭 경보가 설정되어 있는가
  • Collector Pod의 메모리/CPU 사용량 경보가 설정되어 있는가

보안 체크리스트

  • 불필요한 포트가 외부에 노출되지 않았는가
  • TLS가 필요한 통신 경로에 적용되어 있는가
  • API Key 등 민감정보가 환경변수 또는 Secret으로 관리되고 있는가
  • attributes 프로세서로 민감 속성(DB 쿼리, 인증 토큰)을 삭제하고 있는가

마치며

OpenTelemetry Collector는 현대적인 관측 가능성 파이프라인의 핵심 인프라이다. 벤더 중립적인 설계 덕분에 Jaeger, Grafana Tempo, Datadog 등 어떤 백엔드든 유연하게 연동할 수 있으며, 백엔드 교체 시에도 애플리케이션 코드를 수정할 필요가 없다.

안정적인 운영을 위해 가장 중요한 세 가지를 요약하면 다음과 같다.

  1. memory_limiter를 반드시 첫 번째 프로세서로 설정하고 GOMEMLIMIT을 적절히 구성한다.
  2. DaemonSet(Agent) + Deployment(Gateway) 계층 구조로 배포하여 로컬 수집과 중앙 집중 처리를 분리한다.
  3. Exporter의 sending_queue와 retry_on_failure를 활성화하여 일시적인 백엔드 장애에 대한 복원력을 확보한다.

Collector는 지속적으로 발전하고 있으며, OpAMP(Open Agent Management Protocol)를 통한 원격 설정 관리 등 새로운 기능이 계속 추가되고 있다. 공식 문서와 릴리스 노트를 주기적으로 확인하며 최신 변경사항을 반영하는 것이 바람직하다.


참고 자료