SPARTA 과제/SPRING

Grafana, Prometheus 도입

kjw81024 2026. 5. 12. 23:50

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