K8s Prometheus 모니터링이란?
Prometheus는 쿠버네티스 클러스터의 메트릭을 수집하고 알림을 발생시키는 표준 모니터링 시스템입니다. Pull 기반으로 타겟 엔드포인트를 주기적으로 스크래핑하며, PromQL로 강력한 쿼리와 알림 규칙을 작성할 수 있습니다. 이 글에서는 kube-prometheus-stack 배포부터 ServiceMonitor, 커스텀 메트릭, Alertmanager 규칙, Grafana 대시보드 설계까지 실무 운영에 필요한 심화 내용을 다룹니다.
kube-prometheus-stack 배포
Prometheus Operator를 포함한 kube-prometheus-stack Helm 차트가 사실상 표준입니다:
helm repo add prometheus-community
https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus prometheus-community/kube-prometheus-stack
--namespace monitoring
--create-namespace
--set prometheus.prometheusSpec.retention=15d
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi
--set alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.resources.requests.storage=10Gi
--set grafana.adminPassword=mySecretPassword
--set grafana.persistence.enabled=true
--set grafana.persistence.size=5Gi
이 차트 하나로 Prometheus, Alertmanager, Grafana, node-exporter, kube-state-metrics가 모두 배포됩니다.
ServiceMonitor: 애플리케이션 메트릭 수집
Prometheus Operator는 ServiceMonitor CRD로 스크래핑 대상을 선언적으로 관리합니다:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: order-service-monitor
namespace: production
labels:
release: kube-prometheus # Prometheus가 감지하는 라벨
spec:
selector:
matchLabels:
app: order-service
namespaceSelector:
matchNames:
- production
endpoints:
- port: http-metrics # Service의 포트 이름
path: /metrics
interval: 15s
scrapeTimeout: 10s
metricRelabelings:
- sourceLabels: [__name__]
regex: 'go_.*' # Go 런타임 메트릭 제외
action: drop
애플리케이션 Service에 해당 포트가 정의되어 있어야 합니다:
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: production
labels:
app: order-service
spec:
ports:
- name: http
port: 8080
- name: http-metrics # 메트릭 전용 포트
port: 9090
selector:
app: order-service
PodMonitor: Sidecar 없는 Pod 직접 모니터링
Service 없이 Pod를 직접 스크래핑할 때는 PodMonitor를 사용합니다:
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: batch-job-monitor
namespace: production
spec:
selector:
matchLabels:
app: batch-processor
podMetricsEndpoints:
- port: metrics
interval: 30s
PromQL 실전 쿼리
PromQL은 Prometheus의 쿼리 언어입니다. 실무에서 자주 사용하는 패턴들입니다:
# 1. HTTP 요청 에러율 (5분 윈도우)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) * 100
# 2. P99 응답 시간
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)
# 3. Pod CPU 사용률 (%)
sum(rate(container_cpu_usage_seconds_total{
namespace="production",
container!="POD",
container!=""
}[5m])) by (pod)
/ sum(kube_pod_container_resource_requests{
namespace="production",
resource="cpu"
}) by (pod) * 100
# 4. Pod 메모리 사용률 (%)
sum(container_memory_working_set_bytes{
namespace="production",
container!="POD"
}) by (pod)
/ sum(kube_pod_container_resource_limits{
namespace="production",
resource="memory"
}) by (pod) * 100
# 5. Pod 재시작 횟수 (최근 1시간)
increase(kube_pod_container_status_restarts_total{
namespace="production"
}[1h]) > 0
PrometheusRule: 알림 규칙 설정
Prometheus Operator에서 알림 규칙은 PrometheusRule CRD로 관리합니다:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: order-service-alerts
namespace: production
labels:
release: kube-prometheus
spec:
groups:
- name: order-service.rules
rules:
# 에러율 5% 이상 5분 지속
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{
service="order-service", status=~"5.."
}[5m]))
/ sum(rate(http_requests_total{
service="order-service"
}[5m])) > 0.05
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "주문 서비스 에러율 {{ $value | humanizePercentage }}"
description: "5분간 에러율이 5%를 초과했습니다."
runbook_url: "https://wiki.example.com/runbook/high-error-rate"
# P99 응답 시간 2초 초과
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{
service="order-service"
}[5m])) by (le)
) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "주문 서비스 P99 지연 {{ $value | humanizeDuration }}"
# Pod 메모리 90% 이상 사용
- alert: PodMemoryHigh
expr: |
container_memory_working_set_bytes{
namespace="production",
container="order-service"
}
/ container_spec_memory_limit_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} 메모리 {{ $value | humanizePercentage }}"
Alertmanager: 알림 라우팅
Alertmanager는 알림을 Slack, PagerDuty, Webhook 등으로 전달합니다:
# alertmanager-config (values.yaml)
alertmanager:
config:
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/xxx'
route:
receiver: 'default-slack'
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: 'critical-pagerduty'
matchers:
- severity = critical
repeat_interval: 1h
- receiver: 'warning-slack'
matchers:
- severity = warning
receivers:
- name: 'default-slack'
slack_configs:
- channel: '#alerts'
title: '{{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
send_resolved: true
- name: 'critical-pagerduty'
pagerduty_configs:
- service_key: 'your-pagerduty-key'
severity: 'critical'
- name: 'warning-slack'
slack_configs:
- channel: '#alerts-warning'
send_resolved: true
inhibit_rules:
- source_matchers: [severity = critical]
target_matchers: [severity = warning]
equal: [alertname, namespace]
inhibit_rules로 critical 알림이 발생하면 같은 alertname의 warning 알림을 억제합니다. 이는 K8s HPA 오토스케일링의 커스텀 메트릭과 연동해 자동 확장 판단에도 활용됩니다.
커스텀 메트릭 노출 (Spring Boot)
Spring Boot 애플리케이션에서 Prometheus 메트릭을 노출하는 방법입니다:
// build.gradle
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// application.yml
management:
endpoints:
web:
exposure:
include: prometheus, health
metrics:
tags:
application: order-service
environment: production
// 커스텀 비즈니스 메트릭
@Component
@RequiredArgsConstructor
public class OrderMetrics {
private final MeterRegistry registry;
public void recordOrderCreated(String type) {
registry.counter("orders_created_total",
"type", type).increment();
}
public void recordOrderDuration(long ms) {
registry.timer("order_processing_duration")
.record(Duration.ofMillis(ms));
}
public void setActiveOrders(int count) {
registry.gauge("orders_active_count",
new AtomicInteger(count));
}
}
Grafana 대시보드 설계 원칙
효과적인 대시보드는 USE/RED 메서드를 따릅니다:
| 메서드 | 대상 | 메트릭 |
|---|---|---|
| USE (인프라) | CPU, 메모리, 디스크, 네트워크 | Utilization, Saturation, Errors |
| RED (서비스) | API 엔드포인트 | Rate, Errors, Duration |
# Grafana 대시보드 JSON 자동 프로비저닝 (ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1" # Grafana sidecar가 자동 감지
data:
order-service.json: |
{
"dashboard": {
"title": "Order Service",
"panels": [
{
"title": "Request Rate",
"targets": [{
"expr": "sum(rate(http_requests_total{service="order-service"}[5m])) by (status)"
}]
}
]
}
}
Recording Rules: 쿼리 성능 최적화
복잡한 PromQL을 미리 계산해두는 Recording Rule입니다:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: recording-rules
namespace: monitoring
spec:
groups:
- name: http.rules
interval: 30s
rules:
- record: service:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (service)
- record: service:http_errors:ratio5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/ sum(rate(http_requests_total[5m])) by (service)
- record: service:http_latency:p99_5m
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)
Recording Rule로 미리 집계하면 대시보드와 알림 규칙에서 service:http_errors:ratio5m처럼 간단히 참조할 수 있어 쿼리 부하가 줄어듭니다.
운영 베스트 프랙티스
- 리텐션 설계: 15~30일 원본 보존, 장기 저장은 Thanos/Mimir로 다운샘플링
- 라벨 카디널리티 관리: 고유값이 많은 라벨(userId, requestId)은 절대 메트릭에 넣지 마세요
- 스크래핑 간격: 인프라 15s, 비즈니스 30s가 적절합니다
- 알림 피로도 관리:
for절로 최소 5분 지속 시에만 발화,inhibit_rules로 중복 억제 - Grafana 대시보드: 팀별 서비스별로 분리하고, K8s RBAC과 연동해 접근 제어
- 스토리지 모니터링: Prometheus 자체의 디스크 사용량도 알림 설정 필수
마무리
Prometheus는 쿠버네티스 모니터링의 사실상 표준입니다. kube-prometheus-stack으로 빠르게 구축하고, ServiceMonitor로 애플리케이션을 선언적으로 등록하며, PromQL과 PrometheusRule로 정교한 알림을 설정할 수 있습니다. USE/RED 메서드에 따른 대시보드 설계와 Recording Rule을 활용하면 대규모 클러스터에서도 안정적인 모니터링 체계를 운영할 수 있습니다.