본문으로 건너뛰기

실전 고수 팁 — 모니터링 설계 전략

모니터링 도구를 잘 설치하는 것과, 모니터링을 잘 설계하는 것은 전혀 다른 이야기입니다. 수천 개의 알람이 울리는데 정작 중요한 장애를 놓치거나, 화려한 대시보드를 만들었지만 장애 시 아무 도움이 안 되는 상황은 현장에서 흔히 볼 수 있습니다. 이 챕터에서는 실제 프로덕션 환경에서 검증된 모니터링 설계 전략을 다룹니다.

알람 피로(Alert Fatigue) 줄이기

알람 피로란 너무 많은 알림에 노출된 팀이 정작 중요한 알람을 무시하게 되는 현상입니다. 2019년 Google의 SRE 보고서에 따르면, 한 번에 너무 많은 알람이 발생하면 엔지니어들이 알람을 끄거나 무시하는 경향이 있습니다.

알림 우선순위 분류 (Priority Matrix)

알람을 다음 4가지로 분류합니다.

우선순위정의예시대응 방식
P1 - Critical즉각 서비스 중단 영향전체 서버 다운, DB 연결 불가즉시 전화/SMS + 호출
P2 - High서비스 품질 심각하게 저하에러율 5% 초과, p99 응답시간 10초Slack + 15분 내 대응
P3 - Medium잠재적 문제, 즉각 영향 없음디스크 80% 초과, 메모리 경고Slack + 근무시간 내 대응
P4 - Low정보성, 추세 파악용배포 완료, 인증서 30일 전 만료일일 요약 리포트

임계값 설정 원칙

잘못된 임계값 설정은 알람 피로의 주요 원인입니다.

# 잘못된 예 — 절대값 기반 임계값 (노이즈가 많음)
- alert: HighResponseTime
expr: http_response_time_seconds > 0.5 # 항상 특정 시간대에 울림

# 올바른 예 — 동적 기준선(baseline) 대비 이상치 탐지
- alert: AnomalousResponseTime
expr: |
http_response_time_seconds
> on (job) group_left()
(avg_over_time(http_response_time_seconds[1h] offset 1w) * 2)
for: 5m
annotations:
summary: "Response time is 2x higher than same time last week"

# 올바른 예 — 증가율 기반
- alert: ResponseTimeSpike
expr: |
rate(http_request_duration_seconds_sum[5m])
/ rate(http_request_duration_seconds_count[5m])
> 2
for: 3m
labels:
severity: warning

임계값 설정의 황금 규칙:

  • for 절 필수: 순간적인 스파이크로 인한 오탐을 방지합니다. 최소 2~5분 지속 조건을 붙입니다.
  • 충분한 히스토리 데이터 수집 후 설정: 최소 2주치 데이터를 분석한 후 p95/p99를 기준으로 설정합니다.
  • 알람 수를 줄이는 것이 목표: 알람이 너무 많으면 절반을 끄고, 그래도 너무 많으면 또 절반을 끕니다.

SLO, SLI, SLA — 모니터링의 목표 설정

개념 정의

SLA(Service Level Agreement) 는 서비스 제공자와 고객 간에 체결하는 계약입니다. 예: "월 가동률 99.9% 미만 시 요금 환불 제공"

SLO(Service Level Objective) 는 팀 내부에서 달성하고자 하는 목표치입니다. SLA보다 약간 엄격하게 설정하여 완충 역할을 합니다. 예: "월 가동률 99.95% 유지"

SLI(Service Level Indicator) 는 SLO를 측정하기 위한 실제 지표입니다. 예: "성공적으로 처리된 요청 수 / 전체 요청 수"

SLA: 99.9%    (고객과의 계약, 어기면 패널티)

SLO: 99.95% (내부 목표, 여유 버퍼 포함)

SLI: 실제 측정값 (Prometheus 등으로 계산)

Prometheus로 SLI 계산

# prometheus/rules/slo.yml
groups:
- name: slo-rules
interval: 30s
rules:
# ─── SLI: 가용성 (Availability) ─────────────────────────────────────
# 5xx를 제외한 성공률 계산
- record: job:http_requests:success_rate5m
expr: |
sum(rate(http_requests_total{status!~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)

# ─── SLI: 지연시간 (Latency) ─────────────────────────────────────────
# p99 응답시간이 300ms 이하인 요청 비율
- record: job:http_requests:latency_ok_rate5m
expr: |
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) by (job)
/
sum(rate(http_request_duration_seconds_count[5m])) by (job)

# ─── SLO 위반 알람 ────────────────────────────────────────────────────
- alert: SLOAvailabilityBreach
expr: job:http_requests:success_rate5m < 0.9995 # 99.95% 미달
for: 5m
labels:
severity: critical
annotations:
summary: "SLO breach: availability {{ $value | humanizePercentage }}"
description: "Service {{ $labels.job }} availability dropped below 99.95%"

에러 버짓(Error Budget) 기반 알림 전략

에러 버짓은 SLO를 달성하면서 허용되는 실패의 총량입니다.

월 SLO: 99.9%
월 총 시간: 43,200분
허용 다운타임 = 43,200 × (1 - 0.999) = 43.2분

에러 버짓이 소진되면:
- 새 기능 배포 중단
- 안정화에 집중
#!/usr/bin/env python3
# error_budget_calculator.py
# 에러 버짓 소진율 계산기

def calculate_error_budget(slo_target: float, total_requests: int, failed_requests: int) -> dict:
"""
slo_target: SLO 목표값 (예: 0.999 = 99.9%)
total_requests: 전체 요청 수
failed_requests: 실패 요청 수
"""
allowed_failures = total_requests * (1 - slo_target)
actual_success_rate = (total_requests - failed_requests) / total_requests
error_budget_remaining = (allowed_failures - failed_requests) / allowed_failures * 100

return {
"slo_target": f"{slo_target * 100:.2f}%",
"actual_success_rate": f"{actual_success_rate * 100:.4f}%",
"allowed_failures": int(allowed_failures),
"actual_failures": failed_requests,
"error_budget_remaining": f"{error_budget_remaining:.1f}%",
"status": "OK" if error_budget_remaining > 0 else "EXHAUSTED"
}

if __name__ == "__main__":
# 예시: 이번 달 100만 요청 중 800건 실패
result = calculate_error_budget(
slo_target=0.999,
total_requests=1_000_000,
failed_requests=800
)
for key, value in result.items():
print(f"{key:30s}: {value}")

# 출력:
# slo_target : 99.90%
# actual_success_rate : 99.9200%
# allowed_failures : 1000
# actual_failures : 800
# error_budget_remaining : 20.0%
# status : OK

에러 버짓 소진율에 따른 알람 전략:

# prometheus/rules/error-budget.yml
groups:
- name: error-budget
rules:
# 에러 버짓 50% 소진 → 경고
- alert: ErrorBudgetBurnRateHigh
expr: |
(1 - job:http_requests:success_rate5m)
/ (1 - 0.999) > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Error budget burning 5x faster than expected"

# 에러 버짓 90% 소진 → 긴급
- alert: ErrorBudgetAlmostExhausted
expr: |
(1 - job:http_requests:success_rate5m)
/ (1 - 0.999) > 14.4 # 1시간 내 소진 예상
for: 2m
labels:
severity: critical
annotations:
summary: "Error budget will be exhausted within 1 hour at current rate"

골든 시그널 (Golden Signals)

Google SRE 팀이 정의한 모든 서비스 모니터링의 핵심 4개 지표입니다.

1. 지연시간 (Latency)

요청 처리에 걸리는 시간. 성공 요청과 실패 요청을 반드시 구분합니다. 에러는 매우 빠르게 반환될 수 있어, 에러를 포함하면 지연시간이 실제보다 낮게 측정됩니다.

# 지연시간 알람
- alert: HighLatency
expr: |
histogram_quantile(0.99,
rate(http_request_duration_seconds_bucket{status!~"5.."}[5m])
) > 1.0
for: 5m
annotations:
summary: "p99 latency for successful requests exceeds 1s"

2. 트래픽 (Traffic)

서비스에 가해지는 부하의 양. 초당 요청 수(RPS), 초당 바이트 수 등으로 측정합니다.

- alert: UnexpectedTrafficDrop
expr: |
rate(http_requests_total[5m])
< (avg_over_time(rate(http_requests_total[5m])[1h:5m]) * 0.5)
for: 5m
annotations:
summary: "Traffic dropped to less than 50% of 1-hour average — possible upstream issue"

3. 에러율 (Errors)

실패한 요청의 비율. 명시적 에러(5xx)와 암묵적 에러(잘못된 콘텐츠를 200으로 반환)를 모두 추적해야 합니다.

- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)
> 0.01 # 에러율 1% 초과
for: 3m

4. 포화도 (Saturation)

서비스가 얼마나 가득 찼는가. 리소스 한계에 가까워질수록 성능이 저하됩니다.

# CPU 포화도
- alert: HighCPUSaturation
expr: |
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m

# JVM 힙 포화도
- alert: JVMHeapPressure
expr: |
jvm_memory_used_bytes{area="heap"}
/ jvm_memory_max_bytes{area="heap"}
> 0.9
for: 5m
annotations:
summary: "JVM heap usage above 90% — GC pressure likely"

Nginx/Tomcat 핵심 모니터링 메트릭 체크리스트

Nginx 필수 메트릭

# nginx.conf — stub_status 모듈 활성화
server {
listen 8080;
location /nginx_status {
stub_status on;
allow 127.0.0.1;
allow 172.16.0.0/12; # Docker 네트워크
deny all;
}
}
메트릭의미알람 임계값
nginx_connections_active현재 활성 연결 수worker_connections의 80%
nginx_connections_waitingKeep-alive 대기 연결높으면 타임아웃 설정 검토
nginx_http_requests_total총 요청 수 (RPS 계산)비정상 급증/급락
5xx 에러율서버 에러 비율1% 초과 시 경고
nginx_ingress_upstream_latency업스트림 응답시간p99 > 500ms

Tomcat 필수 메트릭

<!-- server.xml — JMX 활성화 -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="200"
minSpareThreads="10"
maxConnections="10000"
acceptCount="100" />
메트릭의미알람 임계값
tomcat_threads_busy현재 처리 중인 스레드maxThreads의 80%
tomcat_threads_config_max최대 스레드 수참고값
tomcat_connections_current현재 연결 수maxConnections의 90%
tomcat_global_request_count처리된 요청 수RPS 추세 모니터링
tomcat_global_error_count에러 발생 수증가 추세 알람
JVM GC 시간 비율GC 오버헤드전체 시간의 5% 초과

대시보드 설계 원칙: 계층별 뷰

잘 설계된 대시보드는 "무엇이 잘못됐는가"를 빠르게 파악하게 해줍니다. 세 계층으로 구성합니다.

계층 1: 비즈니스 뷰 (Business View)
- 주문 전환율, 매출, 활성 사용자 수
- 비즈니스 임원이 보는 화면
- 5분 자동 갱신

계층 2: 애플리케이션 뷰 (Application View)
- API 엔드포인트별 성능 (Golden Signals)
- 에러율, 지연시간, RPS
- 개발팀/SRE가 보는 화면
- 1분 갱신

계층 3: 인프라 뷰 (Infrastructure View)
- CPU, 메모리, 디스크, 네트워크
- 서버별 세부 메트릭
- 인프라팀이 보는 화면
- 30초 갱신

Grafana 대시보드 변수 활용:

{
"templating": {
"list": [
{
"name": "environment",
"type": "custom",
"options": [
{"text": "Production", "value": "prod"},
{"text": "Staging", "value": "staging"}
]
},
{
"name": "instance",
"type": "query",
"query": "label_values(up{env=\"$environment\"}, instance)"
}
]
}
}

대시보드 설계 안티패턴:

  • 너무 많은 패널 (하나의 대시보드에 20개 초과 금지)
  • 색상 과다 사용 (신호등 원칙: 초록=정상, 노랑=경고, 빨강=위험)
  • 컨텍스트 없는 숫자 (현재값 단독 표시 금지, 반드시 추세선과 함께)

온콜(On-call) 체계 구성 팁

온콜 체계를 처음 구성할 때 흔히 저지르는 실수는 "모든 사람이 모든 알람을 받게" 설정하는 것입니다.

PagerDuty/OpsGenie 에스컬레이션 정책 예시:

1차 응답자 (Primary On-call):
- P1/P2 알람 즉시 수신
- 5분 내 미응답 시 → 2차 에스컬레이션

2차 응답자 (Secondary On-call):
- 1차 미응답 시 알람 수신
- 10분 내 미응답 시 → 팀장 에스컬레이션

팀장:
- 15분 이상 미대응 시 알람
- 비즈니스 임팩트 판단 후 추가 인력 소집

교대 주기: 1주 (2주 이상은 번아웃 위험)
온콜 보상: 명확한 인정/보상 체계 없으면 지속 불가

온콜 피로 최소화:

# 알람 발생 후 팀 회고 체크리스트
# 1. 이 알람이 진짜 액션이 필요했나? (Yes/No)
# 2. 알람 임계값이 적절했나?
# 3. 런북이 있었나? 도움이 됐나?
# 4. 자동화로 해결 가능한가?
# 5. 재발 방지를 위해 무엇을 해야 하나?

런북(Runbook) 작성 방법

런북은 장애 발생 시 대응 절차를 문서화한 가이드입니다. 새벽 3시에 잠에서 깬 엔지니어도 따라할 수 있도록 작성해야 합니다.

런북 필수 구성 요소:

# Runbook: High Error Rate on /api/orders

## 알람 조건
- 알람명: OrderServiceHighErrorRate
- 트리거: error_rate > 5% for 3 minutes

## 즉각 진단 명령어
```bash
# 1. 현재 에러율 확인
curl -s http://prometheus:9090/api/v1/query \
--data-urlencode 'query=rate(http_requests_total{status=~"5..",job="order-service"}[5m])' \
| jq '.data.result[].value[1]'

# 2. 최근 에러 로그 확인 (Kibana 쿼리)
# index: tomcat-error-*, 필터: log_level:ERROR AND logger:*OrderController*

# 3. DB 연결 상태 확인
docker exec -it order-service-db mysql -u root -p \
-e "SHOW STATUS LIKE 'Threads_connected'; SHOW PROCESSLIST;"

# 4. 최근 배포 이력 확인
kubectl rollout history deployment/order-service

가능한 원인과 해결 방법

원인증상해결 방법
DB 연결 풀 고갈DB 관련 에러, 응답시간 급증연결 풀 크기 증가, 슬로우 쿼리 확인
외부 API 장애특정 엔드포인트만 에러서킷 브레이커 확인, 폴백 활성화
메모리 부족OOM, GC 과부하인스턴스 재시작, 메모리 증설
코드 배포 실패배포 직후 에러 급증이전 버전 롤백

에스컬레이션

  • 15분 내 해결 불가 → 팀장 호출
  • DB 장애 → DBA 팀 호출
  • 외부 서비스 장애 → 해당 팀 비상 연락처 확인

## 로그 보존 정책 및 비용 최적화

로그 저장 비용은 생각보다 빠르게 증가합니다. 티어드 스토리지 전략이 필요합니다.

Hot Tier (최근 7일): SSD, 빠른 검색 → Elasticsearch Hot Node Warm Tier (730일): HDD, 느린 검색 허용 → Elasticsearch Warm Node Cold Tier (3090일): 오브젝트 스토리지 → S3/GCS (Elasticsearch Frozen Index) Archive (90일+): Glacier/Coldline → 장기 보관, 거의 미조회


**로그 레벨별 보존 기간 권장값:**

| 로그 종류 | 보존 기간 | 이유 |
|---|---|---|
| 액세스 로그 | 90일 | 보안 감사, 트래픽 분석 |
| 에러/예외 로그 | 1년 | 재현 분석, 규정 준수 |
| 결제/트랜잭션 | 5년+ | 회계 감사, 법적 요구사항 |
| 디버그 로그 | 7일 | 개발 용도, 장기 보관 불필요 |
| 보안 이벤트 | 2년+ | 보안 감사, 침해 사고 조사 |

**ILM 기반 비용 최적화:**

```bash
# 현재 인덱스별 디스크 사용량 확인
curl -u elastic:changeme123! \
"http://localhost:9200/_cat/indices?v&s=store.size:desc&h=index,store.size,pri.store.size,docs.count"

# 압축으로 스토리지 절약 (Warm tier에서 force merge)
curl -X POST "http://localhost:9200/nginx-access-2026.01.*/_forcemerge?max_num_segments=1" \
-u elastic:changeme123!

# 오래된 인덱스 수동 삭제 (ILM 정책이 없는 경우)
curl -X DELETE "http://localhost:9200/nginx-access-2025.12.*" \
-u elastic:changeme123!

비용 절감 팁:

  • 샘플링: 트래픽이 많은 서비스는 액세스 로그를 10~20%만 수집 (에러 로그는 100% 유지)
  • 필드 최소화: 불필요한 필드는 Logstash에서 제거 후 저장
  • 인덱스 압축: Warm/Cold 티어 이동 시 force merge로 세그먼트 수를 1로 줄임
  • 알람보다 SLO: 모든 것을 알람으로 해결하려 하지 말고, SLO 달성 여부만 집중 모니터링