관측 신호는 Collector 없이 앱이 OTLP로 직접 push 한다
- 상태: 채택됨
- 작성일: 2026-07-28
- 관련 문서: ADR-0009, APP-248, APP-254, APP-255, APP-405
맥락
Grafana Cloud로 로그·메트릭·트레이스를 보내려면 수집 계층이 필요하다. 표준 구성은 Collector(Grafana Alloy)를 호스트에 두고 앱이 로컬로 보내는 것이다. 그런데 서비스가 둘뿐이고 EC2도 두 대다. 이 규모에서 수집 계층을 하나 더 운영할 만한지가 갈림길이었다.
결정
- Collector를 두지 않고 두 앱이 OTLP 게이트웨이로 직접 push 한다.
- 프로토콜은 http/protobuf다. 게이트웨이가 gRPC를 받지 않는다.
- 인증 헤더는
base64("인스턴스ID:glc_토큰")—glc_토큰을 그대로 넣으면 401이다. - 히스토그램은
base2_exponential_bucket_histogram으로 고정한다. 기본 explicit bucket은 (라우트 × 메서드 × 코드)마다 18 series를 만들어 Free 한도(10K active series)를 태운다. Java는 대문자, Python은 소문자여야 한다. - Alloy를 다시 볼 기준은 숫자로 정해 둔다: 파일에만 로그가 있는 상황 2회 / egress 월 $20 초과 / 서비스 3개 이상 또는 k8s / 호스트 메모리·디스크 장애 / 에러 트레이스 보존이 병목.
대안
대안 1 — Grafana Alloy(Collector) 도입. 표준 구성이고 tail sampling과 호스트 메트릭을 얻는다. 전송 설정도 한 곳에 모인다. 기각한 이유: 서비스 2개·EC2 2대 규모에서는 운영할 것을 하나 더 늘릴 만큼의 값이 안 보였다. 대신 언제 다시 볼지를 조건으로 남겼다.
결과
- 운영할 컴포넌트는 그대로다. 다시 볼 기준을 숫자로 남겨 뒀으니 재검토는 관측이 열어 준다. 누가 판단해서 여는 자리가 아니다.
- 로그 유실 가능성, tail sampling 불가(= "에러만 100% 보존"을 구현할 수 없다), 호스트 메모리·디스크 메트릭 공백, 크레덴셜 분산, EC2별 egress 개별 부담을 함께 받았다.
- 전송 설정이 앱마다 흩어졌다. Java와 Python이 서로 다른 표기(대문자/소문자)를 요구하고 게이트웨이 인증·프로토콜·히스토그램 종류를 두 앱이 각자 맞게 들고 있어야 한다.
- 전송이 앱 프로세스 안에 있다. 전송이 죽어도 앱은 멀쩡히 돌고 그것을 알려 줄 중간 계층이 없다.
실제로 두 번 깨졌다. 메트릭 temporality를
delta로 두자 배치 하나가 400이 되면서 그 안의 HTTP 메트릭까지 통째로 버려졌다.OTEL_INSTRUMENTATION_MICROMETER_ENABLED기본값이 false라 손으로 만든 계측 15종은 열흘간 한 건도 도달하지 않았다(APP-405). GRAFANA_CLOUD_OTLP_TOKEN은docker restart로 안 바뀐다. 이 값을 읽는 것은 앱이 아니다.deploy.sh가 읽어 컨테이너 환경변수로 넣고opentelemetry-instrument는 프로세스 시작 시점에 그것을 읽는다. 그래서 OTLP가 401로 계속 실패하는데 앱은 멀쩡히 도는 모양이 된다. 관측이 죽은 것을 관측할 방법이 없다.