본문 바로가기
DevOps/Monitoring, Observability

#1 Prometheus + Grafana 모니터링 구축하기

by 방배킹 2025. 8. 31.

 

저번글(https://bangbaeking.tistory.com/165)에서 모니터링과 옵저버빌리티에 대해 알아봤다.

옵저버빌리티가 “ 그런 일이 일어났는가?”를 추론하는 과정이라면, 모니터링은 그 신호를 지속 관측하고 경보/대시보드로 운영을 돕는 방식이다.

보통 Metrics / Logs / Traces 세 축을 함께 다루지만, 이번글에서는 모니터링과 Metric에 대해 알아보고 실습을 진행해보려고한다.

메트릭 수집 모델

메트릭 수집 방식에는 Push-based Collector 와 Pull-based Collector가 있다.

Push-based Collector

  • 장점
    • 방화벽/NAT 환경에서 바깥으로만 전송하면 되므로 네트워크 제약 대응에 유리
    • 각 타겟에서 원하는 대로 메트릭 세밀하게 조절
    • 수집 주기나 데이터 형식이 자유로움
  • 단점
    • 수집기 입장에서 대상의 가용성(살아 있는지) 파악이 어려움
    • 메트릭 서버에 부하가 커질 위험이 있음
    • 각 타겟에 설정이 퍼져있어 설정 파편화 위험

Pull-based Collector (Prometheus)

  • 장점
    • 수집 서버가 주기적으로 직접 조회 → 대상의 가용성, 지연 확인이 용이
    • 대부분의 설정이 중앙 집중되어 관리가 용이, 확장성이 좋음
    • 서비스 디스커버리(Kubernetes, Eureka, file_sd 등)와 자연스럽게 맞물림
  • 단점
    • 타깃이 내부망 뒤에 있으면 네트워크/ACL 설계 필요
    • Exporter 설치가 필요한 경우가 많음 (스프링은 자체 엔드포인트 노출만으로도 수집 가능)

PrometheusPull 모델의 표준이라 할 만큼 널리 쓰이며, 장점은 아래와 같다.

  1. 스프링과의 궁합: Micrometer + Actuator 가 Prometheus 포맷으로 쉽게 노출
  2. 생태계: 수많은 Exporter(OS/미들웨어), Grafana, Alertmanager 연동
  3. 운영 단순성: 단일 바이너리/간단한 설정으로 바로 수집·질의 가능
  4. PromQL: 강력한 시계열 질의/집계/함수 지원

스프링 앱에서 메트릭 흐름

(1) Micrometer (수집)

Micrometer는 자바 생태계에서 널리 쓰이는 벤더 중립 메트릭 수집 라이브러리 이다. 애플리케이션 내부에서 발생하는 지표를 표준 API로 수집하고, Prometheus, CloudWatch 등 다양한 백엔드로 내보낼 수 있도록 추상화를 제공한다.

무엇을 자동으로 모니터링하나?

  • HTTP 요청(Spring MVC/WebFlux): 요청 수, 응답 시간, 상태코드 등
  • JVM: 힙/비힙 메모리 사용량(jvm_memory_used_bytes), GC 일시정지(jvm_gc_pause_seconds_*), 스레드 수 등
  • DB/커넥션풀: HikariCP 커넥션 수, 대기 시간, 사용률 등
  • 기타 바인더: 캐시, 메시징(Kafka/Redis), 시스템/프로세스 등

스프링 부트는 Micrometer를 기본 통합하고 있어, 의존성 추가만으로 위 메트릭의 대부분이 자동 계측된다. 또한 MeterRegistry를 통해 커스텀 메트릭을 수집할 수 있다.

(2) Actuator (노출)

Actuator는 스프링 부트 애플리케이션의 운영/진단 엔드포인트(health, info, metrics 등)를 제공하는 모듈이다. Micrometer가 수집한 메트릭을 HTTP 엔드포인트로 노출해 Prometheus가 가져갈 수 있게 한다.

무엇을 설정하나?

  • 엔드포인트 노출 허용
  • management.endpoints.web.exposure.include: health,info,prometheus
  • 기본 경로와 매핑
    • 기본 base-path: /actuator
    • Prometheus 노출 엔드포인트: /actuator/prometheus
    • 필요 시 변경 가능하다.

(3) Prometheus (접근/저장)

Prometheus는 Pull 기반 시계열 모니터링 시스템이다. 지정한 주기마다 대상 엔드포인트를 HTTP GET으로 호출하여 메트릭을 수집하고, 자체 TSDB에 저장한다.

어떻게 접근하나?

  • 정적 타깃
    • prometheus.yml 설정 파일에 직접 타깃을 정의
  • 서비스 디스커버리
    • Kubernetes: kubernetes_sd_configs (+ ServiceMonitor/Pod 어노테이션)
    • Eureka: eureka_sd_configs
    • 파일 기반: file_sd_configs (외부 JSON 갱신)

저장과 라벨

  • 수집된 시계열은 Prometheus 내장 TSDB(WAL → 2시간 블록)로 디스크에 저장
  • 보관 기간은 -storage.tsdb.retention.time(예: 15d)로 설정
  • 자동 라벨:
    • job: job_name 값
    • instance: 타깃 주소(예: ticket-service:9000)
    • 그 외 애플리케이션이 노출한 라벨(예: method, uri, status, application 등)
  • 활용: Grafana가 Prometheus HTTP APIPromQL 쿼리를 보내 시각화/대시보드 구성

 

애플리케이션 모니터링

애플리케이션 레벨 지표(요청 수, 지연 p95/p99, 에러율, 스레드풀/DB 커넥션풀 등)로 어떤 엔드포인트가 느리거나 실패하는지를 확인한다.

서버 리소스 모니터링

OS/호스트 레벨 지표(CPU, 메모리, 디스크 I/O·용량, 네트워크)로 자원 포화 때문에 느린지를 확인한다.

 

두 방식은 서로 보완적이며, 보통 앱 지표로 이상을 감지하고 서버 지표로 자원 병목 여부를 판단한다.

이번 글에서는 애플리케이션 모니터링(Micrometer + Actuator → Prometheus → Grafana)을 실습하고,

추후 배포 환경에서는 Node Exporter서버 리소스 모니터링을 추가할 예정이다.

 

실습

1) 의존성 설정 (+ 최소 Actuator 노출)

모니터링을 공통적으로 적용하기 위해 obs-starter 라는 모듈을 만들고, 필요한 의존성을 추가했다. 멀티 모듈 프로젝트이므로 모니터링이 필요한 서비스에서 해당 모듈만 import 하면 된다.

modules/obs-starter/build.gradle

// Actuator + Micrometer (Prometheus)
api "org.springframework.boot:spring-boot-starter-actuator"
api "io.micrometer:micrometer-registry-prometheus"

/actuator/prometheus 엔드포인트는 기본적으로 비공개 상태이므로, starter 안에서 노출 설정을 함께 해두었다.

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus      # prometheus 추가
  metrics:
    tags:
      application: ${spring.application.name}

2) obs-starter 넣어주기

test-service 모듈에 obs-starter를 추가했다.

test-service/build.gradle

implementation project(":modules:obs-starter")

3) Prometheus 설정

Prometheus에서 test-service의 메트릭을 수집하기 위한 설정을 추가했다.

prometheus/prometheus.yml

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: "test-service"
    metrics_path: /actuator/prometheus
    static_configs:
      - targets: ['host.docker.internal:8080']  # 관리 포트 분리 시 :9000 등으로 변경

Prometheus는 Docker 컨테이너에서 실행 중이고, test-service는 로컬 8080 포트에서 동작하므로 host.docker.internal:8080을 지정했다.

4) Prometheus, Grafana 컨테이너 생성

현재는 Prometheus와 Grafana만 구성했으며, 이후에는 otel-collector, tempo, promtail, loki 등을 추가해 전체 옵저버빌리티 스택을 구축할 예정이다.

docker-compose.yml

version: "3.9"

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    command: ["--config.file=/etc/prometheus/prometheus.yml"]
    ports: ["9090:9090"]
    networks: [obs-net]
    # Linux에서 host.docker.internal을 쓰려면:
    extra_hosts:
      - "host.docker.internal:host-gateway"

  grafana:
    image: grafana/grafana:11.1.0
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_INSTALL_PLUGINS=grafana-piechart-panel
    volumes:
      - ./grafana/provisioning/datasources/datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml:ro
      - grafana-data:/var/lib/grafana
    ports: ["3000:3000"]
    networks: [obs-net]
    depends_on: [prometheus]

networks:
  obs-net:

volumes:
  grafana-data:

5) Grafana 데이터소스 설정 (datasources.yml)

Grafana가 메트릭을 시각화하려면 데이터 소스(Data Source) 를 먼저 등록해야 한다. datasources.yml 파일은 Grafana가 기동될 때 자동으로 불러오는 프로비저닝(provisioning) 설정 파일이며, 여기서 Prometheus를 기본 데이터소스로 지정해주었다.

grafana/provisioning/datasources/datasources.yml

apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: <http://prometheus:9090>
    isDefault: true

6) 테스트 엔드포인트 추가

메트릭 수집을 테스트하기 위해 TestController를 작성했다. MeterRegistry를 통해 커스텀 메트릭도 등록할 수 있다.

@RestController
@RequiredArgsConstructor
@Slf4j
public class TestController {
	private final MeterRegistry meterRegistry;

	@GetMapping("/test")
	public String hello(@RequestParam(defaultValue = "world") String name) {
		// 커스텀 카운터 예시
		meterRegistry.counter("hello.requests", "name", name).increment();

		log.info("hello endpoint called with name={}", name); // 로그에 traceId/spanId 포함(obs-starter의 logback 패턴)
		return "hello " + name;
	}
}

7) 결과화면

Prometheus에서는 아래와 같이 메트릭이 수집되는 것을 확인할 수 있다.

http_server_requests_seconds_count{method="GET", uri="/test"}

해당 지표를 통해 /test API 호출 횟수가 정상적으로 집계되는 것을 확인할 수 있다.

 

위 이미지는 Grafana에서 rate(hello_requests_total[1m]) 쿼리를 사용해 시각화한 그래프이다.

최근 1분 동안의 hello_requests_total 증가량을 초당 요청 수(req/s)로 환산한 값이다.
따라서 단순 누적 호출 횟수가 아닌, 시간 흐름에 따른 요청 발생 속도(트래픽 추이) 를 직관적으로 확인할 수 있다.

예를 들어 초당 2번 호출되면 그래프가 2로 표시되며, 트래픽이 급증하거나 감소하는 시점을 쉽게 파악할 수 있다.

 

위 이미지는 histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) 쿼리를 사용해 그린 차트이다.

  • http_server_requests_seconds_bucket
    Spring Boot Actuator가 기본 제공하는 히스토그램 기반 응답 시간(bucket 단위 분포) 메트릭이다.
  • rate(…[5m])
    최근 5분 동안의 응답 시간 분포를 초당 변화율로 계산해준다.
  • histogram_quantile(0.95, …)
    응답 시간의 95번째 퍼센타일(P95) 값을 구한다.
    즉, 전체 요청 중 95%가 이 값 이하의 응답 시간 안에 처리되었다는 의미다.

즉, 이 그래프는 단순한 평균 응답 시간이 아니라, 사용자가 실제로 체감하는 지연 시간 분포(특히 느린 요청들 포함) 를 더 현실적으로 보여준다.
예를 들어, 평균 응답 시간이 200ms 라도 P95가 1초라면, 일부 사용자는 최대 1초 가까이 기다리고 있다는 뜻이다.

댓글