서비스가 다운되고 나서야 알아채는 것은 이미 늦은 것이다. 프로덕션 환경에서 모니터링은 선택이 아니라 생존 전략이다. 이 글에서는 오픈소스 모니터링 스택의 사실상 표준인 Prometheus와 Grafana를 조합해 실전 대시보드와 알럿까지 구성하는 방법을 단계별로 다룬다. 단순한 설치 가이드가 아니라, 실무에서 마주치는 트레이드오프와 운영 노하우까지 포함했다.
Prometheus 아키텍처와 Exporter 개념
Prometheus는 풀(pull) 기반 메트릭 수집 시스템이다. 모니터링 대상이 메트릭을 밀어 넣는 것이 아니라, Prometheus 서버가 주기적으로 각 대상의 HTTP 엔드포인트(/metrics)를 긁어온다. 이 방식의 장점은 중앙 집중 제어가 쉽고, 수집 대상이 죽었을 때 즉시 감지할 수 있다는 것이다. 단점은 방화벽이나 NAT 뒤에 있는 대상을 긁기 어렵다는 점인데, 이때는 Pushgateway를 중간에 두거나 원격 쓰기(remote_write) 방식을 사용한다.
Exporter는 Prometheus가 직접 이해하지 못하는 시스템(MySQL, Redis, 하드웨어 등)의 메트릭을 Prometheus 형식으로 변환·노출해주는 어댑터다. 대표적인 exporter는 아래와 같다.
| Exporter | 수집 대상 | 기본 포트 | 주요 메트릭 |
|---|---|---|---|
| node_exporter | Linux/macOS 호스트 | 9100 | CPU, 메모리, 디스크, 네트워크 |
| mysqld_exporter | MySQL / MariaDB | 9104 | QPS, 슬로우 쿼리, 커넥션 수 |
| redis_exporter | Redis | 9121 | hit rate, 메모리, keyspace |
| blackbox_exporter | HTTP/TCP/ICMP 엔드포인트 | 9115 | 응답시간, 상태코드, SSL 만료 |
| kube-state-metrics | Kubernetes 오브젝트 | 8080 | Pod 상태, Deployment replicas |
prometheus.yml Scrape 설정
Prometheus의 핵심 설정 파일은 prometheus.yml이다. 전역 스크랩 간격, 알럿 규칙 파일 경로, 그리고 각 scrape_config를 담는다. 아래는 node_exporter와 애플리케이션 서버를 함께 수집하는 현실적인 예시다.
global:
scrape_interval: 15s # 모든 job의 기본 수집 주기
evaluation_interval: 15s # 알럿 규칙 평가 주기
external_labels:
env: production
region: ap-northeast-2
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
rule_files:
- "/etc/prometheus/rules/*.yml"
scrape_configs:
# Prometheus 자체 모니터링
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# 호스트 메트릭 (node_exporter)
- job_name: 'node'
scrape_interval: 10s # job 레벨에서 오버라이드 가능
static_configs:
- targets:
- 'web-01:9100'
- 'web-02:9100'
- 'db-01:9100'
relabel_configs:
- source_labels: [__address__]
regex: '([^:]+):.*'
target_label: instance
replacement: '$1'
# 애플리케이션 서버 (스프링 부트 Actuator / 미크로미터)
- job_name: 'spring-app'
metrics_path: '/actuator/prometheus'
scrape_interval: 15s
kubernetes_sd_configs: # 쿠버네티스 서비스 디스커버리
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: 'true'
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
# 외부 URL 헬스체크 (blackbox_exporter)
- job_name: 'blackbox-http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- 'https://api.example.com/health'
- 'https://www.example.com'
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 'blackbox-exporter:9115'
실무 팁: scrape_interval을 너무 짧게 설정하면 Prometheus 서버 부하와 스토리지 사용량이 급증한다. 일반적으로 15~30초가 적정하며, 알럿 민감도가 중요한 job만 10초로 낮추는 것을 권장한다. 쿠버네티스 환경에서는 static_configs 대신 kubernetes_sd_configs를 사용해 파드가 추가/삭제될 때 자동으로 수집 대상이 갱신되도록 한다.
PromQL 핵심 쿼리 패턴
Prometheus Query Language(PromQL)는 시계열 데이터를 다루는 강력한 언어다. 가장 많이 쓰는 패턴 몇 가지를 실전 예시로 살펴본다.
rate와 irate의 차이: rate()는 지정 구간 전체의 평균 초당 증가율을 반환하고, irate()는 마지막 두 샘플만 사용해 순간 변화율을 계산한다. 스파이크 감지에는 irate가 유리하지만, 알럿 규칙에는 rate가 더 안정적이다.
# CPU 사용률 (instance별)
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# HTTP 요청 처리량 (초당 요청 수, status 200만)
sum(rate(http_requests_total{status="200"}[2m])) by (service)
# 99번째 백분위 응답시간 (histogram_quantile)
histogram_quantile(
0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)
# 에러율 (전체 요청 대비 5xx 비율)
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
# 메모리 사용률
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# 디스크 사용률 (루트 파티션)
(1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100
# 인스턴스 다운 감지 (up 메트릭)
up == 0
histogram_quantile 사용 시 주의사항: 이 함수는 버킷 경계에서만 정확하다. 예를 들어 le=”0.5″ 버킷에 실제 응답시간의 90%가 몰려 있으면 p99가 부정확하게 계산될 수 있다. 따라서 애플리케이션 계측 시 버킷 경계를 예상 응답시간 분포에 맞게 세밀하게 잡는 것이 중요하다(예: 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0 초).
Grafana 대시보드 구성과 알럿
Grafana는 Prometheus 데이터를 시각화하는 데 최적화된 오픈소스 플랫폼이다. 데이터소스 추가 후 대시보드를 구성하는 기본 흐름은 다음과 같다.
- 데이터소스 추가: Configuration → Data Sources → Prometheus → URL에
http://prometheus:9090입력 - 대시보드 임포트: node_exporter는 Grafana.com 대시보드 ID 1860을 임포트하면 즉시 쓸 수 있는 완성도 높은 대시보드를 얻을 수 있다.
- 패널 커스터마이징: 각 패널의 쿼리, 시각화 유형(Time series, Gauge, Stat), 임계값 색상을 조정한다.
알럿 규칙은 Prometheus 측 rules/*.yml에서 정의하고, 알럿 발송은 Alertmanager가 담당한다. 아래는 실전에서 자주 쓰는 알럿 규칙 예시다.
groups:
- name: node.rules
rules:
# CPU 5분 평균 사용률 90% 초과 → 1분 지속 시 알럿
- alert: HighCPUUsage
expr: |
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 1m
labels:
severity: warning
annotations:
summary: "High CPU on {{ $labels.instance }}"
description: "CPU usage is {{ $value | printf "%.1f" }}% for more than 1 minute."
# 메모리 사용률 95% 초과
- alert: HighMemoryUsage
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95
for: 2m
labels:
severity: critical
annotations:
summary: "Memory critical on {{ $labels.instance }}"
# 인스턴스 다운
- alert: InstanceDown
expr: up == 0
for: 30s
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} is down"
- name: app.rules
rules:
# p99 응답시간 1초 초과
- alert: HighLatencyP99
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
) > 1.0
for: 3m
labels:
severity: warning
# 에러율 1% 초과
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service) > 0.01
for: 2m
labels:
severity: critical
Grafana 자체 알럿(Grafana Alerting)도 9.x 버전부터 크게 개선돼 사용할 수 있지만, 규모가 크거나 여러 Prometheus 인스턴스를 운영한다면 Prometheus 측 알럿 규칙 + Alertmanager 조합이 관리 측면에서 일관성이 높다.
실전 운영 팁과 트레이드오프
스토리지 용량 계획: Prometheus의 기본 로컬 스토리지 보존 기간은 15일이며, 메트릭 수·스크랩 간격·레이블 카디널리티에 따라 용량이 급격히 달라진다. 경험적으로 스크랩 간격 15초, 약 1,000개 시계열 기준 하루 약 50~100MB를 소비한다. 장기 보존이 필요하다면 Thanos나 Cortex 같은 원격 스토리지를 연동하는 것이 현실적이다.
레이블 카디널리티 폭발 주의: 레이블에 user_id, request_id 같은 고유값을 붙이면 시계열 수가 폭발적으로 늘어나 Prometheus가 OOM으로 죽을 수 있다. 레이블은 집계 가능한 차원(service, region, status, method 등)만 사용해야 한다.
Grafana 대시보드 as Code: 팀 규모가 커지면 대시보드를 JSON으로 버전 관리(Git)하거나, Grafonnet(Jsonnet 기반 라이브러리) 또는 Terraform Grafana provider로 선언적 관리하는 것을 권장한다. 수동 GUI 편집은 “누가 언제 뭘 바꿨는지” 추적이 불가능하다.
mixin과 SLO 모니터링: 최근에는 SLI/SLO 기반 모니터링이 주목받고 있다. sloth 같은 도구를 사용하면 에러 버짓 기반 번인율(burn rate) 알럿을 자동 생성할 수 있다. 단순 임계값 알럿보다 훨씬 낮은 노이즈로 실제 사용자 영향이 있을 때만 울리는 고품질 알럿을 구성할 수 있다.
마무리
Prometheus + Grafana 스택은 학습 곡선이 있지만, PromQL을 익히고 나면 어떤 모니터링 질문에도 즉각 답을 낼 수 있는 강력한 도구다. 처음에는 node_exporter와 기본 대시보드로 시작해 팀이 실제로 보는 지표를 파악하고, 점진적으로 애플리케이션 계측과 SLO 기반 알럿으로 성숙도를 높여 나가는 접근이 현실적이다. “알럿이 울릴 때마다 대응하는” 반응형 운영에서 “에러 버짓 소진 속도를 보며 선제적으로 대응하는” 운영으로 전환하는 것이 최종 목표다.
자주 묻는 질문
Q. Prometheus와 InfluxDB 중 어떤 것을 선택해야 하나요?
A. Prometheus는 풀 기반 수집, 레이블 기반 다차원 집계, PromQL이 강점이며 쿠버네티스 생태계와 통합이 탁월합니다. InfluxDB는 푸시 기반, 고빈도 데이터(IoT, 금융 tick 데이터), 태그 + 필드 모델이 필요할 때 유리합니다. 마이크로서비스/k8s 환경이라면 Prometheus가 사실상 표준입니다.
Q. Grafana 알럿과 Alertmanager 중 어떤 것을 써야 하나요?
A. 단일 팀·소규모 환경이라면 Grafana Alerting(9.x 이상)이 설정이 간단하고 UI가 편리합니다. 여러 Prometheus 인스턴스, 복잡한 라우팅(팀별 슬랙 채널 분리, 근무 시간 외 PagerDuty 에스컬레이션 등)이 필요하다면 Alertmanager의 routing tree와 inhibition 규칙이 훨씬 강력합니다.
Q. 스크랩 간격을 1초로 설정하면 안 되나요?
A. 기술적으로는 가능하지만 권장하지 않습니다. 스크랩 간격이 짧을수록 스토리지 사용량과 Prometheus 서버 CPU 부하가 선형 이상으로 증가합니다. 또한 1초 단위 정밀도가 필요한 지표라면 메트릭 기반 모니터링보다 분산 추적(Jaeger, Tempo)이 더 적합한 도구입니다.