Grafana 입문 8편. 운영 함정 + 사고 케이스 깊이 — Cardinality 폭발 의 디버그 (TSDB · series 수 모니터 · drop label), Cost 폭증 (Cloud · S3 · Mimir 의 비용 함정), Plugin 의 보안 사고 (CVE · supply chain), DDoS · 광범위 사고 의 alert 폭주, Configuration drift 의 회복, Backup · DR 의 시험과 실패 case, Multi-region 의 latency · clock skew, Compliance · Audit 사고 (GDPR · PIPA), Vendor lock-in 의 회피. 운영 1년 이상 의 자리.
이 글은 Grafana 입문에서 운영까지 시리즈 8편이에요. 1~7편이 기능의 이해 였다면 8편은 운영 1년 이상이 쌓아 올린 무거운 사고 자리예요.
이번 글의 범위
운영자에게 처음엔 안 보이고 서서히 발견되며 한 번 발견하면 잊혀지지 않는 사고들의 모음이에요.
| 영역 | 사고 |
|---|---|
| Data | Cardinality 폭발 · 비용 폭증 |
| Security | Plugin CVE · Audit 누락 |
| Reliability | Alert 폭주 · Config drift · DR 실패 |
| Scale | Multi-region · Clock skew |
| Governance | Compliance · Vendor lock-in |
Cardinality 폭발 — 가장 큰 함정
Cardinality 의 의미 (재정리)
Cardinality(label 조합으로 만들어지는 고유 time series 개수)는 메트릭 시스템의 메모리·비용을 좌우하는 핵심 지표예요.
Time Series 의 개수 = label combination 의 unique 수
예 — http_requests_total:
{method, status, region, service, path}
method: 5 종 (GET, POST, PUT, DELETE, PATCH)
status: 30 종 (200, 201, 400, 401, ...)
region: 3 종 (us, eu, asia)
service: 20 종
path: ???
→ 5 × 30 × 3 × 20 × <path> = 9000 × <path>
path 가 100 종 → 900,000 series
path 가 dynamic (/products/12345 같은) → 무한
Cardinality 폭발 의 증상
- Prometheus / Mimir 의 메모리 폭증
- Query 의 timeout
- Loki 의 stream 수 증가 → injest 느림
- Tempo 의 indexing 느림
- Grafana dashboard 의 로딩 매우 느림
디버그 — Series 수 확인
# Prometheus / Mimir 의 series 수
prometheus_tsdb_head_series
# Top 10 high-cardinality metric
topk(10,
count by (__name__) ({__name__=~".+"})
)
# 특정 metric 의 label 별 cardinality
count(count by (label_name) (metric_name))
# 모든 label 의 cardinality
topk(10,
count by (label_name) (
{__name__="metric_name"}
)
)
Cardinality 회피
1. Label 의식
동적 값 (user_id · request_id · UUID) = label X
normalized 한 값 ("/products/:id") 만 label
2. Drop label (Prometheus relabel)
relabel_configs:
- source_labels: [__name__]
regex: 'noisy_metric_.*'
action: drop
3. Metric Aggregation
raw metric → Recording Rule 의 aggregated metric
원본 series 는 short retention
4. Histogram 의 bucket 의식
너무 많은 bucket = cardinality 폭증
필요 bucket 만 (10 ~ 15 권장)
사고 케이스 — Path Cardinality
사고: e-commerce 의 product page 의 path 가 그대로 label
/products/12345 · /products/12346 · ...
10만 상품 → 10만 unique path label
→ http_requests_total 의 series 수 폭증
→ Prometheus OOM
해결:
Application 의 routing 의식:
GET /products/:id → handler name = "GetProduct"
Instrumentation 의 path 정규화:
span.set_attribute("http.route", "/products/:id")
label 박을 때 path 아닌 route 사용
Cost 폭증
Grafana Cloud Cost
Free Tier 의 한도 초과 시:
- Metrics: 10k → 20k = +$8/월
- Logs: 50GB → 100GB = +$25/월
- 갑자기 트래픽 폭증 = 큰 청구
Mimir 의 Cost
저장:
- 1 active series ≈ 8 bytes (메모리) × N hours retention
- 1억 series × 7일 = 큰 비용
- S3 chunk storage: $0.023 / GB / 월
- 1 TB 의 metric data = $23 / 월
Compute:
- Mimir 의 인스턴스 (Ingester · Querier · Compactor)
- 메모리 의 크기 = active series × bytes
S3 비용 함정
사고 case:
- Loki 의 chunk 가 S3 에 저장
- 너무 많은 작은 chunk → S3 의 *PUT request 비용* 폭증
- GET 의 비용 도 누적
해결:
- chunk 크기 의 충분 (1.5 MB · 2 hour 권장)
- S3 의 Intelligent Tiering
- 오래된 chunk 의 Glacier 의 자동 transition
Query 비용 (BigQuery 와 비교)
BQ 가 datasource 시:
- Grafana panel 의 한 query = BQ scan
- Dashboard 의 자동 refresh = 매번 scan
- 30초 마다 refresh × 10 panel = 30분에 600 query
→ 작은 dashboard 도 큰 비용
해결:
- Materialized view (6편)
- Query result cache (Grafana 의 query caching)
- Default refresh = 5분 (자주 refresh X)
비용 모니터 자동
# 매일 자동 cost 의 audit
def daily_cost_audit():
# Grafana Cloud
cloud_usage = get_grafana_cloud_usage()
# AWS
s3_cost = get_aws_cost_explorer("s3")
ec2_cost = get_aws_cost_explorer("ec2")
# 비용 보고
daily_total = cloud_usage['cost'] + s3_cost + ec2_cost
if daily_total > DAILY_BUDGET:
slack_alert(
f"⚠️ Daily observability cost: ${daily_total:.2f} "
f"> budget ${DAILY_BUDGET:.2f}"
)
# 항상 Slack 보고
post_to_slack("#cost-monitor", format_cost_report(...))
Plugin 의 보안 사고
CVE 의 위험
CVE(공개된 보안 취약점 일련번호)는 plugin 채택 기준의 1순위 지표예요.
Grafana Plugin 의 CVE 사례:
- Grafana 9 의 path traversal CVE
- Image renderer 의 SSRF
- Authentication bypass
- XSS · SQL injection
영향:
- 인증 우회 → 모든 dashboard 접근
- SSRF → 내부 네트워크 의 정보 누출
- Cookie · token 의 탈취
Plugin 의 의식
공식 Grafana plugin:
- Grafana Labs 의 maintain
- CVE 패치 빠름
- 보안 audit 받음
Community plugin:
- 누구나 등록 가능
- maintain 끊김 가능
- CVE 가 발견 안 됨
Enterprise plugin:
- Grafana Labs 의 maintain
- SLA 있음
회피 패턴
1. Plugin upgrade 의 자동 알림
- GitHub watch 또는 RSS
2. 주기적 CVE review
- 매월 모든 plugin 의 CVE 확인
- CVE 발견 → 즉시 patch
3. Plugin 의 origin 의식
- Community plugin = 신중
- 가능 시 alternative · 직접 개발
4. Production 의 plugin 제한
- 표준 plugin 만 허용
- 새 plugin = security review 후
Supply Chain Attack
사례:
- npm package 의 hijack
- Plugin 의 malicious update
- "신뢰 의 사슬" 의 약한 link
회피:
- Plugin signing 의 검증
- 알려진 hash 의 검증
- Production 의 air-gapped 옵션
Alert 폭주 — 광범위 사고
사고 case: AWS Region Down
AWS us-east-1 의 outage:
→ 1000 instance 모두 down
→ 1000 alert 동시 fire
→ Slack 의 1000 메시지
→ PagerDuty 의 1000 page
→ oncall 의 무력감
원인 분석 늦음 → 진짜 사고 의 대응 늦음
회피 — Inhibition 설계
Inhibition(상위 alert가 켜지면 하위 alert를 자동으로 잠재우는 규칙)이 폭주를 잡는 표준이에요.
# Inhibition 의 표준
inhibit_rules:
# Region 전체 down → 자식 alert mask
- source_match:
alertname: AwsRegionDown
target_match_re:
alertname: ".*"
equal: [region]
# Cluster down → 그 cluster 의 모든 alert mask
- source_match:
alertname: ClusterDown
target_match_re:
alertname: ".*"
equal: [cluster]
# Service down → 그 service 의 latency · error alert mask
- source_match:
alertname: ServiceDown
target_match_re:
alertname: "Service(Latency|ErrorRate)"
equal: [service]
회피 — Aggregation Alert
# 단일 instance alert X
# Service 전체 의 aggregate alert O
# 나쁨: 각 instance 의 alert (100 instance = 100 alert)
- alert: InstanceHighCpu
expr: cpu_usage > 80
by: (instance)
# 좋음: service 전체 의 percentage alert (1 alert)
- alert: ServiceHighCpuRatio
expr: |
(
count(cpu_usage > 80) by (service) /
count(cpu_usage) by (service)
) > 0.5
Configuration Drift
Drift 의 의미
Drift(의도된 설정과 실제 시스템 상태의 어긋남)는 운영의 잠재적 가장 큰 미스터리원이에요.
의도된 config:
- Git 의 Terraform · Provisioning 의 file
- "이게 우리 의 설정"
실제 시스템:
- UI 의 수동 변경
- 긴급 시 의 임시 수정
- 사람 마다 다른 변경
→ Git ≠ 실제 → drift
Drift 의 위험
- 재해 복구 시 git 의 config 으로 복원 → 실제 와 다름
- Audit 시 의 의도 안 한 변경 발견
- 사고 시 의 "왜 이 alert 박혔지?" 의 미스터리
- 사람 의 의식 분산
회피 — editable: false
# Provisioning config
datasources:
- name: Prometheus
editable: false ← UI 에서 수정 시도 = error
dashboards:
- name: 'company-dashboards'
editable: false
회피 — Drift Detection
# 주기적 git diff (CI/CD job)
def detect_drift():
# Grafana 의 현재 dashboard 추출
actual_dashboards = grafana_api.list_dashboards()
# Git 의 dashboard
git_dashboards = load_dashboards_from_git()
# 비교
diff = compare(actual_dashboards, git_dashboards)
if diff.has_changes:
slack_alert(
f"⚠️ Drift detected: {len(diff.changes)} 변경\n"
f"{format_diff(diff)}"
)
# 자동 복원 (선택)
if AUTO_REVERT:
grafana_api.apply(git_dashboards)
매시간 cron으로 drift를 자동 감지해요.
Backup · DR 의 실패
DR(Disaster Recovery, 재해 복구)은 backup만으로 끝나지 않아요. 시험까지가 한 세트예요.
Backup 만 하고 시험 안 함
일반 case:
- 매일 backup 자동 (S3 의 daily)
- 1년 동안 backup 안 시험
- 사고 때 복원 시도 → 실패 (backup 손상 · format 변경 · 권한 부족)
→ Backup 있어도 의미 없음
DR 의 주기적 시험
매 분기:
□ Backup 의 download
□ Test environment 에 restore
□ Dashboard 의 query 검증
□ Alert rule 의 검증
□ User · Team 권한 검증
□ 시험 결과 docs
매년:
□ 실 사고 시뮬레이션 (1 시간 의 RTO)
□ Tabletop exercise (모의 사고)
□ Runbook 의 review
사고 case: 잘못된 Backup
사고:
- Backup script 가 dashboard 만 백업
- Alert · Notification Policy · Contact Point 백업 X
- 1 년 후 사고 시 복원 → alert 모두 손실
- 새벽 의 alert 안 옴 → 사용자 영향 의 늦은 감지
해결:
- 전체 export 의 backup (Terraform plan 기반)
- 또는 Grafana 의 Migration API 활용
RTO · RPO
RTO (Recovery Time Objective):
사고 후 시스템 복원 까지 의 시간 목표
예: 4 시간 (4h 안에 모든 monitoring 복원)
RPO (Recovery Point Objective):
허용 가능 한 데이터 손실 시간
예: 24 시간 (마지막 백업 후 24h 의 변경 손실 허용)
설정 의 design:
RPO 가 짧으면 → backup 의 빈도 높여야 (비용 큼)
RTO 가 짧으면 → hot standby · multi-region
Multi-region · Clock Skew
Multi-region 의 latency
회사 가 us-east-1 · asia-northeast3 의 cluster:
- 각 region 의 Prometheus 의 metric
- 한 Grafana 의 view
문제:
Grafana 가 us-east-1 → Prometheus asia-northeast3 query:
network latency 200ms
너무 많은 panel = 누적 latency
해결:
- 각 region 의 Prometheus + 통합 Mimir (글로벌)
- 또는 region 별 Grafana + Cross-region link
- Grafana Cloud 의 multi-region 활용
Clock Skew
Clock Skew(노드 간 시각이 어긋난 상태)는 trace의 음수 duration 같은 기괴한 증상으로 드러나요.
사고 case:
- 100 instance 의 NTP 동기화 X
- 일부 instance 의 시각 = 30 초 어긋남
- Trace 의 timeline 의 음수 duration
- Metric 의 시각 불일치
해결:
- 모든 instance 의 NTP (chrony · ntpd)
- Kubernetes 의 NodeLocalDNS · 자동 sync
- Monitoring: node_time_seconds 의 회사 표준 시각 과 비교
Cross-region Replication 의 함정
사고:
- Mimir 의 multi-region replication
- us-east-1 의 데이터 → asia-northeast3 로 자동 replication
- 네트워크 비용 폭증 + 데이터 일관성 문제
해결:
- Replication 의 의도 명시
- 데이터 의 sharding (region 별 namespace)
- "필요한 데이터 만" replication
Compliance · Audit 사고
GDPR · PIPA 의 의식
GDPR(EU 개인정보 보호 규정) · PIPA(한국 개인정보 보호법)은 관측 데이터에도 그대로 적용돼요. PII(개인식별정보, Personally Identifiable Information)가 로그·메트릭에 박혀 들어가는 게 가장 흔한 위반이에요.
관측 데이터 의 PII:
- log 안 의 email · 전화 · 신용카드
- metric label 의 user_id (직접 식별)
- trace 의 user.email attribute
→ 이런 데이터 가 Grafana 에 저장 = 컴플라이언스 위반
회피 — Pre-ingestion Redaction
# Alloy 의 redaction stage
loki.process "redact" {
forward_to = [loki.write.default.receiver]
stage.regex {
expression = `(?P<email>[\w.+-]+@[\w-]+\.[\w.-]+)`
}
stage.replace {
expression = `(?P<email>[\w.+-]+@[\w-]+\.[\w.-]+)`
replace = "REDACTED_EMAIL"
}
# 신용카드
stage.replace {
expression = `\b\d{4}-\d{4}-\d{4}-\d{4}\b`
replace = "REDACTED_CC"
}
}
사고 case: 의도 안 한 PII
사고:
- Application 의 새 endpoint 의 PII 가 path 에 박힘
- /reset-password?email=alice@company.com
- Loki 의 1년 retention → 모든 사용자 의 email
- 컴플라이언스 audit → 위반
해결:
- Application 의 audit (PII 의 URL 사용 금지)
- Loki 의 정기 scan (regex 의 매칭)
- 발견 시 즉시 redaction stage 추가 + 데이터 삭제
Audit Log 의 의미
Enterprise 의 audit log:
- 누가 dashboard 의 sensitive data 봤나
- 누가 권한 변경 했나
- 누가 alert 의 silence 박았나
→ 사고 시 의 추적 자료
→ 컴플라이언스 의 의무 (GDPR 의 "right to be informed")
Vendor Lock-in 의 회피
Lock-in 의 의식
Grafana 의 lock-in 위험:
- Grafana 의 dashboard JSON format
- PromQL · LogQL · TraceQL 의 query language
- Enterprise plugin 의 vendor-specific
→ 5 년 후 의 vendor 변경 시 의 비용
회피 - 표준 의식
Open standard 의 우선:
- OpenTelemetry (모든 vendor 호환)
- Prometheus exposition format (de-facto 표준)
- SQL · OpenSearch · ClickHouse 의 표준 query
Grafana 의 alternative:
- Datadog (벤더)
- New Relic (벤더)
- Splunk (벤더 + 더 비싸)
- PostHog (오픈소스 product analytics)
- 자체 dashboard
Migration Path
표준 의 의식 의 대가:
- Vendor 의 unique 기능 의 거부 (단순한 정의 만 사용)
- Vendor 마다 의 약간 의 변환 필요
장기적:
- Grafana → Datadog · 반대 의 6 개월 의 migration 가능
- Lock-in 의 약함
운영 1 년 의 KPI
운영 의 metric
관측 시스템 자체 의 health:
1. Availability
- Grafana 의 uptime (99.9% 목표)
- Datasource 의 healthy
- Alert 의 delivery latency
2. Cost
- 월간 비용 의 trend
- 데이터 unit 의 비용 ($ / GB · $ / 1M sample)
3. Adoption
- 활성 사용자 수
- dashboard 의 사용 빈도
- alert 의 useful 비율
4. Incident Response
- Mean Time To Detection (MTTD)
- Mean Time To Recovery (MTTR)
- Alert false positive rate
5. Data Quality
- Cardinality 의 trend
- 데이터 누락 의 빈도
- Backfill 의 빈도
매월 review
□ 비용 trend (예상 vs 실제)
□ 활성 사용자 · dashboard 의 사용
□ Top 10 noisy alert
□ Most viewed dashboard
□ Cardinality 의 trend
□ Plugin 의 CVE · upgrade
□ Backup 의 시험 결과
□ Audit log 의 review
함정 정리 (총정리)
1. Cardinality 폭발
- dynamic 값 = label 금지
- Recording Rule 의 aggregation
- 정기 cardinality 모니터
2. Cost 폭증
- Cloud 의 한도 alert
- Mimir · Loki · BQ 의 비용 의식
- 매일 자동 audit
3. Plugin CVE
- 공식 plugin 우선
- 주기적 CVE review
- Production 의 plugin 제한
4. Alert 폭주
- Inhibition 의 표준
- Aggregation alert (instance X service)
- Multi-window burn rate
5. Configuration Drift
editable: false강제- Drift detection 자동
- Git 의 single source of truth
6. Backup · DR 실패
- 정기 시험 (분기 마다)
- 전체 backup (dashboard 만 X)
- RTO · RPO 의 명시
7. Multi-region Latency
- Region 별 stack + 글로벌 통합
- Cross-region replication 의식
- Cost vs latency 의 trade-off
8. Clock Skew
- NTP 의 강제
- node_time_seconds 의 모니터
- Trace 의 negative duration 의 감지
9. Compliance · PII
- Pre-ingestion redaction
- 정기 scan
- Audit log 의 활성
10. Vendor Lock-in
- Open standard 우선 (OpenTelemetry · Prometheus format)
- Migration path 의 design
- Vendor unique 의 신중
시험 직전 한 번 더 — 운영 함정 압축 노트
Cardinality
prometheus_tsdb_head_series·topk(10, count by ...)- dynamic 값 = label X (path · user_id · request_id)
- Recording Rule 의 aggregation
- 정기 모니터
Cost
- Cloud — usage alert · monthly estimate
- Mimir/Loki — chunk size · retention
- BQ — materialized view · query cache
- 매일 자동 audit · Slack 알림
Plugin
- 공식 vs community vs Enterprise
- CVE 의 자동 알림
- 주기적 review · supply chain 의식
Alert 폭주
- Inhibition (region · cluster · service down)
- Aggregation alert (count by service)
- Multi-window burn rate
- Mute Timings
Drift
editable: false강제- 매시 git diff · 자동 알림
- 자동 revert 옵션
Backup · DR
- 매일 backup + 분기 시험
- 전체 export (dashboard + alert + datasource + team)
- RTO · RPO 의 명시
- Tabletop exercise
Multi-region
- region 별 Prometheus + 글로벌 Mimir
- Cross-region replication 의식
- NTP 의 강제 · clock skew 의 알림
Compliance
- Pre-ingestion redaction (email · 신용카드 · 주민번호)
- 정기 PII scan
- Audit Log 의 export · review
- GDPR · PIPA 의 의식
Vendor Lock-in
- OpenTelemetry · Prometheus format
- 표준 query 의식 (vendor-unique 신중)
- Migration path 의 design
운영 KPI
- Availability (uptime · delivery)
- Cost (월 trend · unit cost)
- Adoption (사용자 · dashboard 의 사용)
- Incident Response (MTTD · MTTR · false positive)
- Data Quality (cardinality · 누락 · backfill)
매월 review
- 비용 · 사용자 · noisy alert · viewed dashboard · cardinality · CVE · backup · audit
공식 문서: Prometheus 운영 · Grafana 운영 에서 더 깊은 운영 가이드를 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 3편 — Loki + LogQL 깊이 (Label Index · 비용 효율)
- 4편 — Tempo + TraceQL · 분산 Trace 깊이
- 5편 — Dashboard · Panel · Variable 깊이
- 6편 — Alerting · Notification · SLO 깊이
- 7편 — Cloud · Enterprise · IaC 깊이
다음 글: