~/infra-dev.md
INFRA_DEV

관측 파이프라인도 장애가 난다: OpenTelemetry Collector를 운영 대상으로 보기

10분 읽기·2026.06.11·출처 5·00
오늘의 질문

장애를 보기 위해 둔 관측 파이프라인이 조용히 막히거나 죽으면, 우리는 무엇을 먼저 감시해야 할까?

infra-dev.md
SURVIVE.exe

관측 파이프라인도 장애가 난다: OpenTelemetry Collector를 운영 대상으로 보기

1. 왜 지금 봐야 하나

서비스 장애를 잡으려고 로그, 메트릭, 트레이스를 붙였는데 정작 수집기가 조용히 막히면 가장 위험한 상태가 된다. 애플리케이션은 실패하고 있는데 대시보드는 평온해 보이고, 알림은 늦게 오거나 아예 오지 않는다. 그래서 관측성은 “데이터를 많이 보내기”가 아니라 “관측 파이프라인 자체가 실패할 때 알아차리기”까지 포함해야 한다.

2026년 6월 초 OpenTelemetry 릴리스들을 보면 이 방향이 분명하다. Collector v0.154.0은 sending_queue::batch 설정 조합에서 Collector가 시작 시 panic 날 수 있던 버그를 고쳤다. Contrib v0.154.0은 로그를 service.name, 전체 resource, 특정 attributes 기준으로 load-balancing 라우팅할 수 있게 했고, 일부 processor에는 변환·드롭·추적 스트림 수를 나타내는 내부 telemetry가 추가됐다. Go SDK v1.44.0은 metric cardinality 기본 제한 2000과 OTLP request size 기본 64MiB 제한을 도입했다. Java SDK v1.63.0은 OTLP sender dispatcher를 bounded로 만들고 rejection을 표면화했다.

이 변화들은 모두 같은 질문으로 모인다. “관측 데이터가 많아질 때 어디서 줄이고, 어디서 나누고, 어디서 실패를 드러낼 것인가?”

2. 핵심 개념

관측 파이프라인은 보통 네 단계로 흐른다.

  1. 애플리케이션 SDK가 span, metric, log를 만든다.
  2. Collector가 OTLP 등으로 데이터를 받는다.
  3. processor가 batch, attribute 수정, sampling, temporality 변환 같은 처리를 한다.
  4. exporter가 백엔드로 보낸다.

이 구조에서 실패 모드는 웹 API와 다르다. 요청 하나가 500으로 끝나는 대신, 데이터가 큐에 쌓이고, batch가 커지고, label/cardinality가 폭발하고, backend rate limit에 걸리고, 일부 signal만 유실된다. 그래서 관측 파이프라인의 핵심 운영 개념은 세 가지다.

  • 큐와 backpressure: exporter가 늦어질 때 수집기가 어디까지 버틸지 정한다.
  • 라우팅과 ordering: 같은 서비스나 같은 resource의 로그·트레이스가 같은 downstream으로 가야 stateful 처리와 tail sampling이 안정적이다.
  • 카디널리티와 크기 제한: user_id, request_id, prompt, exception.stacktrace 같은 값이 metric label이나 attribute에 들어가면 비용과 메모리가 폭발한다.

즉 Collector는 단순 sidecar가 아니라 작은 스트리밍 시스템이다. 큐, 라우터, 제한값, 자체 지표가 모두 필요하다.

3. 최신 이슈와 연결

Collector v0.154.0의 버그 수정은 작아 보이지만 운영 관점에서는 중요하다. 특정 설정에서 sending_queue::sizer를 쓰고 sending_queue::batch::enabled: false인 경우 nil pointer panic으로 Collector가 시작 시 죽을 수 있었다. 장애를 보기 위한 Collector가 설정 조합 하나로 부팅하지 못하면, 배포 직후부터 blind spot이 생긴다. 따라서 Collector config 변경은 애플리케이션 config보다 덜 위험한 작업이 아니다.

Contrib v0.154.0의 load-balancing exporter 로그 라우팅도 같은 맥락이다. 로그를 무작위로 분산하면 단순 저장에는 문제가 없어 보일 수 있다. 하지만 log reduction, throttling, tail-based sampling처럼 이전 이벤트 상태를 기억해야 하는 처리는 같은 service/resource/attribute가 같은 backend shard로 가야 안정적이다. 릴리스 노트는 로그를 service, resource, attributes 기준으로 라우팅할 수 있다고 설명한다. 이것은 “로그도 stateful 처리 대상”이라는 뜻이다.

Go SDK v1.44.0의 cardinality limit 2000은 비용 제어의 신호다. 이전에는 사실상 무제한이라고 생각했던 metric attribute 조합이 기본적으로 제한된다. 새 attribute set은 drop되고 otel.metric.overflow=true가 붙은 특수 attribute set으로 집계될 수 있다. Java SDK v1.63.0의 bounded dispatcher와 rejection 표면화도 같은 방향이다. 큐가 무한히 커지는 대신, 거절을 보이게 만들어 운영자가 알 수 있게 한다.

4. 개발자 관점 해석

개발자는 “관측성 도구를 붙였다”에서 멈추면 안 된다. 붙인 뒤에는 네 가지 질문을 해야 한다.

첫째, 이 파이프라인은 실패를 어떻게 알리나? Collector process 재시작, exporter queue length, dropped spans/logs/metrics, retry count, backend 429/5xx를 별도 alert로 봐야 한다. 애플리케이션 대시보드만 보면 Collector 장애를 놓친다.

둘째, 카디널리티 폭발을 어디서 막나? metric label에 user_id, session_id, trace_id, 원문 URL 전체를 넣으면 비용이 늘고 backend query가 느려진다. Go SDK의 기본 제한은 안전장치지만, 제한에 기대면 데이터가 의도와 다르게 합쳐진다. 더 좋은 방식은 attribute allowlist와 view를 먼저 정하는 것이다.

셋째, 로그 라우팅 기준은 제품 디버깅 방식과 맞나? 결제 서비스의 에러 로그를 추적할 때 service.name 기준이면 충분할 수 있다. 멀티테넌트 SaaS에서 특정 tenant별 throttling을 해야 한다면 attribute 기준 라우팅이 필요할 수 있다. 하지만 tenant ID를 그대로 routing key로 쓰면 shard 수와 개인정보 노출 문제가 생긴다.

넷째, 보안 기본값을 쉽게 풀지 않았나? Collector v0.154.0은 insecure cipher suite를 명시적으로 포함하는 옵션을 추가했지만, 기본은 비활성이다. 운영에서는 “연동이 안 되니 보안을 낮추자”가 아니라, 예외를 썼다면 만료일과 제거 계획을 남겨야 한다.

5. 내 프로젝트에 적용할 체크포인트

  • Collector 설정 변경은 otelcol validate --config=... 같은 검증 단계를 CI에 넣는다.
  • Collector 자체의 health check, pprof, zpages, telemetry metrics 노출 범위를 정한다. 공개 네트워크에 열지 않는다.
  • exporter별 sending_queue, retry, batch 크기, timeout을 문서화한다.
  • backend 장애나 rate limit 때 “얼마나 버티고, 언제 버리고, 무엇을 알릴지”를 정한다.
  • metric label allowlist를 만든다. 사용자·세션·요청 단위 고유값은 기본 금지한다.
  • 로그 attribute에는 개인정보와 secret이 들어가지 않게 redaction 위치를 정한다.
  • load-balancing exporter를 쓴다면 routing key를 service, resource, attributes 중 하나로 명시하고, stateful 처리와 일치하는지 확인한다.
  • SDK 업그레이드 때 cardinality limit, request size limit, exporter rejection 같은 동작 변경을 릴리스 노트에서 확인한다.
  • 관측 데이터 비용을 “스토리지 비용”만 보지 말고 ingest, index, query, retention, alert cardinality까지 나눠 본다.

6. 오늘 10분 액션

10분만 투자해서 우리 서비스의 관측 파이프라인 failure map을 그려보자.

  1. 애플리케이션에서 관측 backend까지 경로를 한 줄로 쓴다. 예: app SDK -> local collector -> gateway collector -> vendor OTLP endpoint.
  2. 각 구간 옆에 실패 신호를 적는다. 예: queue length, dropped count, retry count, 429, process restart.
  3. 지금 알림이 있는 항목에 체크한다.
  4. 체크가 하나도 없는 구간을 오늘의 blind spot으로 표시한다.
  5. 가장 쉬운 하나를 추가한다. 예: Collector process down alert, exporter send failure count, backend ingest 429 alert.

이 액션의 목적은 완벽한 관측성 설계가 아니다. 장애를 보기 위한 시스템이 먼저 장애를 숨기지 않게 만드는 것이다.

7. 더 볼 자료

  • OpenTelemetry Collector 공식 설정 문서: receiver, processor, exporter, connector, service pipeline 구조를 먼저 이해하기 좋다.
  • OpenTelemetry Collector v0.154.0 릴리스: sending_queue 관련 startup panic 수정과 TLS 설정 변화를 확인한다.
  • OpenTelemetry Collector Contrib v0.154.0 릴리스: logs load-balancing routing key, SignalFx trace deprecation, Prometheus exporter suffix 변경 등 운영 영향 항목을 확인한다.
  • OpenTelemetry Go v1.44.0 릴리스: metric cardinality 기본 제한과 OTLP request size 제한을 확인한다.
  • OpenTelemetry Java v1.63.0 릴리스: OTLP sender dispatcher bounding과 deprecated 설정 제거를 확인한다.

중복 회피 메모

최근 infra_dev 글은 Kubernetes Job suspend와 리소스 재조정, OIDC·signed URL 기반 배포 secret 축소, Cloud Run worker pools와 Pub/Sub/Kafka backpressure를 다뤘다. 이번 글은 애플리케이션 작업 큐나 배포 보안이 아니라 OpenTelemetry Collector/SDK 릴리스를 바탕으로 관측 파이프라인 자체의 큐, 라우팅, 카디널리티, 자체 지표, 장애 감지를 운영 대상으로 해석한다.

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)