ch 6 과제 목표가 최프 때 쓸 것 같은 기술들 한 번 간단하게 써보자~ 였는데 마침 Kafka 도 넣고 모니터링도 해볼 수 있을 것 같아서 Grafana 랑 Prometheus 를 사용해보기로 했다 간단하게라도... 사용해보면 좋지 않을까...ㅎ
Grafana 랑 Prometheus 는 최신 인프라 및 애플리케이션 모니터링을 위한 핵심 오픈소스 조합 ,,, 이라고 한다
풀어서 정리하자면
프로메테우스가 데이터를 수집, 저장하면/ 그라파나는 프로메테우스가 수집한 데이터를 가져와서 대시보드에서 시각화 해준다.
즉 프로메테우스는 데이터 저장소, 그라파나는 화면!
-> 안정적인 서비스 운영을 위해 상태 실시간 관찰 가능, 문제 발생 시 즉각 대응 가능
파일 설정
root/
├── docker-compose.yml # ← Prometheus, Grafana 서비스 추가
├── monitoring/
│ ├── prometheus.yml # ← Prometheus 수집 대상 설정
│ └── grafana/
│ ├── provisioning/
│ │ ├── datasources/
│ │ │ └── datasource.yml # ← Grafana 데이터소스 자동 등록
│ │ └── dashboards/
│ │ └── dashboard.yml # ← 대시보드 로드 설정
│ └── dashboards/
│ └── coffee-service.json # ← 실제 대시보드 JSON (export한 것)
build.gradle
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator' # actuator 활성화용
메트릭을 Prometheus가 읽을 수 있는 포맷으로 변환해서 노출
JVM 메모리, HTTP 요청 수, 응답 시간 등등... 진짜 많은 것이 기록 됨
Docker-compose.yml
Prometheus 랑 Grafana 는 SpringBoot 안에 내장된게 아니라 별도 프로세스로 동작 -> 로컬에 직접 설치하는 것 보다 Docker Compose 로 한번에 올려서 통신할 수 있도록 하는게 편함!
# --------------------------
# Prometheus
# --------------------------
prometheus:
image: prom/prometheus
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- kafka-net
# --------------------------
# Grafana
# --------------------------
grafana:
image: grafana/grafana
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
- ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
depends_on:
- prometheus
networks:
- kafka-net
volumes:
grafana_data: # 주의 ^^...
extra host 가 필요한 이유: spring boot 를 ... 로컬에서 돌렸으니까 컨테이너 안에서 localhost 에 접근하려면 host.docker.internal 이라는 호스트 명이 필요하고, host-gateway 가 활성화시켜줌
| 마운트 | 종류 | 목적 |
| grafana_data:/var/lib/grafana | named volume | Grafana가 런타임에 생성한 데이터 (대시보드 수정사항, 사용자 설정 등)를 docker-compose down 해도 보존 |
| ./monitoring/grafana/provisioning:... | bind mount | 내 프로젝트에 작성한 설정 파일을 컨테이너 안에 전달 |
| ./monitoring/grafana/dashboards:... | bind mount | 대시보드 JSON 파일을 컨테이너 안에 전달 |
| 종류 | 예시 | 목적 |
| named volume | grafana_data:/var/lib/grafana | 컨테이너가 만든 데이터를 호스트에 영속 저장 docker-compose down해도 유지 |
| bind mount | ./monitoring/prometheus.yml :/etc/prometheus/prometheus.yml |
내 로컬 파일을 컨테이너 안에 그대로 전달. 파일 공유 목적 |
application.yml
management:
endpoints:
web:
exposure:
include: metrics, health, prometheus, info # promethus 추가 필수
monitoring/prometheus.yml
# monitoring/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'coffee-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['host.docker.internal:8080']
# Spring Boot 앱이 로컬에서 돌아가니까 host.docker.internal로 접근
monitoring/grafana/provisioning/datasources/datasource.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
이 파일이 있으면 Grafana 에 접속해서 데이터소스를 prometheus 랑 직접 연결하지 않고 자동으로 연결해줌
grafana 랑 prometheus 는 지금 같은 docker container 안에 있기 때문에 그냥 저렇게 url 을 적어줘도 된다
monitoring/grafana/provisioning/dashboards/dashboard.yml
apiVersion: 1
providers:
- name: 'default'
folder: 'Coffee Service'
type: file
options:
path: /var/lib/grafana/dashboards
Grafana가 /var/lib/grafana/dashboards 경로에서 JSON 파일을 읽어 대시보드를 자동 로드하도록 설정
동작 방식

spring boot 에서 actuator/prometheus endpoint 를 열어주면 prometheus 가 설정한 주기에 맞춰서 spring app 에서 정보를 가져감 -> grafana 가 저장된 데이터를 가져와서 그래프로 시각화 하는 형태
주의할점: 앱이 데이터를 prometheus 로 밀어넣는게 아니라 prometheus 가 가져감
PromQL 은 SQL 이랑 약간 형태가 다른데 어렵지 않고 검색하면 나오니까 ... 그냥 찾아서 쓰면 된다
이번 커피 프로젝트에서 사용한 것들은 아래와 같다
| 구분 | 메트릭 | PromQL | 쿼리선정 이유 |
| API | 엔드포인트별 RPS | rate(http_server_requests_seconds_count[1m]) | 느린 API 식별 및 트래픽 패턴 파악 |
| API | 평균 응답시간 | rate(http_server_requests_seconds_sum[1m]) / rate(http_server_requests_seconds_count[1m]) |
엔드포인트별 병목 감지 |
| JVM | 힙 메모리 (Eden/Old/Survivor) |
jvm_memory_used_bytes{area="heap"} | 메모리 누수 조기 감지 |
| DB | HikariCP active/idle/pending |
hikaricp_connections_active | 커넥션 고갈 및 deadlock 감지 |
| 비즈니스 | 결제 성공 처리량 | rate(payment_success_count_total[1m]) | 주문→결제 전환 성능 측정 |
| 비즈니스 | 결제 소요시간 (p95) | histogram_quantile (0.95, rate(payment_duration_seconds_bucket[5m])) |
주문→결제 전환 성능 측정 |
| 비즈니스 | 취소 사유별 건수 | rate(payment_cancel_count_total[1m]) | 취소 패턴 분석( 시간초과 vs 취소) |
Custom Metric
Kafka Consumer 에서 Custom Metric 수집하기
기본 Actuator Metric 은 자동으로 추가되는데 Business Metric 은 추가가 필요함
@Slf4j
@Component
public class MetricsConsumer {
private final Timer paymentDurationTimer;
private final Counter paymentSuccessCounter;
private final Counter paymentCancelCounter;
public MetricsConsumer(MeterRegistry meterRegistry) {
this.paymentDurationTimer = Timer.builder("payment.duration")
.description("주문 생성 → 결제 완료 소요시간")
.register(meterRegistry);
this.paymentSuccessCounter = Counter.builder("payment.success.count")
.description("결제 성공 횟수")
.register(meterRegistry);
this.paymentCancelCounter = Counter.builder("payment.cancel.count")
.description("결제 취소 횟수")
.register(meterRegistry);
}
@KafkaListener(topics = "payment-events", groupId = "metrics")
public void consume(PaymentEvent event) {
switch (event.type()) {
case "PAYMENT_COMPLETED" -> {
paymentSuccessCounter.increment();
if (event.orderedAt() != null) {
Duration duration = Duration.between(
event.orderedAt(), event.occurredAt()
);
paymentDurationTimer.record(duration);
}
}
case "PAYMENT_CANCELLED" -> {
paymentCancelCounter.increment();
}
}
}
}
이번에 Order 가 생성되고 Pay 또는 Cancel 이 되는 횟수, Pay 까지 시간이 얼마나 걸리는지 체크하는것을 목표로 했다
| MeterRegistry | pring Boot Actuator에 포함된 메트릭 등록을 위한... 통과소? 같은 느낌 생성자 주입으로 받아서 Timer나 Counter를 등록하면 자동으로 /actuator/prometheus에 노출 |
| Timer | 시간 차이 기록: Grafana에서 히스토그램으로 p50/p95/p99 소요시간을 확인 가능 |
| Counter | 결제 성공/취소가 발생할 때마다 1씩 증가하는 누적 카운터 Grafana에서 rate() 함수를 걸면 초당 결제 성공 건수로 변환 가능 |
Grafana UI 설정
... 좀있다가 추가해야지
트러블슈팅
docker-compose down 시 대시보드가 사라짐....
docker-compose down을 실행한 뒤 다시 docker-compose up -d를 하니 Grafana에 들어가보면 이전에 만들어둔 대시보드가 전부 사라짐......

말 그대로 Grafana Service 에 named volume 을 설정하지 않아서 날아간 것......
근데 당연한거였는데 내가 그냥 바보엿다 Docker container 가 기본적으로 무상태-> 내부에서 수정한 내용이니 당연히 날아감
== Grafana가 런타임에 생성하는 데이터(대시보드, 알림 규칙, 사용자 설정 등)는 컨테이너 내부의 /var/lib/grafana에 저장되는데, volume 없이 띄우면 이 데이터가 컨테이너 라이프사이클에 종속된다.
해결
docker-compose.yml의 Grafana 서비스에 named volume을 추가
grafana:
image: grafana/grafana
volumes:
- grafana_data:/var/lib/grafana # 이 줄 추가
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
- ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
그리고 최상위 volumes: 섹션에 grafana_data:를 선언
volumes:
grafana_data:
docker-compose down -v 를 실행하면 named volume까지 삭제되므로 대시보드가 또 날아가니까
-v 옵션은 정말 모든 데이터를 초기화 시킬때만 사용
-->>>> 추가 방어: Provisioning으로 대시보드 코드화
대시보드를 JSON으로 export해서 Git에 함께 커밋해두면, volume이 날아가더라도 docker-compose up -d만 하면 자동으로 대시보드가 복구된다.
대시보드를 JSON 으로 export 하는 방법은 우측 상단에 메뉴를 누르고 Export, 또는 JSON Model 을 눌러서 빼면 되는데
나는 이상하게... 안 보였다
다른 방법은 주소창에 이거 쓰기
http://localhost:3000/api/dashboards/uid/adrmvxj
뭐가 잔뜩 뜬다...
복사해서 JSON 파일로 만들어달라고 AI 한테 부탁하기 또는 직접 고치기
| datasource uid | ${DS_PROMETHEUS} (변수)로 교체 |
| meta 제거 | API 응답에 포함된 meta 블록은 provisioning에서 필요 X 삭제 |
'SPARTA 과제 > SPRING' 카테고리의 다른 글
| Project 3 생각해 볼만한 부분 정리 (0) | 2026.05.11 |
|---|---|
| Lock Service 분리 트러블슈팅 (0) | 2026.05.11 |
| Distributed Lock 트러블슈팅 (0) | 2026.05.07 |
| RedisTemplate StackOverFlow Error (1) | 2026.05.03 |
| K6 테스트 (0) | 2026.04.29 |