infra
Platform

모듈 맵

[Kubernetes] Prometheus Operator와 Grafana 연동 대시보드 구축

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 25 / 29

[Kubernetes] Prometheus Operator와 Grafana 연동 대시보드 구축

kube-prometheus-stack으로 Prometheus, Grafana, Alertmanager를 배포하고, ServiceMonitor로 파드 메트릭을 수집하며, PromQL과 알림 규칙으로 운영 가시성을 확보합니다

🚨INCIDENT ALERT
HIGH

서비스는 느려졌는데 CPU, 에러율, 지연 시간 중 무엇이 먼저 나빠졌는지 아무도 모릅니다. Kubernetes에서는 파드가 자주 바뀌기 때문에 정적 모니터링 설정만으로는 놓치는 대상이 생깁니다. Prometheus는 동적인 클러스터 환경에서 메트릭을 수집하고 알림의 근거를 제공합니다.

Kubernetes 모니터링 — Prometheus Operator와 Grafana

프로덕션 클러스터에서 "API가 느린 것 같다"는 제보가 들어왔습니다. 개발자들이 추측으로 서비스를 바라보는 동안, 모니터링 대시보드가 있다면 30초 안에 원인을 찾을 수 있습니다. 어느 서비스의 응답시간 P99가 튀었는지, 어느 파드의 CPU가 포화 상태인지, 에러율이 정상 범위를 벗어난 시점이 언제인지. Kubernetes 환경에서 파드는 수시로 재시작되고 IP가 바뀝니다. Prometheus Operator는 이 동적인 환경에서 수집 대상을 자동으로 추적합니다. ServiceMonitor 하나를 만들면 해당 서비스의 파드가 몇 개로 늘어나든 자동으로 수집 대상에 포함됩니다.


이번 챕터에서 배울 것

kube-prometheus-stack을 처음부터 설치하고 커스텀 애플리케이션의 메트릭을 수집하여 Grafana에서 시각화합니다. 알림 규칙 작성과 Alertmanager 라우팅까지 운영 환경 수준의 모니터링 스택을 구성합니다.

  • 1kube-prometheus-stack — Prometheus Operator + Grafana + Alertmanager 일괄 배포
  • 2ServiceMonitor로 파드 메트릭 수집 자동화
  • 3PodMonitor와 ServiceMonitor의 차이
  • 4PromQL 기본 — rate(), sum(), by(), histogram_quantile()
  • 5PrometheusRule로 알림 규칙 코드로 관리
  • 6Grafana 대시보드 프로비저닝
실습 환경 준비

kube-prometheus-stack은 Prometheus, Grafana, Alertmanager, 각종 exporter를 포함하여 약 1-2GB RAM이 필요합니다. 로컬 환경에서는 minikube를 --memory 4096 이상으로 시작하거나, kind 클러스터를 사용하세요.

kubectl 클러스터 연결 확인
kubectl cluster-info
모니터링 네임스페이스 생성
kubectl create namespace monitoring
prometheus-community Helm 저장소 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts && helm repo update
현재 클러스터 노드 메모리 확인 (스택이 RAM을 많이 사용함)
kubectl top nodes 2>/dev/null || kubectl get nodes -o wide
실습 완료 후 정리

helm uninstall kube-prometheus-stack -n monitoring && kubectl delete namespace monitoring

💡개념

kube-prometheus-stack — 표준 모니터링 스택 배포

Prometheus를 직접 설치하고 Grafana를 별도로 설치하다 보면 버전 호환성 문제가 발생하고, Alertmanager 연동, exporter 추가, scrape 설정마다 파일을 수동으로 수정해야 합니다. 클러스터 컴포넌트 수가 늘어날수록 설정 파일이 비대해지고 누락이 생깁니다. kube-prometheus-stack은 이 모든 컴포넌트를 하나의 Helm Chart로 묶어 초기 설치와 업그레이드를 단순화합니다. Prometheus Operator가 포함되어 있어 이후 수집 대상 추가와 알림 규칙 관리를 Kubernetes 리소스(CRD)로 선언적으로 처리할 수 있습니다. 이 CB에서는 kube-prometheus-stack 설치와 기본 제공 대시보드 확인까지의 절차를 다룹니다. 개별 설치보다 훨씬 빠르게 프로덕션 수준의 모니터링 스택을 구성할 수 있습니다.

kube-prometheus-stack — 표준 모니터링 스택 배포

스택 구성 요소

kube-prometheus-stack 설치 후 실행되는 컴포넌트:
├── prometheus-operator          — Prometheus/Alertmanager CRD Controller
├── prometheus                   — 메트릭 수집 및 저장 (StatefulSet)
├── alertmanager                 — 알림 라우팅 및 그룹핑 (StatefulSet)
├── grafana                      — 메트릭 시각화 (Deployment)
├── kube-state-metrics           — Kubernetes 오브젝트 상태 메트릭
├── node-exporter                — 노드 시스템 메트릭 (DaemonSet)
└── prometheus-adapter           — HPA용 커스텀 메트릭 API

설치

로컬 터미널
# values 파일 생성 (리소스 절약 설정)
cat > prometheus-values.yaml << 'EOF'
# Grafana 설정
grafana:
  adminPassword: "admin123"     # 실습용 패스워드
  service:
    type: NodePort              # 로컬 접근용 (프로덕션에서는 Ingress 사용)
  persistence:
    enabled: false              # 실습용 (프로덕션에서는 true)

# Prometheus 설정
prometheus:
  prometheusSpec:
    retention: 15d              # 메트릭 보존 기간
    storageSpec:
      volumeClaimTemplate:
        spec:
          resources:
            requests:
              storage: 10Gi
    # 모든 네임스페이스의 ServiceMonitor를 허용
    serviceMonitorSelectorNilUsesHelmValues: false
    podMonitorSelectorNilUsesHelmValues: false

# Alertmanager 설정
alertmanager:
  alertmanagerSpec:
    storage:
      volumeClaimTemplate:
        spec:
          resources:
            requests:
              storage: 2Gi
EOF

# 스택 설치
helm install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  -f prometheus-values.yaml \
  --wait \
  --timeout 10m

# 설치 확인
kubectl get pods -n monitoring
# NAME                                                     READY   STATUS
# alertmanager-kube-prometheus-stack-alertmanager-0        2/2     Running
# kube-prometheus-stack-grafana-7d9f6b8d4c-xk2p9          3/3     Running
# kube-prometheus-stack-kube-state-metrics-xxx             1/1     Running
# kube-prometheus-stack-operator-xxx                       1/1     Running
# prometheus-kube-prometheus-stack-prometheus-0            2/2     Running
# kube-prometheus-stack-prometheus-node-exporter-xxx       1/1     Running  (각 노드)
🔍실행 후 확인할 것
  • NAME조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
  • STATUS/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

Grafana 접근

Kubernetes
# NodePort로 접근 (minikube/kind)
kubectl get svc -n monitoring kube-prometheus-stack-grafana
# NAME                              TYPE       CLUSTER-IP    PORT(S)
# kube-prometheus-stack-grafana     NodePort   10.96.x.x     80:32xxx/TCP

# minikube의 경우
minikube service kube-prometheus-stack-grafana -n monitoring

# 또는 포트포워딩으로 접근
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80 &
# → http://localhost:3000 접속 (admin / admin123)

기본 제공되는 대시보드 확인

Grafana 로그인 후 Dashboards → Browse:
  - Kubernetes / Cluster       — 클러스터 전체 리소스 사용률
  - Kubernetes / Nodes         — 노드별 CPU/Memory/Disk
  - Kubernetes / Workloads     — Deployment/DaemonSet 상태
  - Kubernetes / Pods          — 파드별 상세 메트릭
  - Kubernetes / Namespaces    — 네임스페이스별 리소스
  - Node Exporter / Full       — 노드 상세 시스템 메트릭

💡개념

ServiceMonitor — 파드 메트릭 자동 수집 설정

새로운 마이크로서비스를 배포할 때마다 prometheus.ymlscrape_configs를 수동으로 수정하고 Prometheus를 재시작하거나 reload를 보내야 합니다. 파드 IP가 바뀌면 수집이 끊기고, 새 파드가 추가되면 설정을 또 변경해야 합니다. 서비스가 수십 개를 넘어서면 이 방식은 유지보수가 불가능해집니다. ServiceMonitor는 이 문제를 해결합니다. Prometheus Operator의 핵심 가치는 ServiceMonitor입니다. 기존 Prometheus에서 파드를 추가하려면 prometheus.ymlscrape_configs를 수동으로 수정하고 재로드해야 했습니다. ServiceMonitor는 이를 Kubernetes 리소스로 선언하면 Operator가 자동으로 Prometheus 설정에 반영합니다.

ServiceMonitor — 파드 메트릭 자동 수집 설정

메트릭을 노출하는 샘플 애플리케이션 배포

YAML
# sample-app.yaml — /metrics 엔드포인트가 있는 nginx
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
  labels:
    app: sample-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
        version: "1.0"
    spec:
      containers:
        - name: app
          image: quay.io/brancz/prometheus-example-app:v0.3.0
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              cpu: 50m
              memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
  namespace: default
  labels:
    app: sample-app              # ServiceMonitor가 이 레이블로 찾음
spec:
  selector:
    app: sample-app
  ports:
    - name: http                 # 포트 이름이 ServiceMonitor의 port와 일치해야 함
      port: 8080
      targetPort: 8080

ServiceMonitor 정의

YAML
# servicemonitor-sample.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: sample-app
  namespace: default
  labels:
    release: kube-prometheus-stack  # Prometheus가 이 레이블로 ServiceMonitor를 선택
spec:
  # scrape할 Service를 레이블로 선택
  selector:
    matchLabels:
      app: sample-app              # Service의 labels.app: sample-app

  # 여러 네임스페이스에 걸친 Service 선택 (기본: 동일 네임스페이스)
  namespaceSelector:
    matchNames:
      - default
      - production

  # scrape 엔드포인트 설정
  endpoints:
    - port: http                   # Service의 port 이름과 일치
      path: /metrics               # 메트릭 경로 (기본값)
      interval: 30s                # scrape 간격
      scrapeTimeout: 10s           # 타임아웃
      # TLS 설정 (HTTPS 엔드포인트의 경우)
      # tlsConfig:
      #   insecureSkipVerify: true
Kubernetes
kubectl apply -f sample-app.yaml
kubectl apply -f servicemonitor-sample.yaml

# Prometheus UI에서 타겟 확인 (약 1분 후)
kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-stack-prometheus 9090:9090 &
# → http://localhost:9090/targets 접속
# status=UP으로 표시되면 수집 중

PodMonitor vs ServiceMonitor

YAML
# Service 없이 파드를 직접 scrape할 때 PodMonitor 사용
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: batch-jobs
  namespace: default
  labels:
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app: batch-processor
  podMetricsEndpoints:
    - port: metrics                # 파드 containerPort 이름
      path: /metrics
      interval: 60s               # 배치 잡은 긴 간격

# ServiceMonitor: Service → Endpoints를 통해 파드 자동 추적
# PodMonitor:     Service 없이 파드를 직접 레이블로 선택

💡개념

PromQL 기본 — 메트릭 조회와 집계

Grafana에서 메트릭을 보려는데 쿼리 박스 앞에서 막힙니다. Counter는 계속 증가하는 숫자라 그냥 쓰면 의미가 없고, Histogram에서 P99를 뽑으려면 특별한 함수가 필요한데 어디서 시작해야 할지 모릅니다. PromQL을 모르면 Prometheus는 데이터 창고에 불과하고 실제 운영 판단에 쓰이지 못합니다. Prometheus는 자체 쿼리 언어 PromQL을 사용하며, 시계열 데이터에 특화된 몇 가지 핵심 함수를 익히면 대부분의 운영 질문에 답할 수 있습니다. 이 CB에서는 Counter, Gauge, Histogram 타입별 쿼리 패턴과 집계 함수 사용법을 다룹니다. 시계열 데이터에 특화되어 있어 "지난 5분간 초당 에러율", "노드별 메모리 사용률 상위 5개" 같은 쿼리를 간결하게 표현합니다.

메트릭 타입별 쿼리 패턴

PROMQL
# --- Gauge (현재 상태값) ---
# 현재 메모리 사용률 (%)
100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100)

# 파드별 메모리 사용량 (MiB)
container_memory_working_set_bytes{container!=""} / 1024 / 1024

# --- Counter (단조 증가값) — rate()로 변화율 계산 ---
# 초당 HTTP 요청 수
rate(http_requests_total[5m])

# 초당 에러 요청 수 (5xx)
rate(http_requests_total{status=~"5.."}[5m])

# --- Histogram — 분위수 계산 ---
# HTTP 응답시간 P99 (99th percentile)
histogram_quantile(
  0.99,
  rate(http_request_duration_seconds_bucket[5m])
)

# HTTP 응답시간 P50, P95, P99 비교
histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

집계 함수 (by, without)

PROMQL
# 서비스별 초당 요청 수 합계
sum(rate(http_requests_total[5m])) by (service)
# 결과:
# {service="auth"}      → 45.2
# {service="payment"}   → 12.1
# {service="catalog"}   → 150.3

# 네임스페이스별 파드 수
count(kube_pod_info) by (namespace)

# 파드별 CPU 사용률 상위 5개
topk(5, rate(container_cpu_usage_seconds_total{container!=""}[5m]))

# 노드별 CPU 사용률 (전체 대비 %)
100 * (1 - avg by (instance) (
  rate(node_cpu_seconds_total{mode="idle"}[5m])
))

Kubernetes 환경에서 자주 쓰는 쿼리

PROMQL
# Deployment 파드 가용률
kube_deployment_status_replicas_available / kube_deployment_spec_replicas

# 파드 재시작 횟수 증가율 (크래시 루프 감지)
rate(kube_pod_container_status_restarts_total[15m]) * 60

# OOMKilled 파드 감지
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}

# PersistentVolume 사용률
(
  kubelet_volume_stats_used_bytes /
  kubelet_volume_stats_capacity_bytes
) * 100

# 노드별 할당 가능 CPU 대비 요청 비율
sum(kube_pod_container_resource_requests{resource="cpu"}) by (node) /
sum(kube_node_status_allocatable{resource="cpu"}) by (node) * 100

Grafana에서 PromQL 사용

1. Grafana → Explore (나침반 아이콘) → Prometheus 데이터 소스 선택
2. 쿼리 입력 후 Run Query
3. 시각화: Graph, Stat, Table, Heatmap 등 선택
4. Dashboard에 패널 추가: "Add to Dashboard" 버튼

💡개념

PrometheusRule — 알림 규칙 코드로 관리

알림 규칙을 UI에서 직접 설정하면 추적이 안 됩니다. PrometheusRule CRD로 알림 규칙을 YAML 파일로 관리하면 Git에서 히스토리를 추적하고, 코드 리뷰를 거치고, CI/CD로 배포할 수 있습니다.

PrometheusRule 정의

YAML
# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: app-alerts
  namespace: default
  labels:
    release: kube-prometheus-stack  # Prometheus가 선택하는 레이블
spec:
  groups:
    - name: availability            # 알림 그룹 이름
      interval: 30s                 # 평가 간격
      rules:
        # 파드 크래시 루프 감지
        - alert: PodCrashLooping
          expr: |
            rate(kube_pod_container_status_restarts_total[15m]) * 60 > 0
          for: 5m                   # 5분간 지속될 때만 발화
          labels:
            severity: critical
            team: platform
          annotations:
            summary: "파드 {{ $labels.pod }}가 크래시 루프 중"
            description: |
              네임스페이스 {{ $labels.namespace }}의 파드
              {{ $labels.pod }} (컨테이너: {{ $labels.container }})가
              지난 15분간 {{ printf "%.1f" $value }}회/분 재시작되고 있습니다.

        # 고에러율 감지
        - alert: HighErrorRate
          expr: |
            sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
            /
            sum(rate(http_requests_total[5m])) by (service)
            > 0.05
          for: 2m
          labels:
            severity: warning
            team: backend
          annotations:
            summary: "{{ $labels.service }} 에러율 {{ printf \"%.1f\" $value | humanizePercentage }}"
            description: "서비스 {{ $labels.service }}의 5xx 에러율이 5%를 초과했습니다."

        # 높은 응답시간 감지
        - alert: HighResponseTime
          expr: |
            histogram_quantile(
              0.99,
              rate(http_request_duration_seconds_bucket[5m])
            ) > 1.0
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "{{ $labels.service }} P99 응답시간 1초 초과"
            description: "{{ $labels.service }}의 P99 응답시간이 {{ printf \"%.2f\" $value }}초입니다."

    - name: resources
      rules:
        # 노드 메모리 고갈 임박
        - alert: NodeMemoryPressure
          expr: |
            100 - (
              node_memory_MemAvailable_bytes /
              node_memory_MemTotal_bytes * 100
            ) > 85
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "노드 {{ $labels.instance }} 메모리 부족"
            description: "메모리 사용률 {{ printf \"%.1f\" $value }}%"

        # PV 용량 임박
        - alert: PersistentVolumeAlmostFull
          expr: |
            (
              kubelet_volume_stats_used_bytes /
              kubelet_volume_stats_capacity_bytes
            ) * 100 > 80
          for: 15m
          labels:
            severity: warning
          annotations:
            summary: "PV {{ $labels.persistentvolumeclaim }} 용량 {{ printf \"%.1f\" $value }}% 사용"

Alertmanager 라우팅 설정

YAML
# alertmanager-config.yaml (Helm values에 포함하거나 Secret으로 배포)
alertmanager:
  config:
    global:
      resolve_timeout: 5m
      slack_api_url: "https://hooks.slack.com/services/xxx/yyy/zzz"

    route:
      group_by: ["alertname", "namespace"]
      group_wait: 30s       # 그룹 내 첫 알림 대기 (더 쌓이길 기다림)
      group_interval: 5m    # 같은 그룹 알림 간격
      repeat_interval: 12h  # 해결 안 됐을 때 반복 간격
      receiver: "slack-general"

      routes:
        - match:
            severity: critical
          receiver: "pagerduty-oncall"

        - match:
            team: backend
          receiver: "slack-backend"

    receivers:
      - name: "slack-general"
        slack_configs:
          - channel: "#alerts"
            title: "{{ .GroupLabels.alertname }}"
            text: "{{ range .Alerts }}{{ .Annotations.description }}{{ end }}"

      - name: "slack-backend"
        slack_configs:
          - channel: "#backend-alerts"
            send_resolved: true

      - name: "pagerduty-oncall"
        pagerduty_configs:
          - routing_key: "YOUR_INTEGRATION_KEY"
            severity: "{{ .CommonLabels.severity }}"
Kubernetes
kubectl apply -f prometheus-rules.yaml

# 알림 규칙이 Prometheus에 반영됐는지 확인
# http://localhost:9090/rules → "app-alerts" 그룹 확인

실습 — 전체 수집 파이프라인 구성

1단계: kube-prometheus-stack 설치

로컬 터미널
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 \
  --set grafana.adminPassword=admin123 \
  --set grafana.service.type=NodePort \
  --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \
  --wait

kubectl get pods -n monitoring

2단계: 샘플 앱 배포

Kubernetes
kubectl apply -f - << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: app
          image: quay.io/brancz/prometheus-example-app:v0.3.0
          ports:
            - name: http
              containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
  namespace: default
  labels:
    app: sample-app
spec:
  selector:
    app: sample-app
  ports:
    - name: http
      port: 8080
      targetPort: 8080
EOF

3단계: ServiceMonitor 적용 및 수집 확인

Kubernetes
kubectl apply -f - << 'EOF'
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: sample-app
  namespace: default
  labels:
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app: sample-app
  endpoints:
    - port: http
      path: /metrics
      interval: 15s
EOF

# Prometheus 포트포워딩 후 타겟 확인
kubectl port-forward -n monitoring \
  svc/prometheus-operated 9090:9090 &

# 1-2분 후 http://localhost:9090/targets에서
# "default/sample-app/0" 타겟이 UP 상태인지 확인

4단계: PromQL로 메트릭 조회

로컬 터미널
# Prometheus UI (http://localhost:9090/graph)에서 쿼리 실행:

# 샘플 앱의 초당 요청 수
rate(http_requests_total[1m])

# 클러스터 전체 파드 수 (네임스페이스별)
count(kube_pod_info) by (namespace)

# 노드 CPU 사용률
100 * (1 - avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])))

5단계: Grafana에서 대시보드 확인

Kubernetes
# Grafana 포트포워딩
kubectl port-forward -n monitoring \
  svc/kube-prometheus-stack-grafana 3000:80 &

# http://localhost:3000 접속 (admin / admin123)
# Dashboards → Kubernetes / Workloads → namespace=default 선택
# → sample-app Deployment 메트릭 확인

문제 상황

로컬 터미널
# ServiceMonitor를 적용했는데 Prometheus UI의 /targets에서 보이지 않거나
# "Unknown" 또는 "0/0" 타겟으로 표시됨

# Prometheus Configuration 확인
# http://localhost:9090/config 에서 job_name 목록에 서비스가 없음

원인 1: ServiceMonitor의 레이블이 Prometheus selector와 불일치 (가장 흔함)

Kubernetes
# Prometheus가 ServiceMonitor를 선택하는 방식 확인
kubectl get prometheus -n monitoring -o yaml | \
  grep -A 10 "serviceMonitorSelector:"

# 출력 예시:
# serviceMonitorSelector:
#   matchLabels:
#     release: kube-prometheus-stack   ← 이 레이블이 ServiceMonitor에 있어야 함

# ServiceMonitor의 레이블 확인
kubectl get servicemonitor sample-app -n default -o yaml | grep "labels:" -A 5
# labels:
#   release: kube-prometheus-stack   ← 일치 여부 확인

# 해결: ServiceMonitor에 올바른 레이블 추가
kubectl patch servicemonitor sample-app -n default \
  --type=merge \
  -p '{"metadata":{"labels":{"release":"kube-prometheus-stack"}}}'

원인 2: ServiceMonitor의 selector가 Service 레이블과 불일치

Kubernetes
# Service의 실제 레이블 확인
kubectl get svc sample-app -n default --show-labels
# NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   LABELS
# sample-app   ClusterIP   10.96.x.x    <none>        8080/TCP  5m    app=sample-app

# ServiceMonitor의 selector 확인
kubectl get servicemonitor sample-app -n default -o jsonpath='{.spec.selector}'
# {"matchLabels":{"app":"sample-app-v2"}}   ← "app=sample-app"과 불일치!

# 해결: selector 수정
kubectl patch servicemonitor sample-app -n default \
  --type=merge \
  -p '{"spec":{"selector":{"matchLabels":{"app":"sample-app"}}}}'

원인 3: ServiceMonitor의 port 이름이 Service port 이름과 불일치

Kubernetes
# Service의 port 이름 확인
kubectl get svc sample-app -n default -o yaml | grep -A 5 "ports:"
# ports:
#   - name: http          ← Service port 이름
#     port: 8080

# ServiceMonitor의 endpoint port 이름 확인
kubectl get servicemonitor sample-app -n default -o yaml | grep -A 3 "endpoints:"
# endpoints:
#   - port: web           ← "http"와 불일치!

# 해결: ServiceMonitor endpoint port 이름을 "http"로 수정

원인 4: 애플리케이션이 /metrics를 노출하지 않음

Kubernetes
# 파드에서 직접 /metrics 엔드포인트 테스트
kubectl exec -n default \
  $(kubectl get pod -n default -l app=sample-app -o name | head -1) \
  -- wget -qO- http://localhost:8080/metrics | head -20

# 출력이 없거나 404이면 앱이 메트릭을 노출하지 않음
# 해결: 앱에 Prometheus 메트릭 라이브러리 추가 필요

Prometheus UI에서 디버깅

1. http://localhost:9090/targets
   → "Unhealthy" 탭에서 오류 메시지 확인
   → "scrape_error: dial tcp ...: connection refused" = 앱이 포트를 안 엶
   → "scrape_error: 404" = /metrics 경로 없음

2. http://localhost:9090/service-discovery
   → "Discovered" 목록에서 ServiceMonitor가 발견됐는지 확인
   → "Dropped" 목록에서 제외된 레이블 확인

3. http://localhost:9090/config
   → ServiceMonitor가 Prometheus 설정으로 변환됐는지 확인

💼
실무 맥락
현업 패턴

배경

플랫폼팀 가이드: 팀에서 새 마이크로서비스를 배포할 때마다 모니터링 설정이 빠지는 일이 반복됐습니다. 배포 후 장애가 나면 메트릭이 없어 대응이 느렸습니다. 신규 서비스 배포 체크리스트를 만들어 표준화했습니다.

서비스 측 준비 (개발팀)

[ ] 애플리케이션에 Prometheus 메트릭 라이브러리 추가
    - Node.js: prom-client
    - Python: prometheus-client
    - Go: prometheus/client_golang
    - Java: micrometer-prometheus

[ ] /metrics 엔드포인트 노출 (기본 포트 또는 별도 포트)

[ ] 최소 메트릭 세트 정의:
    - http_requests_total (Counter, status/method/path 레이블)
    - http_request_duration_seconds (Histogram)
    - 비즈니스 메트릭 (주문 수, 결제 성공률 등)

[ ] 컨테이너 포트에 이름 부여 (ServiceMonitor에서 이름으로 참조)
    ports:
      - name: http     ← 이름 필수
        containerPort: 8080
      - name: metrics  ← 메트릭 포트 별도 분리 시
        containerPort: 9090

인프라 측 준비 (플랫폼팀)

YAML
# 서비스당 최소 3개 리소스 배포:

# 1. Service (레이블 포함)
metadata:
  labels:
    app: new-service
    team: backend

# 2. ServiceMonitor
spec:
  selector:
    matchLabels:
      app: new-service
  endpoints:
    - port: metrics

# 3. PrometheusRule (최소 알림 규칙)
rules:
  - alert: NewServiceDown
    expr: up{job="new-service"} == 0
    for: 2m
  - alert: NewServiceHighErrorRate
    expr: rate(http_requests_total{status=~"5..",service="new-service"}[5m]) > 0.1
    for: 5m

Grafana 대시보드 프로비저닝

YAML
# ConfigMap으로 대시보드 자동 프로비저닝
apiVersion: v1
kind: ConfigMap
metadata:
  name: new-service-dashboard
  namespace: monitoring
  labels:
    grafana_dashboard: "1"  # grafana sidecar가 자동으로 로드
data:
  new-service.json: |
    {
      "title": "New Service Metrics",
      "panels": [...]
    }

이 체크리스트를 Helm Chart에 포함하면 서비스 배포와 모니터링 설정이 동시에 이루어집니다.


핵심 요약

개념설명
kube-prometheus-stackPrometheus + Grafana + Alertmanager + exporter 일괄 Helm Chart
Prometheus OperatorServiceMonitor/PrometheusRule CRD를 감시하고 Prometheus 설정을 자동 업데이트
ServiceMonitorlabelSelector로 Service를 선택하여 파드 scrape 대상 자동 등록
PodMonitorService 없이 파드를 직접 레이블로 선택하여 scrape
rate(counter[window])Counter의 초당 변화율 계산 — Counter는 항상 rate()로 사용
histogram_quantile(φ, ...)Histogram에서 분위수(P50/P95/P99) 계산
sum() by (label)레이블 기준으로 메트릭 집계
PrometheusRule알림 규칙을 YAML로 코드화 — Git 관리 및 코드 리뷰 가능
Alertmanager알림 수신 → 그룹핑 → 라우팅 → Slack/PagerDuty 전송
타겟 디버깅/targets, /service-discovery, /config 순으로 Prometheus UI 확인

지식 확인

퀴즈 — 4문제

Q1

ServiceMonitor 리소스가 Prometheus scrape 타겟을 지정하는 방식은?

Q2

PromQL 쿼리 `rate(http_requests_total[5m])` 의 의미는?

Q3

Prometheus의 Alertmanager 역할은 무엇인가?

Q4

Prometheus가 ServiceMonitor 설정대로 타겟을 scrape하지 않을 때 가장 먼저 확인해야 할 것은?

0 / 4 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

kubernetes고급 · 75
[Kubernetes] Istio 서비스 메시가 제공하는 가시성과 mTLS 보안
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점