📊
모니터링 & 관찰 가능성 가이드

Prometheus 완전 가이드

👥 방문자 수

Prometheus + Grafana로 서버와 애플리케이션을 모니터링하세요. 메트릭 수집, PromQL, 알림(AlertManager), Kubernetes 통합까지 완전 가이드.

Prometheus Grafana PromQL AlertManager Kubernetes

1. 관찰 가능성(Observability)이란?

관찰 가능성은 시스템의 외부 출력만으로 내부 상태를 이해할 수 있는 능력입니다. 세 가지 핵심 신호(Three Pillars of Observability)로 구성됩니다.

신호설명도구특징
메트릭 (Metrics)시간에 따른 수치 데이터 (CPU, 요청 수, 지연 시간)Prometheus, Grafana집계에 강함, 저장 비용 낮음
로그 (Logs)특정 이벤트의 상세 기록Loki, ELK Stack디버깅에 강함, 저장 비용 높음
트레이스 (Traces)분산 시스템의 요청 흐름 추적Tempo, Jaeger마이크로서비스 병목 분석
구분모니터링 (Monitoring)관찰 가능성 (Observability)
질문"무언가 잘못됐나?""왜 잘못됐나?"
접근사전 정의된 임계값 알림임시 조회(ad-hoc) 분석
알 수 있는 것알려진 알 수 없는 것알려지지 않은 알 수 없는 것

2. Prometheus란?

Prometheus는 오픈소스 시스템 모니터링 및 알림 도구입니다. CNCF(Cloud Native Computing Foundation) 졸업 프로젝트로, Kubernetes 생태계의 표준 모니터링 솔루션입니다.

Pull 기반 메트릭 수집

Prometheus는 에이전트가 서버에 데이터를 보내는 Push 방식이 아닌, Prometheus 서버가 주기적으로 타깃에서 데이터를 가져오는 Pull 방식을 사용합니다.

># Prometheus 데이터 모델: 시계열 (Time Series)
# 형식: metric_name{label1="value1", label2="value2"} value timestamp

# 예시 메트릭
http_requests_total{method="GET", path="/api/users", status="200"} 1234 1716000000000
http_requests_total{method="POST", path="/api/users", status="201"} 56 1716000000000
http_requests_total{method="GET", path="/api/users", status="500"} 3 1716000000000

# 4가지 메트릭 타입
# Counter: 단조 증가 (요청 수, 에러 수)
http_requests_total{job="api"} 9825

# Gauge: 임의로 증가/감소 (CPU 사용률, 메모리, 연결 수)
node_memory_MemAvailable_bytes 2147483648

# Histogram: 관찰값 분포 (응답 시간, 요청 크기)
# _bucket, _sum, _count 세 가지 시계열로 저장됨
http_request_duration_seconds_bucket{le="0.1"} 100
http_request_duration_seconds_bucket{le="0.5"} 150
http_request_duration_seconds_bucket{le="1.0"} 160
http_request_duration_seconds_bucket{le="+Inf"} 165
http_request_duration_seconds_sum   23.5
http_request_duration_seconds_count 165

# Summary: 클라이언트 측에서 계산한 분위수
rpc_duration_seconds{quantile="0.5"} 0.012
rpc_duration_seconds{quantile="0.9"} 0.025
rpc_duration_seconds{quantile="0.99"} 0.083text

3. Docker Compose로 설치

># docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:v2.51.2
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./prometheus/rules/:/etc/prometheus/rules/
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'
      - '--web.enable-admin-api'
    ports: ["9090:9090"]
    restart: unless-stopped

  grafana:
    image: grafana/grafana:10.4.2
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning/:/etc/grafana/provisioning/
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD: grafana123
      GF_USERS_ALLOW_SIGN_UP: "false"
    ports: ["3000:3000"]
    depends_on: [prometheus]
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:v1.7.0
    container_name: node-exporter
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    ports: ["9100:9100"]
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.49.1
    container_name: cadvisor
    privileged: true
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports: ["8080:8080"]
    restart: unless-stopped

  alertmanager:
    image: prom/alertmanager:v0.27.0
    container_name: alertmanager
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
      - alertmanager_data:/alertmanager
    command:
      - '--config.file=/etc/alertmanager/alertmanager.yml'
      - '--storage.path=/alertmanager'
    ports: ["9093:9093"]
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:
  alertmanager_data:yaml
># prometheus/prometheus.yml
global:
  scrape_interval:     15s   # 15초마다 메트릭 수집
  evaluation_interval: 15s   # 15초마다 알림 규칙 평가
  scrape_timeout:      10s

# AlertManager 연동
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-exporter'
    static_configs:
      - targets: ['node-exporter:9100']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

  # cAdvisor (컨테이너 메트릭)
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']yaml

4. 메트릭 수집 설정

># prometheus/prometheus.yml - 고급 scrape 설정
scrape_configs:
  # HTTP 엔드포인트 상태 모니터링 (Blackbox Exporter)
  - job_name: 'blackbox-http'
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://example.com
          - https://api.example.com/health
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115

  # 동적 서비스 디스커버리 (파일 기반)
  - job_name: 'dynamic-services'
    file_sd_configs:
      - files: ['/etc/prometheus/targets/*.json']
        refresh_interval: 30s

  # Docker 서비스 디스커버리
  - job_name: 'docker-containers'
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
    relabel_configs:
      # prometheus.scrape=true 레이블이 있는 컨테이너만 수집
      - source_labels: [__meta_docker_container_label_prometheus_scrape]
        action: keep
        regex: 'true'
      # 포트 레이블 사용
      - source_labels: [__meta_docker_container_label_prometheus_port]
        target_label: __address__
        regex: (.+)
        replacement: "${1}"yaml

5. PromQL 기초

PromQL(Prometheus Query Language)은 Prometheus의 함수형 쿼리 언어입니다. 메트릭 이름과 레이블 셀렉터로 시계열을 선택하고, 함수로 변환합니다.

># ── 즉각 벡터 (Instant Vector) ──────────────────────────────
# 현재 시점의 값
http_requests_total

# 레이블 필터링
http_requests_total{job="api", status="200"}

# 정규식 매칭 (=~: 포함, !~: 미포함)
http_requests_total{status=~"2.."}        # 2xx 상태 코드
http_requests_total{status!~"2.."}        # 2xx 이외 상태 코드
http_requests_total{path=~"/api/.*"}      # /api/ 로 시작하는 경로

# ── 범위 벡터 (Range Vector) ──────────────────────────────────
# 과거 5분간의 샘플 수집
http_requests_total[5m]

# 시간 단위: s(초), m(분), h(시간), d(일), w(주)
node_cpu_seconds_total[1h]

# ── rate(): 초당 평균 증가율 ───────────────────────────────────
# Counter 메트릭에 사용 (Gauge에는 사용 금지)
rate(http_requests_total[5m])

# irate(): 마지막 두 샘플만 사용 (스파이크 감지에 적합)
irate(http_requests_total[5m])

# increase(): 범위 내 총 증가량
increase(http_requests_total[1h])

# ── histogram_quantile(): 분위수 계산 ─────────────────────────
# P99 응답 시간
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# P50 (중앙값)
histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))

# 레이블별 P95
histogram_quantile(0.95,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, job)
)promql

6. PromQL 집계 & 함수

># ── 집계 연산자 ──────────────────────────────────────────────
# sum: 합산
sum(http_requests_total)                     # 전체 요청 수 합계
sum(http_requests_total) by (job)            # job 레이블별 합계
sum(http_requests_total) without (instance)  # instance 제외한 나머지로 그룹

# count: 개수
count(up == 1)                               # 현재 UP 상태인 타깃 수

# avg: 평균
avg(node_cpu_seconds_total{mode="idle"})

# max / min: 최대/최소
max(node_memory_MemAvailable_bytes)
min(node_filesystem_free_bytes{fstype="ext4"})

# topk / bottomk: 상위/하위 N개
topk(5, rate(http_requests_total[5m]))       # 요청 많은 상위 5개
bottomk(3, node_filesystem_free_bytes)       # 여유 공간 적은 하위 3개

# ── 실무 쿼리 예시 ───────────────────────────────────────────
# CPU 사용률 (%) - 전체 코어 평균
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# CPU 사용률 - 인스턴스별
100 - (
  avg by (instance) (
    rate(node_cpu_seconds_total{mode="idle"}[5m])
  ) * 100
)

# 메모리 사용률 (%)
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

# 디스크 사용률
(1 - (node_filesystem_free_bytes / node_filesystem_size_bytes)) * 100

# HTTP 에러율 (5xx / 전체)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))

# 디스크 가득 찰 때까지 남은 시간 예측 (초)
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600)promql

7. Grafana 연동 & 대시보드

># grafana/provisioning/datasources/prometheus.yml
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: true
    jsonData:
      timeInterval: '15s'
      queryTimeout: '60s'yaml
># grafana/provisioning/dashboards/dashboard.yml
apiVersion: 1

providers:
  - name: 'default'
    orgId: 1
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    options:
      path: /etc/grafana/provisioning/dashboardsyaml
💡
대시보드 빠르게 시작하기
Grafana 대시보드에서 Dashboards → Import → 1860 입력 후 "Node Exporter Full" 대시보드를 불러올 수 있습니다. CPU, 메모리, 디스크, 네트워크 패널이 모두 포함되어 있습니다.
># Grafana API로 대시보드 프로그래매틱 생성
curl -X POST http://admin:grafana123@localhost:3000/api/dashboards/import \
  -H 'Content-Type: application/json' \
  -d '{
    "dashboard": {
      "id": null,
      "title": "Node Exporter Full",
      "tags": ["node", "prometheus"]
    },
    "folderId": 0,
    "overwrite": true,
    "inputs": [{
      "name": "DS_PROMETHEUS",
      "type": "datasource",
      "pluginId": "prometheus",
      "value": "Prometheus"
    }]
  }'

# 공식 대시보드 ID로 임포트
# 1860 - Node Exporter Full
# 3662 - Prometheus 2.0 Stats
# 7362 - Node Exporter Dashboard EN 20201010
# 6417 - Kubernetes Clusterbash

8. AlertManager (알림 설정)

># prometheus/rules/alerts.yml
groups:
  - name: node-alerts
    interval: 30s
    rules:
      # CPU 사용률 80% 초과
      - alert: HighCPUUsage
        expr: |
          100 - (avg by (instance) (
            rate(node_cpu_seconds_total{mode="idle"}[5m])
          ) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CPU 사용률 높음 ({{ $labels.instance }})"
          description: "CPU 사용률이 {{ printf \"%.1f\" $value }}%입니다. (임계값: 80%)"

      # 메모리 사용률 90% 초과
      - alert: HighMemoryUsage
        expr: |
          (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "메모리 부족 ({{ $labels.instance }})"
          description: "메모리 사용률 {{ printf \"%.1f\" $value }}%"

      # 디스크 사용률 85% 초과
      - alert: DiskSpaceWarning
        expr: |
          (1 - (node_filesystem_free_bytes / node_filesystem_size_bytes)) * 100 > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "디스크 부족 경고"

      # 디스크 4시간 내 가득 참 예측
      - alert: DiskWillFillIn4Hours
        expr: |
          predict_linear(node_filesystem_free_bytes[1h], 4 * 3600) < 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "디스크 4시간 내 가득 참 예측"

  - name: http-alerts
    rules:
      # HTTP 5xx 에러율 5% 초과
      - alert: HighHTTPErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          /
          sum(rate(http_requests_total[5m])) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "HTTP 에러율 높음"
          description: "에러율 {{ printf \"%.2f\" $value | humanizePercentage }}"yaml
># alertmanager/alertmanager.yml
global:
  smtp_smarthost: 'smtp.gmail.com:587'
  smtp_from: 'alert@example.com'
  smtp_auth_username: 'alert@example.com'
  smtp_auth_password: 'app-password'
  resolve_timeout: 5m

# 알림 라우팅
route:
  group_by: ['alertname', 'instance']
  group_wait:      30s    # 그룹화 대기 시간
  group_interval:  5m     # 같은 그룹 재알림 간격
  repeat_interval: 4h     # 해결 안 된 알림 반복 간격
  receiver: 'slack-critical'

  routes:
    - match:
        severity: critical
      receiver: 'slack-critical'
    - match:
        severity: warning
      receiver: 'email-warning'

receivers:
  - name: 'slack-critical'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
        channel: '#alerts-critical'
        title: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
        text: |
          {{ range .Alerts }}
          *알림:* {{ .Annotations.summary }}
          *상세:* {{ .Annotations.description }}
          *심각도:* {{ .Labels.severity }}
          *시작:* {{ .StartsAt.Format "2006-01-02 15:04:05" }}
          {{ end }}
        send_resolved: true

  - name: 'email-warning'
    email_configs:
      - to: 'ops-team@example.com'
        subject: '[경고] {{ .GroupLabels.alertname }}'
        body: |
          {{ range .Alerts }}
          알림: {{ .Annotations.summary }}
          상세: {{ .Annotations.description }}
          {{ end }}

inhibit_rules:
  # critical 발생 시 동일 인스턴스의 warning 억제
  - source_match:
      severity: critical
    target_match:
      severity: warning
    equal: ['instance']yaml

9. 앱 메트릭 계측 (Python/Go)

Python (prometheus_client)

># pip install prometheus-client fastapi uvicorn
from prometheus_client import (
    Counter, Gauge, Histogram, Summary,
    start_http_server, CollectorRegistry, CONTENT_TYPE_LATEST, generate_latest
)
from fastapi import FastAPI, Request, Response
import time

app = FastAPI()

# ── 메트릭 정의 ──────────────────────────────────────────────
# Counter: 총 요청 수 (레이블: method, path, status)
REQUEST_COUNT = Counter(
    'http_requests_total',
    'Total HTTP requests',
    ['method', 'path', 'status']
)

# Histogram: 응답 시간 분포
REQUEST_DURATION = Histogram(
    'http_request_duration_seconds',
    'HTTP request duration',
    ['method', 'path'],
    buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
)

# Gauge: 현재 처리 중인 요청 수
IN_PROGRESS = Gauge(
    'http_requests_in_progress',
    'HTTP requests currently in progress',
    ['method']
)

# Summary: 데이터베이스 쿼리 시간
DB_QUERY_DURATION = Summary(
    'db_query_duration_seconds',
    'Database query duration'
)

# ── 미들웨어로 자동 계측 ──────────────────────────────────────
@app.middleware('http')
async def metrics_middleware(request: Request, call_next):
    method = request.method
    path = request.url.path

    # /metrics 엔드포인트 제외
    if path == '/metrics':
        return await call_next(request)

    IN_PROGRESS.labels(method=method).inc()
    start_time = time.time()

    response = await call_next(request)

    duration = time.time() - start_time
    status = str(response.status_code)

    REQUEST_COUNT.labels(method=method, path=path, status=status).inc()
    REQUEST_DURATION.labels(method=method, path=path).observe(duration)
    IN_PROGRESS.labels(method=method).dec()

    return response

@app.get('/metrics')
def metrics():
    return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)python

Go (prometheus/client_golang)

>package main

import (
    "net/http"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "path"},
    )

    activeConnections = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "http_active_connections",
        Help: "Number of active HTTP connections",
    })
)

// 미들웨어
func metricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        activeConnections.Inc()
        defer activeConnections.Dec()

        wrapped := &responseWriter{ResponseWriter: w, status: 200}
        next.ServeHTTP(wrapped, r)

        duration := time.Since(start).Seconds()
        status := fmt.Sprintf("%d", wrapped.status)

        httpRequestsTotal.
            WithLabelValues(r.Method, r.URL.Path, status).Inc()
        httpRequestDuration.
            WithLabelValues(r.Method, r.URL.Path).Observe(duration)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/metrics", promhttp.Handler())
    mux.HandleFunc("/api/users", usersHandler)

    http.ListenAndServe(":8080", metricsMiddleware(mux))
}go

10. Kubernetes 통합 (kube-prometheus-stack)

># Helm으로 kube-prometheus-stack 설치
# Prometheus + Grafana + AlertManager + Node Exporter + kube-state-metrics 포함

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 기본 설치
helm install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace

# 커스텀 values로 설치
helm install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --values values.yaml

# 포트 포워딩으로 접속
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090bash
># ServiceMonitor: 서비스의 메트릭 수집 설정
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app
  namespace: monitoring
  labels:
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app: my-app
  namespaceSelector:
    matchNames: [default, production]
  endpoints:
    - port: http
      path: /metrics
      interval: 30s
      scrapeTimeout: 10s
---
# PrometheusRule: Kubernetes 클러스터 알림 규칙
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: my-app-alerts
  namespace: monitoring
  labels:
    release: kube-prometheus-stack
spec:
  groups:
    - name: my-app
      rules:
        - alert: PodRestartingTooOften
          expr: |
            rate(kube_pod_container_status_restarts_total[15m]) * 60 * 15 > 5
          for: 0m
          labels:
            severity: warning
          annotations:
            summary: "Pod {{ $labels.pod }} 재시작 빈번"

        - alert: PodCrashLooping
          expr: |
            kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} == 1
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "Pod CrashLoopBackOff: {{ $labels.pod }}"yaml

11. 로그 수집 (Loki)

># docker-compose.yml에 Loki + Promtail 추가
services:
  loki:
    image: grafana/loki:2.9.7
    container_name: loki
    ports: ["3100:3100"]
    volumes:
      - ./loki/config.yml:/etc/loki/config.yml
      - loki_data:/loki
    command: -config.file=/etc/loki/config.yml
    restart: unless-stopped

  promtail:
    image: grafana/promtail:2.9.7
    container_name: promtail
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./promtail/config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
    depends_on: [loki]
    restart: unless-stopped

volumes:
  loki_data:yaml
># promtail/config.yml
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  # 시스템 로그
  - job_name: system
    static_configs:
      - targets: [localhost]
        labels:
          job: varlogs
          __path__: /var/log/*.log

  # Docker 컨테이너 로그
  - job_name: containers
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        regex: '/(.*)'
        target_label: container
      - source_labels: [__meta_docker_container_log_stream]
        target_label: streamyaml
># LogQL 기본 쿼리 (Grafana Explore에서 사용)

# 모든 로그
{job="varlogs"}

# 특정 컨테이너 로그
{container="my-app"}

# 에러 로그 필터링
{container="my-app"} |= "ERROR"

# 정규식 필터
{job="nginx"} |~ "5[0-9][0-9]"

# 로그 파싱 후 집계: 분당 에러 수
sum(rate({container="my-app"} |= "ERROR" [1m]))

# JSON 로그 파싱
{container="api"} | json | level="error" | line_format "{{.message}}"promql

12. 다음 단계

🚀
Prometheus 심화 로드맵

OpenTelemetry: 메트릭, 로그, 트레이스를 통합하는 오픈 표준. Prometheus 대신 OTLP 프로토콜로 수집
Tempo: Grafana의 분산 트레이싱 솔루션. Jaeger 호환, 오브젝트 스토리지 기반
Grafana Cloud: 관리형 Prometheus + Loki + Tempo + Grafana 통합 플랫폼
Thanos / Cortex: Prometheus 장기 저장, 고가용성, 수평 확장
VictoriaMetrics: Prometheus와 호환되는 고성능 시계열 DB
Prometheus Operator: Kubernetes CRD로 Prometheus 클러스터 관리

연계 가이드: Kubernetes 가이드 · Docker 가이드