이 글에서 다루는 것
dev/prod 간 메트릭이 교차 수집되는 문제를 라벨 기반으로 완전 분리하고, Prometheus TSDB를 local-path로 전환하여 성능과 안정성을 개선하는 과정을 다룹니다.
선수지식
이 단계에서 해결하려는 문제
dev와 prod는 분리돼 있어야 하는데, 정작 관측 데이터에서는 서로 섞여 보이는 문제가 계속 생겼다. Prometheus/KSM/Kubelet의 라벨과 셀렉터가 조금만 어긋나도 dev에서 prod 메트릭을 읽는 심각한 혼선이 발생한다. 이번 단계에서는 관측 스택을 라벨 기반으로 완전히 절단하는 데 집중한다. 그리고 TSDB는 로컬 디스크로 옮겨 속도와 안정성을 끌어올리고, NFS는 나머지 관측 도구 전용으로 재정비한다.
🎯 핵심 요약
- 문제
prometheus-dev.local에서airflow-prod,fastapi-prod메트릭이 섞여서 나옴- kube-state-metrics / kubelet(cAdvisor) / ServiceMonitor 라벨이 꼬여 dev<->prod 교차 수집
- NFS 기반 Prometheus TSDB로 worker2 종료 지연 + I/O 부담 체감
- 목표
- dev/prod 메트릭 수집 완전 분리
- Prometheus TSDB를 local-path로 전환(속도 및 안정성 개선)
- NFS는 관측 도구(Grafana/Alertmanager/로그 등)에만 사용
1️⃣ 문제 상황 정리 — “왜 dev에서 prod가 보이지?”
1-1. 실제로 겪은 증상
prometheus-dev.local에서 아래 쿼리를 날리면:
curl -skG 'https://prometheus-dev.local/api/v1/query' \
--data-urlencode 'query=count by(namespace)(kube_pod_info)'
결과에 airflow-prod, fastapi-prod, monitoring-prod 같은 prod 네임스페이스가 같이 등장한다. 겉으로 보기엔 스택은 dev/prod로 나눠져 있는데, 관측 데이터 레벨에서는 섞여 있는 상태였다.
1-2. 진짜 원인들
kube-state-metrics.prometheus.monitor.additionalLabels.release값을 잘못 넣어서 prod 쪽 KSM도release=monitoring-dev로 찍혀 버림- Prometheus의
serviceMonitorSelector.matchLabels.release가 dev/prod 간 일관성이 없음 - kubelet(cAdvisor) 메트릭에 namespace 필터가 안 들어가 있어서 노드 기준으로 떠 있는 모든 컨테이너 메트릭이 섞여 들어옴
2️⃣ dev/prod 완전 분리 설계 — “라벨로 자르는 기준 만들기”
핵심 개념: 모든 관측 리소스에는 release=monitoring-{env} 라벨을 강제하고, Prometheus는 자기 env의 release 값만 본다.
2-1. Prometheus 셀렉터 통일
dev (apps/monitoring-dev.yaml):
prometheus:
prometheusSpec:
serviceMonitorSelector:
matchLabels:
release: monitoring-dev
podMonitorSelector:
matchLabels:
release: monitoring-dev
ruleSelector:
matchLabels:
release: monitoring-dev
prod (apps/monitoring-prod.yaml):
prometheus:
prometheusSpec:
serviceMonitorSelector:
matchLabels:
release: monitoring-prod
podMonitorSelector:
matchLabels:
release: monitoring-prod
ruleSelector:
matchLabels:
release: monitoring-prod
이 셋이 통일돼야 한다: ServiceMonitor, PodMonitor, PrometheusRule 모두 release 라벨이 환경에 맞아야 한다.
3️⃣ kube-state-metrics (KSM) — dev/prod 라벨 & 필터링
3-1. 잘못됐던 부분
# (오류) apps/monitoring-prod.yaml
kube-state-metrics:
prometheus:
monitor:
additionalLabels:
release: monitoring-dev # prod인데 dev로 들어가 있었음
prod KSM의 ServiceMonitor도 dev Prometheus가 보는 release=monitoring-dev 라벨 세트를 타버렸다.
3-2. 수정본 (핵심)
# apps/monitoring-prod.yaml
kube-state-metrics:
prometheus:
monitor:
enabled: true
additionalLabels:
release: monitoring-prod
metricRelabelings:
- action: keep
sourceLabels: [namespace]
regex: '.*-prod|monitoring-prod|kube-system'
dev도 동일 구조로 release: monitoring-dev, regex: '.*-dev|monitoring-dev|kube-system'을 적용한다.
3-3. 검증 쿼리
curl -skG 'https://prometheus-dev.local/api/v1/query' \
--data-urlencode 'query=count by(namespace)(kube_pod_info)'
- dev:
dev,monitoring-dev,kube-system만 등장 - prod:
prod,monitoring-prod,kube-system만 등장
4️⃣ kubelet / cAdvisor — “노드는 공유인데, 메트릭도 공유되면 큰일”
4-1. 문제 포인트
kubelet의 /metrics/cadvisor 엔드포인트는 노드 전체 파드 메트릭을 노출한다. dev/prod Prometheus 둘 다 kubelet을 스크랩하면 한쪽에서 다른 쪽 네임스페이스 메트릭까지 같이 보게 된다. 기본 metricRelabelings는 /metrics용이고, /metrics/cadvisor에는 적용되지 않는다.
4-2. 해결: cAdvisorMetricRelabelings 추가
dev 예시:
kubelet:
service:
labels:
stack: dev
serviceMonitor:
namespaceSelector:
matchNames: [kube-system]
selector:
matchLabels:
stack: dev
metricRelabelings:
- action: keep
sourceLabels: [namespace]
regex: '.*-dev|monitoring-dev|kube-system'
cAdvisorMetricRelabelings:
- action: keep
sourceLabels: [namespace]
regex: '.*-dev|monitoring-dev|kube-system'
prod는 stack: prod, monitoring-prod, .*-prod로 동일 구조.
4-3. 검증 쿼리
curl -skG 'https://prometheus-dev.local/api/v1/query' \
--data-urlencode 'query=count by(namespace)(container_cpu_cfs_periods_total{job="kubelet", __metrics_path__="/metrics/cadvisor"})'
dev 쿼리 결과에 prod namespace가 보이면 아직 어딘가 구멍이 있는 것이다.
🧩 팁 — “stale metric & 잡소음 알람 정리”
5-1. stale metric 정리
교차 수집 이슈를 해결했어도 이전에 수집된 시계열이 잠깐 남아 있을 수 있다. 환경을 깨끗하게 리셋하고 싶다면:
kubectl -n monitoring-dev rollout restart deploy/monitoring-dev-kube-state-metrics
kubectl -n monitoring-prod rollout restart deploy/monitoring-prod-kube-state-metrics
kubectl -n monitoring-dev rollout restart statefulset prometheus-monitoring-dev-kube-promet-prometheus
kubectl -n monitoring-prod rollout restart statefulset prometheus-monitoring-prod-kube-prome-prometheus
5-2. Watchdog 알람 Slack 소음 제거
기본 Watchdog 알람은 “파이프라인이 살아있는지 확인하는 용도"라 항상 firing 된다.
route:
routes:
- match:
alertname: Watchdog
receiver: "null"
6️⃣ Prometheus TSDB 스토리지 — NFS -> local-path 전환
6-1. 왜 Prometheus만 local-path로 옮겼나?
- TSDB 특성상 쓰기/읽기 I/O가 많고 민감함
- NFS 위에 올리면: worker 노드 종료 시 unmount 대기로 종료 지연, 네트워크 레이턴시가 관측 성능에 영향
- Grafana/Alertmanager는 I/O량이 훨씬 적으므로 NFS로도 충분
구조 정리: Prometheus는 local-path(노드 로컬 디스크), Grafana/Alertmanager는 NFS(nfs-monitoring).
6-2. values 수정
prometheus:
prometheusSpec:
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
6-3. 실제 전환 절차 (데이터 초기화 전제)
# (1) 값 반영
kubectl apply -f apps/monitoring-dev.yaml
# (2) Prometheus 일시 중단
kubectl -n monitoring-dev scale statefulset prometheus-monitoring-dev-kube-promet-prometheus --replicas=0
# (3) 기존 NFS PVC 삭제
kubectl -n monitoring-dev delete pvc -l app.kubernetes.io/name=prometheus
# (4) Prometheus 재기동 (local-path 기반 PVC 생성)
kubectl -n monitoring-dev scale statefulset prometheus-monitoring-dev-kube-promet-prometheus --replicas=1
prod도 동일하게 진행한다. ArgoCD selfHeal 때문에 레플리카가 다시 올라오지 않도록 타이밍에 신경 써야 한다.
6-4. PVC 확인
kubectl -n monitoring-dev get pvc | grep prometheus
# STORAGECLASS 열이 local-path 여야 정상
7️⃣ Prometheus TSDB 실제 위치 — “데이터가 디스크 어디에 있나”
7-1. PVC -> PV -> 호스트 경로 추적
kubectl -n monitoring-dev get pvc <prometheus-pvc-name> -o yaml | grep volumeName
kubectl get pv pvc-xxxxxx -o yaml | grep path
# path: /opt/local-path-provisioner/pvc-xxxxxx
컨테이너 내부에서는 같은 볼륨이 /prometheus로 마운트되어 있다. 노드에서 직접 확인하면 chunks_head, wal, snapshots 등 TSDB 구조가 보인다.
8️⃣ NFS는 어디에 쓰나? — 관측 스택 나머지(PVC) 전략
8-1. NFS 동적 프로비저너 (observability 전용)
Loki/Promtail, Grafana, Alertmanager 등 로그/대시보드 계열은 공유 스토리지가 편해서 NFS를 유지한다.
helm upgrade --install nfs-prov \
nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
-n storage \
--set nfs.server=192.168.18.141 \
--set nfs.path=/mnt/nfs_share/mlops/observability \
--set storageClass.name=nfs-observability \
# ... (이하 생략)
nfs-observability라는 StorageClass를 생성하여 Loki / Grafana / Alertmanager에 사용한다.
8-2. Monitoring 스택에서 NFS 사용하는 영역
- Grafana: 대시보드/설정
- Alertmanager: 알람 히스토리
- Loki: 로그 보관
Prometheus TSDB는 이미 local-path로 분리했으므로 읽기/쓰기 많은 시계열은 로컬, 공유가 편한 설정/로그는 NFS라는 역할 분리가 된다.
설계 판단 (Why This Way?)
노드 수가 제한된 환경에서 물리적 클러스터 분리 대신 release 라벨 + metricRelabelings로 논리적 메트릭 격리를 구현했습니다. Prometheus TSDB는 고 IOPS가 필수이므로 local-path를, Grafana/Loki 등 I/O가 적은 워크로드는 NFS를 사용하여 스토리지를 워크로드 특성별로 분리했습니다.
다음에 읽을 글
→ Observability 4단계: Loki/Promtail 로그 파이프라인 구축 — 중앙 집중 로그 수집