Prometheus + Grafana 모니터링 완전 가이드 — Docker Compose부터 Kubernetes까지
서비스가 갑자기 느려졌는데 원인을 알 수 없었던 경험이 있으신가요? 로그를 뒤지고, 서버에 SSH로 접속해 top 명령어를 치고, 결국 "재시작해보자"로 끝나는 악순환은 모니터링 체계가 없을 때 흔히 겪는 일입니다. 현대 서비스 운영에서 무엇이 어떻게 동작하고 있는지를 실시간으로 파악하는 능력은 선택이 아닌 필수가 되었습니다.
Prometheus와 Grafana는 이 문제를 해결하는 데 있어 사실상 업계 표준 조합입니다. Prometheus는 메트릭을 수집·저장·분석하고, Grafana는 그 데이터를 사람이 읽을 수 있는 대시보드로 변환합니다. CNCF(Cloud Native Computing Foundation) Graduated 프로젝트로서 쿠버네티스 생태계와 깊이 통합되어 있으며, Gartner Observability Platforms 부문에서도 꾸준히 인정받아온 성숙한 스택입니다.
이 글은 처음 모니터링을 도입하는 백엔드·DevOps 개발자를 위해 두 도구의 동작 원리부터 Docker Compose 로컬 환경 구성, Node.js 커스텀 메트릭 계측, Kubernetes 프로덕션 배포까지 단계별로 정리했습니다. 이 글을 읽고 나면 로컬 환경에서 메트릭 수집부터 Grafana 시각화까지 직접 구축할 수 있습니다. Kubernetes 경험자라면 이후 섹션에서 프로덕션 수준의 설정 방법도 확인해볼 수 있습니다.
TL;DR — 핵심 요약
- Prometheus:
/metrics엔드포인트를 주기적으로 스크래핑(Pull 방식)하는 시계열 DB + PromQL 쿼리 엔진- Grafana: Prometheus 등 100개 이상의 데이터 소스를 연결하는 시각화 플랫폼 (데이터를 직접 수집하지 않음)
- 로컬 시작:
docker compose up -d한 줄로 즉시 구동 가능- 핵심 주의: 레이블에 고유값(
user_id,request_id)을 넣으면 메모리 폭발 위험- PromQL 실행 위치: Prometheus UI(
localhost:9090) 또는 Grafana Explore 탭
핵심 개념
Prometheus: Pull 방식의 메트릭 수집 엔진
Prometheus는 동작 방식의 핵심이 Pull 모델에 있습니다. 모니터링 대상 서비스가 /metrics HTTP 엔드포인트를 노출하면, Prometheus가 주기적으로 이 엔드포인트를 "스크래핑(scraping)"하여 데이터를 가져갑니다.
StatsD처럼 서비스가 모니터링 서버로 데이터를 밀어 넣는 Push 방식과 달리, Pull 방식에서는 Prometheus가 각 서비스를 polling합니다. 덕분에 서비스가 응답하지 않으면 즉시 이상 감지가 가능하고, 모니터링 설정이 Prometheus 측에 중앙 집중되어 관리가 용이합니다.
# prometheus.yml — 기본 스크래핑 설정 예시
global:
scrape_interval: 15s # 15초마다 메트릭 수집
scrape_configs:
- job_name: 'my-app'
static_configs:
- targets: ['localhost:8080'] # /metrics 엔드포인트를 노출하는 서비스수집된 데이터는 내장 시계열 DB에 저장되며, **PromQL(Prometheus Query Language)**로 조회·집계할 수 있습니다. PromQL 쿼리는 http://localhost:9090의 Prometheus UI 또는 Grafana의 Explore 탭에서 실행해볼 수 있습니다. 처음 시작한다면 아래 세 가지 쿼리 중 하나를 Prometheus UI에 그대로 붙여 넣어 결과를 확인해보시는 것을 권장합니다.
# 지난 5분간 HTTP 요청의 초당 평균 처리 수
# (아래 Node.js 예시에서 정의한 orders_total 같은 Counter 메트릭에 적용)
rate(orders_total[5m])
# 서비스별 99번째 백분위 레이턴시
# (http_request_duration_seconds 히스토그램 메트릭에 적용)
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))
# 에러율 (5xx 응답 비율)
sum(rate(orders_total{status="error"}[5m])) / sum(rate(orders_total[5m]))PromQL 핵심 함수 빠른 참조
rate(): 초당 변화율 — Counter 메트릭에는 반드시 감싸서 사용increase(): 특정 기간 동안의 총 증가량histogram_quantile(): 히스토그램에서 백분위 계산sum() by (label): 레이블 기준으로 집계
Grafana: 시각화 레이어의 역할
Grafana는 데이터를 직접 수집하지 않습니다. Prometheus, Loki, Elasticsearch 등 100개 이상의 데이터 소스에 연결해 메트릭·로그·트레이스를 하나의 대시보드에서 시각화하는 역할에 집중합니다.
메트릭 → Prometheus ──────────────────────────┐
로그 → Loki ────────────────────────────────┤ Grafana (시각화)
트레이스 → Tempo ───────────────────────────────┤
프로파일 → Pyroscope ──────────────────────────┘
↑
Grafana Alloy (통합 텔레메트리 컬렉터, OpenTelemetry 호환)Grafana Alloy란? 2025년 EOL이 된 Grafana Agent를 대체하는 통합 텔레메트리 컬렉터입니다. OpenTelemetry, Prometheus, Loki, Pyroscope 파이프라인을 단일 컬렉터로 수집·변환할 수 있으며, 에이전트 방식으로 각 서버에 배포해 사용합니다. 이 글의 Docker Compose 예시에서는 간결성을 위해 직접 스크래핑 방식을 사용합니다.
Grafana와 Prometheus의 관계는 "데이터베이스와 시각화 도구"의 관계와 같습니다. Prometheus 없이도 Grafana는 동작하고, Grafana 없이도 Prometheus는 동작합니다. 하지만 두 도구가 함께할 때 강력한 모니터링 환경이 완성됩니다.
메트릭의 4가지 유형 이해하기
Prometheus에서 다루는 메트릭은 네 가지 유형이 있습니다. 어떤 유형을 쓸지 잘못 선택하면 데이터가 왜곡될 수 있어 이해가 필요합니다.
| 유형 | 특징 | 대표 사용 예시 |
|---|---|---|
| Counter | 단조 증가, 재시작 시 0으로 리셋 | HTTP 요청 총수, 에러 발생 횟수 |
| Gauge | 증감 자유, 현재 상태값 | CPU 사용률, 현재 연결 수, 메모리 사용량 |
| Histogram | 버킷별 분포 + 합계 + 카운트 | 응답 시간 분포, 요청 크기 분포 |
| Summary | 클라이언트 측 백분위 계산 | SLA 기반 레이턴시 측정 (사용 빈도 낮음) |
실전 적용
로컬 개발 환경 구성 (Docker Compose)
개발 환경에서 Prometheus + Grafana 스택을 가장 빠르게 구동하는 방법입니다.
먼저 프로젝트 루트에 .env 파일을 만들어 비밀번호를 분리합니다.
# .env
GRAFANA_PASSWORD=여기에_안전한_비밀번호_입력# docker-compose.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:v3.1.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=15d' # 15일 보존
grafana:
image: grafana/grafana:11.4.0
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- prometheus
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
network_mode: host # 네트워크 통계 등 일부 메트릭을 정확히 수집하려면 필요 (Linux 환경)
pid: host # 프로세스 정보 수집에 필요 (Linux 환경)
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
volumes:
grafana-data:macOS 사용자 참고:
network_mode: host와pid: host는 Linux 전용 옵션으로, Docker Desktop on macOS에서는 동작하지 않습니다. macOS 로컬 개발 환경에서는 두 옵션을 제거해도 기본 메트릭 수집은 가능하며, 네트워크 통계 일부가 누락될 수 있습니다.
# prometheus.yml — node-exporter 스크래핑 포함
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']| 구성 요소 | 역할 | 접속 주소 |
|---|---|---|
| Prometheus | 메트릭 수집 및 저장 | http://localhost:9090 |
| Grafana | 대시보드 시각화 | http://localhost:3000 |
| node-exporter | 호스트 시스템 메트릭 노출 | http://localhost:9100/metrics |
docker compose up -d 실행 후, Grafana에 접속해 Prometheus를 데이터 소스로 추가(URL: http://prometheus:9090)하고, Grafana 공식 대시보드 ID 1860을 임포트하면 즉시 호스트 모니터링 대시보드를 확인할 수 있습니다.
Node.js 애플리케이션 계측
서비스의 비즈니스 메트릭을 직접 정의하고 Prometheus로 수집하는 예시입니다. 먼저 클라이언트 라이브러리를 설치합니다.
pnpm add prom-client// metrics.ts — prom-client 라이브러리 사용
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
export const registry = new Registry();
// 주문 처리 카운터
export const ordersTotal = new Counter({
name: 'orders_total',
help: '처리된 총 주문 수',
labelNames: ['status', 'payment_method'],
registers: [registry],
});
// API 레이턴시 히스토그램
// buckets 값은 웹 API의 일반적인 응답 시간 분포(10ms~5s)를 커버하도록 설정
// 서비스 SLA에 맞게 임계값 주변 구간을 촘촘히 조정하는 것을 권장
export const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP 요청 처리 시간 (초)',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 2, 5],
registers: [registry],
});
// 현재 활성 사용자 수
export const activeUsers = new Gauge({
name: 'active_users_current',
help: '현재 세션이 활성화된 사용자 수',
registers: [registry],
});// app.ts — Express 미들웨어 및 /metrics 엔드포인트
import express from 'express';
import { registry, httpRequestDuration, ordersTotal } from './metrics';
const app = express();
// 모든 요청의 레이턴시 측정 미들웨어
// res.on('finish') 안에서 req.route.path를 참조해야
// 라우트 매칭 완료 후 정확한 경로가 기록됩니다.
// 미들웨어 실행 시점에는 req.route가 undefined이므로
// startTimer 시점에 route 레이블을 넣으면 카디널리티 폭발이 발생합니다.
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer({ method: req.method });
res.on('finish', () => {
end({
route: req.route?.path ?? 'unknown',
status_code: res.statusCode,
});
});
next();
});
// Prometheus 스크래핑 엔드포인트
app.get('/metrics', async (req, res) => {
res.set('Content-Type', registry.contentType);
res.send(await registry.metrics());
});
// 주문 처리 라우트 예시
app.post('/orders', async (req, res) => {
// ... 주문 처리 로직 ...
ordersTotal.inc({ status: 'success', payment_method: 'card' });
res.json({ success: true });
});주의:
req.route?.path ?? req.path를 미들웨어 실행 시점에 route 레이블로 바로 사용하면, 라우트 매칭 전이라/api/users/123처럼 개별 요청 경로가 그대로 기록되어 카디널리티 폭발을 일으킵니다. 위 예시처럼res.on('finish')콜백 안에서req.route.path를 참조하거나,express-prom-bundle라이브러리 사용을 고려해볼 수 있습니다.
Kubernetes 프로덕션 환경 구성 (심화)
사전 조건: Kubernetes 클러스터와 Helm 3.x가 설치된 환경이 필요합니다. 로컬에서는 kind 또는 minikube로 클러스터를 구성할 수 있습니다.
쿠버네티스 환경에서는 Helm 차트 하나로 전체 모니터링 스택을 구성할 수 있습니다. kube-prometheus-stack은 Prometheus, Grafana, Alertmanager, node-exporter, kube-state-metrics를 한 번에 설치합니다.
# Helm 레포지토리 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# kube-prometheus-stack 설치
helm install kube-prometheus-stack \
prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=your-secure-password \
--set prometheus.prometheusSpec.retention=30d# 설치 상태 확인 — 모든 파드가 Running 상태인지 확인
kubectl get pods -n monitoring
# Grafana 대시보드 로컬 포트포워딩으로 접속
kubectl port-forward svc/kube-prometheus-stack-grafana 3000:80 -n monitoring이 명령어만으로 노드 CPU·메모리, 파드 상태, 네임스페이스별 리소스 사용량 등 쿠버네티스 운영에 필요한 기본 대시보드가 자동으로 설치됩니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Pull 기반 수집 | 서비스 다운 시 즉시 감지 가능. Prometheus가 각 서비스를 polling하므로 응답 없으면 알람 발생 |
| 강력한 쿼리 언어 | PromQL로 복잡한 집계·필터링·비율 계산이 가능. 레이블 기반 다차원 데이터 분석 지원 |
| 풍부한 Exporter 생태계 | MySQL, Redis, Nginx, PostgreSQL 등 주요 미들웨어용 공식 Exporter 제공 |
| 범용 시각화 | Grafana 하나로 Prometheus, 로그(Loki), 트레이스(Tempo)를 통합 대시보드에서 확인 가능 |
| 클라우드 네이티브 표준 | CNCF Graduated 프로젝트. 쿠버네티스 생태계와 네이티브 통합 |
| 완전 관리형 옵션 | Grafana Cloud를 사용하면 인프라 운영 없이 바로 시작 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 단기 보존 한계 | 기본 저장소는 수개월~수년 보존에 부적합 | Thanos* 또는 Grafana Mimir**로 장기 스토리지 분리 |
| HA 구성 복잡 | Prometheus 자체는 단일 노드. 고가용성 구성 시 중복 스크래핑 발생 | Thanos Receive 또는 Mimir의 분산 인제스트 레이어 활용 |
| 카디널리티 폭발 | 레이블 조합 수가 급증하면 메모리·스토리지 사용량 폭등 | 레이블 설계 단계에서 user_id, request_id 등 고카디널리티 레이블 제외 |
| 보안 기본값 취약 | /metrics 엔드포인트에 기본적으로 인증 없음 |
네트워크 격리(내부망 전용) 또는 TLS + Basic Auth 설정 |
| PromQL 학습 곡선 | rate, histogram_quantile 등 처음엔 직관적이지 않음 |
Grafana의 쿼리 빌더 UI에서 시작 후 점진적으로 PromQL 학습 |
* Thanos: Prometheus 클러스터 간 글로벌 쿼리 레이어와 장기 오브젝트 스토리지(S3 등) 연동을 제공하는 오픈소스 프로젝트
** Grafana Mimir: Prometheus의 원격 쓰기(remote_write) 백엔드로, 수평 확장과 장기 보존을 지원. 2025년 출시된 Mimir 3.0은 새 쿼리 엔진으로 메모리 사용량을 최대 92% 절감했습니다.
실무에서 가장 흔한 실수
-
Counter에
rate()없이 직접 그래프를 그리는 경우 — Counter는 누적값이므로 항상rate()또는increase()로 감싸서 시간당 변화율로 보는 것이 맞습니다. 누적 그래프는 대부분 의미 없는 우상향 직선이 됩니다. -
레이블에 고유값을 넣는 경우 —
{user_id="12345"},{request_id="abc-xyz"}처럼 요청마다 달라지는 값을 레이블로 사용하면 카디널리티 폭발로 Prometheus가 수 시간 내에 메모리 부족 상태에 빠질 수 있습니다. 레이블은{status="success"},{region="ap-northeast-2"}처럼 "상태의 분류"에만 사용하는 것을 권장합니다. -
스크래핑 간격을 너무 짧게 설정하는 경우 —
scrape_interval: 1s로 설정하면 타깃이 많을 때 Prometheus 서버에 과부하가 생깁니다. 대부분의 운영 환경에서는 15~30초가 적절하며, 정밀한 SLA 측정이 필요한 경우에만 예외적으로 단축하는 것을 권장합니다.
카디널리티(Cardinality)란? 레이블의 유니크한 값 조합 수를 의미합니다. 예를 들어
user_id레이블에 100만 명의 사용자 ID가 들어가면 시계열 데이터가 100만 개 생성됩니다. Prometheus는 이 모든 시계열을 메모리에 유지하므로, 고카디널리티 레이블은 OOM(Out of Memory) 의 주요 원인이 됩니다.
마치며
Prometheus와 Grafana의 조합은 단순한 서버 모니터링 도구를 넘어, 서비스의 상태를 실시간 비즈니스 인텔리전스로 전환해주는 현대 엔지니어링의 핵심 인프라입니다.
복잡하게 느껴질 수 있지만, 처음에는 작은 단계부터 시작해볼 수 있습니다. 지금 바로 시작할 수 있는 3단계를 안내합니다.
-
로컬 스택 구동: 위에서 제공한
docker-compose.yml로docker compose up -d를 실행해볼 수 있습니다. 스택이 올라온 뒤 Prometheus와 Grafana가 연결되지 않는다면, 먼저docker compose ps로 컨테이너 상태를 확인하고 Grafana 데이터 소스 URL이http://prometheus:9090(컨테이너 이름 기준)으로 설정되어 있는지 살펴보시면 좋습니다. -
애플리케이션 계측: 현재 개발 중인 서비스에
prom-client(Node.js),prometheus_client(Python),micrometer(Java/Spring) 등 공식 클라이언트 라이브러리를 추가해 가장 중요한 메트릭 하나(예: API 요청 수)를/metrics로 노출해볼 수 있습니다. 레이블 설계 전에 카디널리티 폭발 주의사항을 먼저 읽어보시는 것을 권장합니다. -
첫 알람 설정: Grafana의 Unified Alerting 기능을 사용해 에러율이 1%를 초과하면 Slack이나 이메일로 알림을 보내는 규칙을 설정해볼 수 있습니다. 알람을 받는 순간 모니터링이 단순한 대시보드에서 서비스를 지키는 도구로 탈바꿈하는 것을 실감할 수 있습니다.
다음 글: Grafana Loki와 Tempo로 로그·트레이스까지 연결해 진정한 Full-Stack Observability를 구축하는 방법을 다룰 예정입니다.