LabHub

OTCA — 오픈텔레메트리 인증 어소시에이트 · 컬렉터 파이프라인 · 이론

memory_limiter 는 왜 맨 앞이고 batch 는 왜 맨 뒤인가

LabHub 에서 이어서 보기

한 줄 요약

컬렉터 설정에서 파이프라인에 나열한 프로세서의 순서가 곧 신호에 적용되는 처리 순서입니다. 짧은 문장이지만 운영에서 만드는 차이는 큽니다. memory_limiter 는 맨 앞, batch 는 맨 뒤, tail_samplingbatch 앞, 민감 정보 삭제는 메타데이터를 붙이는 프로세서 입니다.

왜 이게 필요했나

SDK 가 백엔드로 직접 보내도 동작은 합니다. 그런데도 컬렉터를 두는 이유는 다섯 가지입니다.

1. 정책을 재배포 없이 바꿉니다. 샘플링 비율, 속성 필터, 보존 대상은 운영 중에 조정하게 되는 값입니다. 그것이 앱 환경변수에 있으면 20개 서비스를 롤아웃해야 합니다.
2. 앱을 백엔드 장애로부터 격리합니다. 백엔드가 느려지면 SDK 의 전송 큐가 차고 앱 메모리가 올라갑니다. 컬렉터가 앞에 있으면 그 압력을 대신 받습니다.
3. 백엔드를 바꿀 수 있습니다. 신호별로 다른 목적지를 쓰거나 두 백엔드를 병행 운영하는 일이 설정 파일 한 곳에서 끝납니다.
4. 민감 정보를 앱 밖에서 지웁니다. 토큰이나 이메일이 속성에 섞이는 사고는 반드시 일어납니다. 컬렉터에 방어선을 두면 사고 대응이 재배포가 아니라 설정 변경이 됩니다.
5. 테일 샘플링을 할 수 있습니다. 트레이스 결과를 보고 판단하려면 스팬이 한곳에 모여야 하고, 그 장소는 앱이 될 수 없습니다.

어떻게 동작하나

설정의 최상위 섹션은 여섯 개입니다. receivers, processors, exporters, connectors, extensions, service. 중요한 것은 앞의 다섯 곳에 컴포넌트를 정의만 하면 활성화되지 않는다는 점입니다. service.pipelines(그리고 확장은 service.extensions)에서 참조해야 실제로 돕니다. 설정을 다 썼는데 아무 일도 일어나지 않는 사고의 절반이 여기서 나옵니다.

connectors 는 한 파이프라인의 출력을 다른 파이프라인의 입력으로 잇는 컴포넌트입니다. 트레이스에서 RED 메트릭을 뽑아내는 spanmetrics 가 대표적입니다. 익스포터이면서 동시에 리시버인 셈이라 별도 섹션을 갖습니다.

순서가 만드는 차이를 하나씩 봅니다.

memory_limiter 값 산정에는 관례가 있습니다.

| 컨테이너 메모리 | limit_mib | spike_limit_mib | GOMEMLIMIT |
| --- | --- | --- | --- |
| 1Gi | 820 | 164 | 656MiB |
| 2Gi | 1638 | 328 | 1310MiB |
| 4Gi | 3276 | 655 | 2621MiB |

limit_mib 은 컨테이너 한도의 약 80%, spike_limit_mib 은 그 20% 입니다. 컨테이너 한도보다 limit_mib 이 높으면 프로세서가 개입하기 전에 커널이 먼저 프로세스를 죽입니다.

batch 에서 자주 틀리는 것send_batch_size 를 상한으로 착각하는 것입니다. 이 값은 "이만큼 모이면 즉시 보내라"는 트리거이고, 실제 배치 크기의 상한은 send_batch_max_size 입니다. 후자를 지정하지 않으면 배치가 예상보다 훨씬 커질 수 있습니다.

현장에서 만나는 모습

증상은 세 가지로 갈립니다.

데이터가 아예 안 들어온다. 임시로 debug 익스포터를 붙여 리시버까지 도달했는지 먼저 봅니다. 수신 지표가 0 이면 앱이 아직 아무것도 안 보냈거나 주소를 잘못 본 것이고, 이때 가장 흔한 원인이 gRPC 와 HTTP 포트를 바꿔 적은 경우입니다.

들어오긴 하는데 백엔드에 없다. 전송 실패 지표를 먼저 보고, 실패가 없는데도 사라진다면 큐 지표와 거절 지표를 봅니다. 여기서 tail_sampling 이 원인인 경우가 꽤 많습니다. 정책이 의도보다 공격적이면 데이터는 정상 전송됐는데 백엔드에 특정 트레이스만 없는 모양이 되고, 지표는 아무 이상도 보고하지 않습니다. 파이프라인에서 잠시 빼고 재현되는지 보는 것이 가장 빠른 구분법입니다.

주기적으로 죽는다. 대부분 메모리 압박입니다. memory_limiter 가 이미 있는데도 죽는다면 한도가 컨테이너 제한과 맞지 않는 경우를 의심합니다.

읽어야 할 자체 지표는 세 단계로 나뉩니다. 수신은 otelcol_receiver_accepted_spansotelcol_receiver_refused_spans 쌍, 처리는 otelcol_processor_incoming_itemsoutgoing_items 비교, 전송은 otelcol_exporter_sent_spans, send_failed_spans, enqueue_failed_spans, 그리고 queue_sizequeue_capacity 의 비율입니다. 알림을 하나만 건다면 큐 지표를 고릅니다. 큐가 용량에 붙어 있는 상태가 지속되면 그다음에 오는 것은 거의 항상 거절과 유실입니다.

다음 실습에서 할 것

/root/otca-collector/config.yaml 하나를 처음부터 끝까지 씁니다. OTLP 리시버 두 포트, 컨테이너 한도의 80% 로 잡은 memory_limiter, 쿠버네티스 메타데이터 부착, 민감 속성 삭제와 해싱, 트리거와 상한을 모두 지정한 batch, 큐와 재시도를 켠 익스포터, health_check 와 zpages 확장, 그리고 프로세서 순서를 지킨 세 개의 파이프라인까지.