Spring Boot 기반 Observability 구축기

2026. 7. 15. 13:30Development/[Server] 서버

728x90
반응형

Spring Boot 기반 Observability 구축기 (Prometheus · Grafana · OpenTelemetry · Tempo · Loki · Kafka)

들어가며

백엔드 애플리케이션을 운영하다 보면 단순히 서비스가 동작하는 것만으로는 충분하지 않다.

장애가 발생했을 때 다음과 같은 질문에 답할 수 있어야 한다.

  • 현재 CPU와 메모리는 정상인가?
  • 어떤 API가 느린가?
  • 요청 하나가 어떤 서비스를 거쳤는가?
  • Kafka를 통해 전달된 메시지는 어디까지 처리되었는가?
  • 실제 에러 로그는 무엇인가?

이러한 문제를 해결하기 위한 개념이 바로 Observability(관측성) 이다.

이번 실습에서는 Spring Boot 애플리케이션에 다음 기술들을 적용하여 Observability 환경을 구축하였다.

Spring Boot
Prometheus
Grafana
OpenTelemetry
Tempo
Kafka
Loki
Grafana Alloy

 

Observability의 3대 축

 

Observability는 일반적으로 다음 세 가지 요소로 구성된다.

1. Metrics

애플리케이션의 상태를 수치로 표현한다.

예시

CPU 사용률
메모리 사용량
응답 시간
TPS
에러율
주문 생성 횟수

2. Traces

하나의 요청이 시스템 내부를 어떻게 이동했는지 보여준다.

예시

HTTP Request
 ↓
Service
 ↓
Kafka Producer
 ↓
Kafka Consumer
 ↓
Database

3. Logs

실제 발생한 이벤트와 예외 메시지를 저장한다.

예시

INFO
WARN
ERROR
Stack Trace

 

전체 아키텍처

최종적으로 구성한 구조는 다음과 같다.

Prometheus

Prometheus는 Metrics 저장소 역할을 수행한다.

Spring Boot Actuator가 제공하는 메트릭을 주기적으로 수집한다.

수집 방식은 Pull 기반이다.

Prometheus
     ↓
/actuator/prometheus
수집 가능한 대표 메트릭
JVM Memory
CPU Usage
GC
Thread Count
HTTP Requests
Custom Metrics
예를 들어 다음과 같은 비즈니스 메트릭도 수집할 수 있다.
주문 생성 횟수
주문 처리 시간

 

Grafana

Grafana는 데이터 시각화 플랫폼이다.

Prometheus, Tempo, Loki와 연결하여 하나의 UI에서 모든 정보를 확인할 수 있다.


Grafana에서 확인한 것

CPU 사용량
메모리 사용량
요청 수
응답 시간
Trace
로그

 

OpenTelemetry

Observability 세계의 사실상 표준이다.

Metrics, Traces, Logs를 생성하고 전달하는 역할을 한다.

이번 실습에서는 주로 Trace 생성에 활용하였다.

Trace란?

예를 들어 API 호출이 발생했다고 가정하자.

내부적으로는 다음과 같은 흐름이 발생한다.
Controller
 ↓
Service
 ↓
Kafka Producer
 ↓
Kafka Consumer

 

Trace는 이러한 흐름을 하나의 요청 단위로 추적하는 기술이다.


Span

Trace는 여러 개의 Span으로 구성된다.

예시

GET /orders
 ├─ order-processing
 └─ kafka-producer
      └─ kafka-consumer
각 Span은 다음 정보를 가진다.
시작 시간
종료 시간
실행 시간
예외 여부
부모 Span
자식 Span

 

Tempo

Tempo는 Grafana 생태계의 Trace 저장소이다.

OpenTelemetry가 생성한 Trace 데이터를 저장한다.


Tempo를 통해 다음 정보를 확인할 수 있었다.

요청 흐름
실행 시간
Span 관계
서비스 간 호출

 

Kafka와 Distributed Tracing

이번 실습에서 가장 흥미로웠던 부분이다.

일반적으로 Kafka를 사용하면 다음과 같은 문제가 발생한다.

Producer Trace
Consumer Trace
가 서로 분리되어 저장된다.

 

Trace Context Propagation

이 문제를 해결하기 위해 Trace Context를 Kafka Header에 저장하였다.

흐름은 다음과 같다.

HTTP Request
 ↓
Trace Context 생성
 ↓
Kafka Header 저장
 ↓
Consumer 수신
 ↓
Trace Context 복원

최종적으로 아래와 같이 하나의 Trace로 연결되어 확인할 수 있었다.

GET /orders
 ├─ order-processing
 └─ kafka-producer
      └─ kafka-consumer

 

Loki

Loki는 로그 저장소이다.

Prometheus가 메트릭을 저장하듯 Loki는 로그를 저장한다.

 

Grafana Alloy

Grafana Alloy는 로그 수집 에이전트이다.

과거에는 Promtail을 많이 사용했지만 최근에는 Alloy가 권장된다.

로그 파일
 ↓
Alloy
 ↓
Loki

 

Logs + Trace 연계

Observability에서 가장 강력한 기능 중 하나다.

예를 들어 Tempo에서 다음 Trace를 발견했다고 하자.

GET /orders
 └─ kafka-consumer
 

이때 Trace ID를 이용하여 Loki에서 관련 로그를 조회할 수 있다.

 

최종 경험 정리

Kafka 환경에서 Trace Context를 전파하여 실제 Distributed Tracing까지 구현하였다.

최종적으로는 다음 구조를 완성하였다.

Spring Boot
     │
     ├─ Metrics → Prometheus → Grafana
     │
     ├─ Traces → OpenTelemetry → Tempo → Grafana
     │
     └─ Logs → Alloy → Loki → Grafana
 
 
단순히 "모니터링"을 넘어서, 요청 하나가 시스템 내부를 어떻게 이동하고 어떤 로그를 남기며 어디서 문제가 발생했는지 추적할 수 있는 Observability 환경을 직접 구축해본 것이 이번 실습의 가장 큰 수확이었다

 

728x90
반응형