
저번글(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 설치가 필요한 경우가 많음 (스프링은 자체 엔드포인트 노출만으로도 수집 가능)
Prometheus 는 Pull 모델의 표준이라 할 만큼 널리 쓰이며, 장점은 아래와 같다.
- 스프링과의 궁합: Micrometer + Actuator 가 Prometheus 포맷으로 쉽게 노출
- 생태계: 수많은 Exporter(OS/미들웨어), Grafana, Alertmanager 연동
- 운영 단순성: 단일 바이너리/간단한 설정으로 바로 수집·질의 가능
- 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 API에 PromQL 쿼리를 보내 시각화/대시보드 구성
애플리케이션 모니터링
애플리케이션 레벨 지표(요청 수, 지연 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초 가까이 기다리고 있다는 뜻이다.
'DevOps > Monitoring, Observability' 카테고리의 다른 글
| #2 Loki와 Promtail을 이용해 Grafana에서 로그 데이터 시각화하기 (0) | 2025.10.13 |
|---|---|
| Monitoring(모니터링) vs Observability(옵저버빌리티) (2) | 2025.07.28 |
댓글