3개 마이크로서비스가 의존하는 인프라(DB·Redis·Kafka)와 옵저버빌리티 스택을 한 번에 띄우는 Docker Compose 스택입니다. 애플리케이션 서비스 자체는 컨테이너에 넣지 않고 ./gradlew bootRun(루트 README의 Quick Start 참고) 또는 Helm(K8s)으로 실행합니다.
| Service | Port | Purpose |
|---|---|---|
| PostgreSQL | 5432 | orderdb, paymentdb, inventorydb 생성 |
| Redis | 6379 | 재고 캐시, 분산락 (여러 인스턴스가 같은 자원에 동시 접근 못 하게 잠그는 도구) 데모 |
| Kafka | 9092 | Phase 2 이벤트 흐름 (메시지 브로커 — 발행/구독 채널) |
| OpenTelemetry Collector | 4317, 4318 | OTLP (OTel 의 표준 송신 프로토콜) 수신과 텔레메트리 라우팅 |
| Prometheus | 9090 | 메트릭 저장, 알람 룰 평가 |
| Loki | 3100 | 로그 저장 |
| Tempo | 3200 | 트레이스 저장 |
| Grafana | 3000 | 대시보드와 데이터소스 연결 |
| Alertmanager | 9093 | 알람 라우팅 (Prometheus 가 발화시킨 알람을 슬랙/이메일 등으로) |
docker compose -f infra/docker-compose.yml up -d
docker compose -f infra/docker-compose.yml psGrafana는 http://localhost:3000 에서 admin / admin으로 접속할 수 있습니다.
기본은 인프라만 띄우고 3 서비스는 호스트에서 ./gradlew bootRun 으로 띄웁니다. 셸 3개를 더 열지 않고 한 명령으로 전부 올리고 싶으면 docker-compose.app.yml override 를 추가합니다:
docker compose \
-f infra/docker-compose.yml \
-f infra/docker-compose.app.yml \
up -d --build- 각 서비스는 자기
services/<name>/Dockerfile(multi-stage, JDK 21 빌드 → JRE 런타임) 로 빌드됩니다. build context 는 레포 루트 — order-service 가modules/를 composite build 로 당겨오기 때문입니다. - 컨테이너 안에서 인프라는 컨테이너 DNS 로 붙습니다 (
postgres/redis/kafka:29092INTERNAL listener /otel-collector:4318). - 각 서비스는 HTTP 포트 (8081/8082/8083) 를 호스트로 publish 합니다 — 그래야 Prometheus 의
host.docker.internal:808xscrape (ADR-008) 가prometheus.yml수정 없이 그대로 동작합니다. - 설정 검증 (Docker daemon 없이 YAML merge 만):
docker compose -f infra/docker-compose.yml -f infra/docker-compose.app.yml config.
이미지 빌드/기동은 Docker daemon 이 떠 있어야 합니다. 본 레포의 표준 데모 경로는 여전히 인프라는 컨테이너 + 앱은 호스트 bootRun 입니다 (Prometheus host scrape 전제와 가장 잘 맞음).
docker compose -f infra/docker-compose.yml configPrometheus 컨테이너는 OTLP metrics 와 remote-write (다른 시스템이 메트릭을 직접 푸시하는 방식) 트래픽을 모두 받을 수 있게 설정되어 있습니다.
- OpenTelemetry Collector 는 OTLP metrics 를
/api/v1/otlp/v1/metrics로 보냅니다. - Tempo metrics-generator (트레이스로부터 메트릭을 자동 생성하는 부가 기능) 와 k6 (부하 도구) 는 remote-write metrics 를
/api/v1/write로 보냅니다.
기본은 단일 collector (otel-collector 한 컨테이너). 트래픽이 올라가 tail_sampling buffer 가 saturate 에 닿으면 agent → backend pool 의 2-tier 로 분리합니다.
docker compose \
-f infra/docker-compose.yml \
-f infra/docker-compose.collector-2tier.yml \
up -d핵심:
- agent (
agent.yaml) 는 어플리케이션이 보내는 OTLP (4317/4318) 를 그대로 받아loadbalancingexporter 로 backend pool 에 분배.routing_key=traceID의 consistent hashing 으로 같은 trace 의 모든 span 이 항상 같은 backend 인스턴스로 라우팅됩니다 (tail_sampling 의 의사결정이 정확하려면 한 trace 의 span 이 한 instance 메모리에 모여야 함). - backend (
backend.yaml) 가 tail_sampling 을 수행하고 Tempo 로 export.OTEL_BACKEND_ID를 resource attribute 로 박아 검증 스크립트가 같은 trace 의 모든 span 이 한 backend 로 갔는지 실측할 수 있게 했습니다.
검증:
./infra/otel-collector/verify-2tier.sh
# [verify-2tier] result: total=50 ok=50 split=0 not_sampled=0
# [verify-2tier] OK — every sampled trace had a single backendsplit > 0 이면 routing_key 가 깨진 것 — 코드 변경 후 회귀 테스트로 활용합니다. ADR-017 참고.
로컬 데이터 (DB·Kafka·Loki·Tempo 의 영속 볼륨) 를 버려도 될 때만 사용합니다.
docker compose -f infra/docker-compose.yml down -v