Skip to content

Repository files navigation

monolith-otel-lab

모놀리식 Spring Boot 애플리케이션에 OpenTelemetryGrafana Tempo를 적용해, 단일 프로세스 안에서 요청이 계층(Controller → Service → Inventory → Payment → Repository → DB)을 따라 흐르는 모습을 trace / metrics / logs로 관측할 수 있는지 검증하는 실험 프로젝트다.

MSA가 아니다. 목적은 "모놀리식 내부 요청 흐름을 어떻게 관측하는가"를 보여주는 것이다.

📚 학습 자료로 사용하기

이 저장소는 교재처럼 읽고 실습할 수 있게 구성되어 있다. 처음이라면 study-guide부터.

문서 내용
docs/study-guide.md 읽기 순서 · 실습 체크포인트 9개 · 연습문제 6개 · FAQ
docs/architecture.md 구성도 — 토폴로지, 계층, 요청 시퀀스, 설정 파일 지도
docs/observability-deep-dive.md 동작 원리 — @Observed가 Tempo의 span이 되기까지
.workspace/ 설계 결정 기록(ADR 8종) · 스펙 · 검증 결과 — "왜 이렇게 만들었나"

Architecture

flowchart LR
    Client[Client] --> App[Monolith Spring Boot App]
    App --> DB[(PostgreSQL)]
    App -- OTLP --> Collector[OpenTelemetry Collector]
    Collector --> Tempo[Grafana Tempo]
    Prometheus[Prometheus] -- scrape /actuator/prometheus --> App
    Grafana[Grafana] --> Tempo
    Grafana --> Prometheus
Loading

Tech Stack

영역 선택
Language / Framework Java 21, Spring Boot 3 (Spring MVC)
Build Gradle (Groovy) + Wrapper
Persistence Spring Data JPA (Hibernate)
Database PostgreSQL
Tracing Micrometer Observation + Micrometer Tracing (OpenTelemetry bridge) → OTLP
Trace backend Grafana Tempo
Metrics Micrometer + Spring Boot Actuator (Prometheus registry)
Dashboard Grafana (datasource + 대시보드 자동 provisioning)
Alerting Grafana managed alerting (span metrics 기반 실험 rule)
Logging Logback structured JSON (trace_id / span_id 포함)

Observability Design

  • Trace: 각 계층 메서드를 @Observed로 감싸 span을 만든다. Micrometer Observation이 span을 만들고, Micrometer Tracing(OTel bridge)이 OTLP(HTTP, :4318/v1/traces)로 Collector에 보낸다. Collector는 Tempo로 trace를 전달한다. HTTP 요청 자체는 Spring MVC가 자동으로 root span(http.server.requests)을 만든다.
  • Metrics: 앱 자체 metric은 Actuator /actuator/prometheus로 노출하고 Prometheus가 직접 scrape한다. 추가로 Tempo metrics-generator가 trace span에서 traces_spanmetrics_* RED metrics를 생성해 Prometheus에 remote write한다. 즉, metric은 "앱 계측 기반"과 "trace 파생 기반" 두 경로를 함께 볼 수 있다.
  • Logs: Logback이 JSON으로 출력하며, Micrometer Tracing이 MDC에 넣은 traceId/spanIdtrace_id/span_id 필드로 노출해 로그와 trace를 연결한다.

Quick Start

make up      # docker compose up --build (app, postgres, collector, tempo, prometheus, grafana)

기동 후:

curl localhost:10080/healthz       # {"status":"ok"}

Docker 런타임이 필요하다. Docker Desktop을 실행하거나 colima를 사용한다면 colima start 후 진행한다. Makefile이 docker compose(플러그인)와 docker-compose(단독 바이너리)를 자동 감지한다. 다른 런타임은 make up COMPOSE="nerdctl compose" 처럼 override할 수 있다. App의 기본 host port는 10080이다. 컨테이너 내부 포트는 계속 8080이며, 필요하면 make up APP_PORT=19090처럼 host port만 바꿀 수 있다.

Generate Traffic

make load    # 정상 주문 20건 + 실패 주문(fail_payment=true) 1건

Open Grafana

http://localhost:3000   (admin / admin)
  • Explore → Tempo: 최근 trace를 검색해 POST /orders 요청을 연다.
  • Dashboards → monolith-otel-lab: 요청 수/지연, 주문 생성/실패 카운트, span metrics 기반 RED 지표.
  • Alerting → Alert rules: 결제 실패 span이 들어오면 Payment authorization span errors rule이 Firing 되는지 확인.

What to Check

정상 주문 trace 구조 (Tempo에 표시되는 실제 span 이름):

http post /orders
  └─ order-controller.create-order
       └─ order-service.create-order
            ├─ inventory-service.reserve
            ├─ payment-client.authorize
            └─ order-repository.insert

⚠️ span 이름은 코드의 OrderService.createOrder가 아니라 kebab-case로 표시된다 — Micrometer가 이름을 정규화하기 때문이다. Tempo에서 검색할 때 주의. (원리 설명)

실패 주문(POST /orders?fail_payment=true) trace에서는 payment-client.authorize span이 error 상태이고, order-repository.insert는 실행되지 않는다.

JSON 로그에 trace_id/span_id가 포함되어 Tempo의 trace와 연결할 수 있다.

Alert Test

이 프로젝트는 Grafana managed alert rule을 provisioning한다. 외부 Slack/Email/Webhook 전송은 붙이지 않고, Grafana UI/API에서 alert 상태 전이를 확인하는 실험용 구성이다.

make load

이 명령은 마지막에 POST /orders?fail_payment=true를 1회 보내므로, Tempo span metrics의 payment-client.authorize error count가 증가한다. Grafana에서:

Alerting → Alert rules → Payment authorization span errors

를 열면 10~30초 안에 Firing 상태를 확인할 수 있다. API로도 확인 가능하다.

curl -fsS -u admin:admin http://localhost:3000/api/alertmanager/grafana/api/v2/alerts \
  | jq '.[] | select(.labels.alertname=="Payment authorization span errors") | {labels,status}'

rule은 최근 2분의 결제 실패 span 증가분을 보기 때문에, 새 실패 요청이 없으면 시간이 지나며 다시 Normal로 돌아간다.

Local Kubernetes with kind

Docker Compose와 같은 관측성 스택을 로컬 Kubernetes에서도 실험할 수 있다. Kubernetes 예제는 kind 기준이며, Grafana provisioning은 compose에서 쓰는 dashboard/datasource/alert 파일을 재사용한다.

Compose stack과 같은 host port(10080, 3000, 9090)를 사용한다. 이미 compose를 실행 중이면 먼저 make down으로 정리한다.

namespace는 목적별로 분리한다.

Namespace Purpose Resources
monolith-otel-app 애플리케이션 런타임 Spring Boot app Service/Deployment
monolith-otel-data 데이터 저장소 PostgreSQL Service/Deployment
monolith-otel-observability 관측성 플랫폼 OpenTelemetry Collector, Tempo, Prometheus, Grafana

namespace가 다르기 때문에 app은 postgres.monolith-otel-data.svc.cluster.local, otel-collector.monolith-otel-observability.svc.cluster.local처럼 namespace-qualified DNS로 접근한다.

make k8s-up
curl -fsS http://localhost:10080/healthz
make k8s-load
make k8s-status

확인 URL:

App:        http://localhost:10080
Grafana:    http://localhost:3000   (admin/admin)
Prometheus: http://localhost:9090

정리:

make k8s-down

Test / Verification Guide

1. Fast application tests

애플리케이션 코드 변경을 빠르게 확인할 때 사용한다.

make test

기대 결과:

BUILD SUCCESSFUL

테스트 범위는 단위 테스트, MockMvc web slice, JPA(H2) slice, Spring context-load다. 실제 PostgreSQL/Tempo/Grafana 연동은 아래 통합 테스트로 확인한다.

2. Docker Compose integration test

로컬 기본 실습 경로다.

make up
curl -fsS http://localhost:10080/healthz
make load

기대 결과:

{"status":"ok"}
sent 20 successful orders and 1 failing payment request

Prometheus에서 앱 metric과 span metrics를 확인한다.

curl -fsS -G 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=order_created_count_total'

curl -fsS -G 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=sum by (span_name, status_code) (traces_spanmetrics_calls_total)'

Grafana provisioning도 API로 확인할 수 있다.

curl -fsS -u admin:admin http://localhost:3000/api/datasources
curl -fsS -u admin:admin http://localhost:3000/api/v1/provisioning/alert-rules

브라우저에서는 다음을 확인한다.

Grafana → Explore → Tempo → http post /orders trace
Grafana → Dashboards → monolith-otel-lab
Grafana → Alerting → Payment authorization span errors

정리:

make down

3. Kubernetes integration test

kind 기반 로컬 Kubernetes 경로다. Compose와 같은 host port를 쓰므로, 먼저 Compose stack을 내려둔다.

make down
make k8s-dry-run
make k8s-up
make k8s-status
curl -fsS http://localhost:10080/healthz
make k8s-load

기대 결과:

6 pods Running/Ready across 3 namespaces
{"status":"ok"}
sent 20 successful orders and 1 failing payment request

Kubernetes 환경에서도 같은 URL에서 확인한다.

App:        http://localhost:10080
Grafana:    http://localhost:3000   (admin/admin)
Prometheus: http://localhost:9090

Tempo API를 직접 보고 싶으면 짧게 port-forward를 연다.

kubectl -n monolith-otel-observability port-forward svc/tempo 3200:3200

curl -fsS -G 'http://localhost:3200/api/search' \
  --data-urlencode 'q={name="http post /orders"}' \
  --data-urlencode 'limit=5'

정리:

make k8s-down

API

Method Path 설명
GET /healthz {"status":"ok"}
POST /orders 주문 생성 (201)
POST /orders?fail_payment=true 결제 실패 시뮬레이션 (402)
GET /orders/{order_id} 주문 조회 (200 / 404)

요청/응답 예시:

curl -X POST localhost:10080/orders -H 'Content-Type: application/json' \
  -d '{"user_id":"user-1","items":[{"sku":"item-1","quantity":2}]}'
# -> {"order_id":"...","status":"created"}

Local Development

make test    # ./gradlew test (단위 + web 슬라이스 + JPA(H2) 슬라이스 + context-load)
make build   # ./gradlew bootJar

테스트는 빠른 피드백을 위해 H2를 사용한다. 실제 PostgreSQL 동작은 make up 통합 스택에서 검증한다.

Ports

app:             http://localhost:10080
grafana:         http://localhost:3000   (admin/admin)
prometheus:      http://localhost:9090
postgres:        localhost:5432
otel-collector:  localhost:4317 (gRPC), 4318 (HTTP)
tempo:           internal only

Prometheus는 Docker 네트워크 내부에서 app:8080을 scrape하므로 host port 변경의 영향을 받지 않는다. Tempo metrics-generator는 span metrics를 Prometheus의 /api/v1/write로 remote write한다.

Project Layout

src/main/java/com/sangjinsu/monolithotellab
  ├─ web         REST controller, exception handler, request log filter
  ├─ order       service, JPA entity, repository, dto
  ├─ inventory   fake inventory service
  ├─ payment     fake payment client
  └─ platform/observability   @Observed 활성화(ObservedAspect)
deploy/           docker compose/k8s 관측성 설정, otel-collector, tempo, prometheus, grafana provisioning + dashboard
docs/             학습 문서 — 구성도 · 동작 원리 · 학습 가이드 (+ Grafana 스크린샷)
.workspace/       LLM 작업 위키 — 스펙, ADR 8종, 계획, 검증 기록 (읽는 법: .workspace/README.md)

Notes

  • 실험 프로젝트이므로 인증/인가, 실제 결제 연동, 운영 수준 구성은 포함하지 않는다.
  • 설계 결정과 변경 이력은 .workspace/에 기록되어 있다 (AGENTS.md가 최상위 스펙).

License

MIT

About

Monolith observability lab — Spring Boot + OpenTelemetry to Grafana Tempo, Prometheus metrics, and trace-correlated JSON logs (docker-compose)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages