Tempo·Grafana로 분산 트레이싱 관찰 가능성 강화
목차
관찰 가능성 스택을 운영하다 보면 어느 시점에 메트릭만으로는 부족하다는 걸 체감하게 된다. Prometheus로 JVM 힙을 보고, Grafana 대시보드에서 레이턴시 p99도 확인하고 있는데, 막상 특정 요청이 왜 느린지 파고들면 어디서 시간이 빠지는지 알기 어렵다. 메트릭은 집계값이라 "평균적으로 느리다"는 건 보이지만, "이 트랜잭션이 어느 서비스에서 얼마나 걸렸는가"는 트레이스 없이는 답이 안 나온다.
그래서 이번에 Grafana Tempo를 관찰 가능성 스택에 붙였다. 큰 공사는 아니었는데, 설정 파일 파싱 문제 하나가 예상 외로 시간을 잡아먹어서 기록해둔다.
기존 구조와 Tempo를 끼워 넣은 맥락
기본 구조는 이미 잡혀 있었다. Spring Boot Actuator가 /actuator/prometheus 엔드포인트를 열어두고, Prometheus가 스크레이프 주기에 맞춰 긁어가고, Grafana가 그걸 시각화하는 흐름. Actuator 설정은 아래처럼 최소한으로 노출만 열어둔 상태였다.
management:
endpoints:
web:
exposure:
include: health,prometheus
metrics:
export:
prometheus:
enabled: true
여기에 분산 트레이싱을 붙이려면 애플리케이션이 스팬 데이터를 내보낼 수 있어야 하고, 그걸 받아서 저장할 백엔드가 필요하다. Jaeger나 Zipkin도 선택지였지만 Grafana 생태계에 이미 들어와 있었기 때문에 Tempo가 자연스러운 선택이었다. Loki, Prometheus, Tempo를 Grafana 하나에서 묶으면 로그-메트릭-트레이스를 같은 타임라인 위에서 상호 참조할 수 있다는 게 실용적인 이유다. 툴체인을 흩어놓으면 결국 장애 대응할 때 탭 전환하면서 컨텍스트를 손으로 이어 붙이게 되는데, 그 비용이 생각보다 크다.
이번에 건드린 작업 범위는 세 가지였다.
- Prometheus 스크레이프 타겟 설정 수정, 새로 추가된 서비스 인스턴스 반영
- Grafana datasource에 Tempo 추가, 기존 대시보드와 연결
- 애플리케이션의 Zipkin/Tempo 연동 엔드포인트 주소 조정
설정 파싱 삽질과 보안 처리
Actuator 엔드포인트는 외부에 노출되면 안 된다. 메트릭 데이터 자체가 민감하다기보다는 내부 구조가 드러나는 게 부담스럽고, 요청이 외부에서 자유롭게 들어오는 게 운영상 바람직하지 않아서 IP 기반 허용 목록 필터를 별도로 달아뒀다. 허용 범위를 설정 파일에서 읽어오는 방식인데, YAML에서 IP 목록을 쉼표로 구분해 문자열로 넣을 때 공백 파싱이 제대로 안 되는 문제가 생겼다.
예를 들어 이런 값을 쓰면:
monitoring:
allowed-ips: "127.0.0.1, 10.0.0.1, 10.0.0.2"
파싱 후 비교할 때 " 10.0.0.1"처럼 앞에 공백이 붙어서 매칭이 안 됐다. 처리 로직에서 split 이후 각 토큰에 trim()을 빠뜨린 거였다. 단순한 실수지만, 설정 파일에서 문자열 목록을 가져와 비교할 때는 공백 정규화를 기본으로 넣는 습관이 필요하다는 걸 다시 확인했다. 특히 운영 환경에서 IP 필터가 조용히 뚫리거나 반대로 정상 요청이 막히면 원인 찾기 번거로워진다.
Tempo 연동 자체는 별 이슈 없이 붙었다. Spring Boot 3.x 기준으로 Micrometer Tracing에 Brave나 OpenTelemetry 중 하나를 골라 의존성 추가하고, 트레이스 내보낼 엔드포인트를 설정에 넣어주면 기본 구성은 끝난다. 샘플링 비율은 운영 초기에 낮게 잡고, 트래픽 패턴이 파악된 뒤 조정하는 게 안전하다. 100% 샘플링은 트레이스 저장소에 부하를 주고 스토리지 비용도 따라 올라가니까, 처음부터 전부 잡으려 하기보다 의미 있는 샘플에서 패턴을 읽는 게 낫다.
Grafana 대시보드, 뭘 보는 게 맞나
트레이싱을 붙이고 나서 Grafana를 활용하는 방식이 달라진다. 대시보드에서 레이턴시 스파이크를 발견하면, Tempo 패널로 넘어가 해당 시간대 느린 트레이스를 바로 조회할 수 있다. Loki 연동이 돼 있으면 같은 trace ID로 에러 로그도 한 화면에서 볼 수 있어서 맥락 전환 없이 원인을 좁혀갈 수 있다.
기본적으로 봐야 할 지표는 아래처럼 정리해두면 편하다.
| 카테고리 | 지표 | 확인 목적 |
|---|---|---|
| JVM 메모리 | jvm_memory_used_bytes, jvm_gc_pause_seconds |
힙 압박·GC 튜닝 단서 |
| 스레드 풀 | jvm_threads_live_threads |
포화 여부 조기 감지 |
| HTTP 요청 | http_server_requests_seconds |
요청 수, p95/p99 레이턴시 |
| 트레이스 | Tempo 서비스 그래프 | 서비스 간 의존성과 에러율 |
알림 설정은 임계치 초과 시 Slack 채널로 연동해두면 야간에 이상 징후가 생겨도 빠르게 인지할 수 있다. Grafana Alerting에서 contact point를 Slack webhook으로 걸어두는 건 그리 오래 걸리는 작업이 아닌데, 대시보드만 만들고 알림을 미뤄두는 건 절반만 한 거라고 생각한다. 보고 있어야 의미가 있지, 대시보드가 예쁘게 만들어져 있다고 장애가 줄어드는 건 아니니까.
이번 작업은 규모 자체는 작았다. 하지만 메트릭만 있을 때와 트레이싱이 붙었을 때 장애 대응 속도는 체감상 꽤 다르다. 요청 하나가 어디서 얼마나 걸렸는지 눈에 보이면 "이 서비스 느린 것 같은데"가 아니라 "DB 쿼리에서 병목이 있고 이게 문제"로 바로 좁혀진다. YAML 공백 하나 때문에 잠깐 막혔던 게 오히려 필터 로직을 다시 검토하는 계기가 됐으니, 작은 버그가 나쁜 것만은 아니다.
댓글 0
첫 댓글 달아줘.