19. 관측 가능성과 모니터링

19. 관측 가능성과 모니터링

난이도: ⭐⭐⭐⭐

개요

관측 가능성(Observability)은 시스템의 외부 출력으로부터 내부 상태를 이해할 수 있는 능력입니다. 이 레슨에서는 관측 가능성의 세 가지 기둥(three pillars)인 메트릭(metrics), 로그(logs), 트레이스(traces)와 더불어 대규모 분산 시스템 모니터링을 위한 실용적인 도구 및 프레임워크를 다룹니다.


목차

  1. 관측 가능성의 기초
  2. 메트릭과 시계열 데이터
  3. 대규모 로깅
  4. 분산 트레이싱
  5. 경보와 SLO
  6. OpenTelemetry
  7. 연습 문제

1. 관측 가능성의 기초

1.1 관측 가능성의 세 가지 기둥

┌─────────────────────────────────────────────────────────────────┐
              Three Pillars of Observability                      
                                                                 
  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          
     Metrics           Logs           Traces               
                                                          
   "What is     │  │ "What          "What path   │          │
    happening?" │  │  happened?"      did it                
                                    take?"      │          │
  ├──────────────┤  ├──────────────┤  ├──────────────┤          
   Numeric         Structured      Request-               
   time-series     events with     scoped                 
   data            context         causality              
  ├──────────────┤  ├──────────────┤  ├──────────────┤          
   Low cost        Medium cost     Higher cost            
   per signal      per signal      per signal             
  ├──────────────┤  ├──────────────┤  ├──────────────┤          
   Prometheus      ELK Stack       Jaeger                 
   Grafana         Loki            Zipkin                 
   Datadog         Fluentd         Tempo                  
  └──────────────┘  └──────────────┘  └──────────────┘          
                                                                 
             Correlation via Trace IDs and Labels                
                                                                 
└─────────────────────────────────────────────────────────────────┘

1.2 모니터링 vs 관측 가능성

┌─────────────────────────────┬───────────────────────────────────┐
 Monitoring                   Observability                      
├─────────────────────────────┼───────────────────────────────────┤
 Known unknowns               Unknown unknowns                   
 "Is CPU > 90%?"              "Why is latency high?"             
 Dashboard-driven             Exploration-driven                 
 Predefined alerts            Ad-hoc investigation               
 Reactive                     Proactive                          
 Works for simple systems     Essential for distributed systems  
└─────────────────────────────┴───────────────────────────────────┘

2. 메트릭과 시계열 데이터

2.1 메트릭 유형

┌─────────────────────────────────────────────────────────────────┐
│              Four Golden Signals (Google SRE)                    │
│                                                                 │
│  1. Latency      Time to serve a request                       │
│  2. Traffic      Demand on the system (RPS)                    │
│  3. Errors       Rate of failed requests                       │
│  4. Saturation   How "full" the system is                      │
│                                                                 │
│              RED Method (for microservices)                      │
│                                                                 │
│  1. Rate      Requests per second                              │
│  2. Errors    Failed requests per second                       │
│  3. Duration  Distribution of request latencies                │
│                                                                 │
│              USE Method (for infrastructure)                     │
│                                                                 │
│  1. Utilization   % of resource busy                           │
│  2. Saturation    Queue depth, pending work                    │
│  3. Errors        Error count                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.2 Prometheus 아키텍처

┌─────────────────────────────────────────────────────────────────┐
│              Prometheus Ecosystem                                │
│                                                                 │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐       │
│  │ Application │     │ Application │     │   Node      │       │
│  │  /metrics   │     │  /metrics   │     │  Exporter   │       │
│  └──────┬──────┘     └──────┬──────┘     └──────┬──────┘       │
│         │                   │                   │               │
│         └───────────┬───────┘───────────────────┘               │
│                     │  scrape (pull)                             │
│              ┌──────▼──────┐                                    │
│              │  Prometheus  │                                    │
│              │   Server     │──────▶ AlertManager ──▶ PagerDuty │
│              │  (TSDB)      │               │        Slack      │
│              └──────┬──────┘               │        Email      │
│                     │                                           │
│              ┌──────▼──────┐                                    │
│              │   Grafana    │                                    │
│              │ (Dashboard)  │                                    │
│              └─────────────┘                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.3 Prometheus 메트릭 유형

# Counter — monotonically increasing (requests, errors)
http_requests_total{method="GET", path="/api/users", status="200"} 12345

# Gauge — goes up and down (temperature, queue size)
process_memory_bytes 1073741824

# Histogram — samples in configurable buckets
http_request_duration_seconds_bucket{le="0.1"} 1000
http_request_duration_seconds_bucket{le="0.5"} 1200
http_request_duration_seconds_bucket{le="1.0"} 1250
http_request_duration_seconds_count 1280
http_request_duration_seconds_sum 320.5

# Summary — similar to histogram with quantiles
http_request_duration_seconds{quantile="0.5"} 0.042
http_request_duration_seconds{quantile="0.9"} 0.087
http_request_duration_seconds{quantile="0.99"} 0.235

2.4 PromQL 쿼리

# Request rate over 5 minutes
rate(http_requests_total[5m])

# Error rate percentage
sum(rate(http_requests_total{status=~"5.."}[5m])) /
sum(rate(http_requests_total[5m])) * 100

# 95th percentile latency
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# Top 5 endpoints by request rate
topk(5, sum by (path)(rate(http_requests_total[5m])))

# Memory usage percentage
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/ node_memory_MemTotal_bytes * 100

# Predict disk full in 4 hours
predict_linear(node_filesystem_free_bytes[1h], 4*3600) < 0

3. 대규모 로깅

3.1 구조화된 로깅

{
  "timestamp": "2026-02-15T10:30:00Z",
  "level": "ERROR",
  "service": "order-service",
  "trace_id": "abc123def456",
  "span_id": "789ghi",
  "user_id": "u-42",
  "message": "Failed to process order",
  "error": "PaymentDeclined",
  "order_id": "ord-789",
  "amount": 129.99,
  "duration_ms": 1250
}

3.2 ELK 스택 아키텍처

┌─────────────────────────────────────────────────────────────────┐
│              ELK Stack (Elastic Stack)                           │
│                                                                 │
│  Applications                                                   │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                           │
│  │ Service  │ │ Service  │ │ Service  │                           │
│  │    A     │ │    B     │ │    C     │                           │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘                           │
│       │            │            │                               │
│       └────────────┼────────────┘                               │
│                    ▼                                            │
│  ┌──────────────────────────────────┐                           │
│  │     Filebeat / Fluentd           │  Log Shippers             │
│  └──────────────┬───────────────────┘                           │
│                 ▼                                               │
│  ┌──────────────────────────────────┐                           │
│  │        Logstash / Kafka          │  Processing / Buffer      │
│  │   (parse, filter, enrich)        │                           │
│  └──────────────┬───────────────────┘                           │
│                 ▼                                               │
│  ┌──────────────────────────────────┐                           │
│  │       Elasticsearch              │  Storage & Search         │
│  │   (index, full-text search)      │                           │
│  └──────────────┬───────────────────┘                           │
│                 ▼                                               │
│  ┌──────────────────────────────────┐                           │
│  │          Kibana                  │  Visualization            │
│  │   (dashboards, queries)          │                           │
│  └──────────────────────────────────┘                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.3 Grafana Loki (경량 대안)

┌─────────────────────────────────────────────────────────────────┐
│              Grafana Loki Stack                                  │
│                                                                 │
│   Does NOT index log content (only labels)                     │
│   Much cheaper storage than Elasticsearch                      │
│   LogQL query language (similar to PromQL)                     │
│   Ideal for Kubernetes environments                            │
│                                                                 │
│  Promtail ──▶ Loki ──▶ Grafana                                  │
│  (agent)      (store)   (query/visualize)                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
# LogQL examples
{service="order-service"} |= "error"                # contains "error"
{service="order-service"} | json | status >= 500     # JSON parsing + filter
{service="order-service"} | json | line_format "{{.message}}"
rate({service="order-service"} |= "error" [5m])      # error rate

3.4 로그 레벨과 모범 사례

Level     When to Use
─────────┼──────────────────────────────────────────
TRACE     Very fine-grained (usually disabled)
DEBUG     Development troubleshooting
INFO      Normal operations, business events
WARN      Recoverable issues, degraded service
ERROR     Failures requiring attention
FATAL     Application cannot continue

모범 사례: - 일반 텍스트보다 구조화된 로깅(JSON) 사용 - 모든 로그에 상관 관계 ID(trace_id) 포함 - 적절한 레벨로 로깅 (프로덕션에서 INFO 스팸 금지) - 보존 정책 설정 (7일 hot, 30일 warm, 90일 cold) - 민감한 데이터 로깅 금지 (PII, 자격 증명)


4. 분산 트레이싱

4.1 트레이스 구조

┌─────────────────────────────────────────────────────────────────┐
│              Distributed Trace Example                           │
│                                                                 │
│  Trace ID: abc-123-def                                          │
│                                                                 │
│  ┌────────────────────────────────────────────────────┐         │
│  │ Span A: API Gateway (200ms)                        │         │
│  │  ├─────────────────────────┐                       │         │
│  │  │ Span B: Auth (30ms)     │                       │         │
│  │  └─────────────────────────┘                       │         │
│  │         ├──────────────────────────────┐            │         │
│  │         │ Span C: Order Service (120ms)│            │         │
│  │         │  ├───────────────┐           │            │         │
│  │         │  │ Span D: DB   │           │            │         │
│  │         │  │ Query (15ms) │           │            │         │
│  │         │  └───────────────┘           │            │         │
│  │         │       ├──────────────┐       │            │         │
│  │         │       │ Span E:     │       │            │         │
│  │         │       │ Payment     │       │            │         │
│  │         │       │ (80ms)      │       │            │         │
│  │         │       └──────────────┘       │            │         │
│  │         └──────────────────────────────┘            │         │
│  └────────────────────────────────────────────────────┘         │
│                                                                 │
│  0ms            100ms           200ms                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.2 트레이싱 시스템

┌────────────────┬──────────────────────────────────────────────────┐
 Tool            Description                                      
├────────────────┼──────────────────────────────────────────────────┤
 Jaeger          Open source, Uber-originated, CNCF project       
 Zipkin          Open source, Twitter-originated                   
 Grafana Tempo   Cost-efficient, only stores trace IDs             
 AWS X-Ray       Managed service for AWS workloads                 
 Datadog APM     Commercial, integrated with metrics/logs          
└────────────────┴──────────────────────────────────────────────────┘

4.3 컨텍스트 전파

┌─────────────────────────────────────────────────────────────────┐
│              W3C Trace Context Headers                           │
│                                                                 │
│  HTTP Request:                                                  │
│  ┌─────────────────────────────────────────────────────┐        │
│  │ traceparent: 00-{trace-id}-{span-id}-{flags}       │        │
│  │ tracestate: vendor1=value1,vendor2=value2            │        │
│  └─────────────────────────────────────────────────────┘        │
│                                                                 │
│  Example:                                                       │
│  traceparent: 00-abc123def456-789ghi012-01                      │
│  ┌──────┬──────────────┬────────────┬──────┐                    │
│  │ ver  │  trace-id    │  span-id   │flags │                    │
│  │ 00   │ abc123def456 │ 789ghi012  │ 01   │                    │
│  └──────┴──────────────┴────────────┴──────┘                    │
│                                                                 │
│  Service A ──(traceparent)──▶ Service B ──(traceparent)──▶ C    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.4 샘플링 전략

┌─────────────────────────────────────────────────────────────────┐
              Sampling Strategies                                 
                                                                 
  1. Head-based sampling                                         
     Decision at trace start: sample 10% of all requests         
     + Simple, low overhead                                      
      May miss important traces                                 
                                                                 
  2. Tail-based sampling                                         
     Decision after trace completes: keep errors + slow traces   
     + Captures interesting traces                               
      Higher memory usage (buffer all spans)                    
                                                                 
  3. Rate-limited sampling                                       
     Keep N traces per second per service                        
     + Predictable cost                                          
      May miss bursts                                           
                                                                 
└─────────────────────────────────────────────────────────────────┘

5. 경보와 SLO

5.1 SLI / SLO / SLA

┌─────────────────────────────────────────────────────────────────┐
│              SLI  SLO  SLA                                    │
│                                                                 │
│  SLI (Service Level Indicator)                                  │
│  ├── What you measure                                           │
│  ├── "Proportion of requests < 200ms"                           │
│  └── "Proportion of requests returning 2xx"                     │
│                                                                 │
│  SLO (Service Level Objective)                                  │
│  ├── Internal target                                            │
│  ├── "99.9% of requests < 200ms over 30 days"                  │
│  └── "99.95% availability per month"                            │
│                                                                 │
│  SLA (Service Level Agreement)                                  │
│  ├── External contract with consequences                        │
│  └── "99.9% uptime or service credits issued"                  │
│                                                                 │
│  Rule: SLO should be stricter than SLA                          │
│        (e.g., SLO = 99.95% when SLA = 99.9%)                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.2 오류 예산

┌─────────────────────────────────────────────────────────────────┐
│              Error Budget Concept                                │
│                                                                 │
│  SLO = 99.9% availability                                      │
│  Error Budget = 100% - 99.9% = 0.1%                            │
│                                                                 │
│  Per 30 days: 0.1% × 30 × 24 × 60 = 43.2 minutes of downtime │
│                                                                 │
│  Error Budget Remaining:                                        │
│  ┌────────────────────────────────────────────────────┐         │
│  │████████████████████████████████████░░░░░░░░░░░░░░░│         │
│   70% remaining              30% consumed                    │
│  └────────────────────────────────────────────────────┘         │
│                                                                 │
│  Policy:                                                        │
│   Budget > 50%: Deploy freely, experiment                      │
│   Budget 20-50%: Careful deployments, extra testing            │
│   Budget < 20%: Freeze features, focus on reliability          │
│   Budget = 0%: Emergency freeze until budget replenishes       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.3 경보 모범 사례

# Prometheus alerting rules example
groups:
  - name: slo-alerts
    rules:
      # Multi-window, multi-burn-rate alert
      - alert: HighErrorRate
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[5m]))
            / sum(rate(http_requests_total[5m]))
          ) > 14.4 * 0.001  # 14.4x burn rate for 5m window
          AND
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
            / sum(rate(http_requests_total[1h]))
          ) > 14.4 * 0.001
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Error rate exceeds SLO burn rate"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P99 latency above 1s"

경보 안티 패턴: - 경보 피로(Alert fatigue): 실행 불가능한 경보가 너무 많음 - 런북(Runbook) 누락: 해결 단계 없는 경보 - 소유자 없음: "모두"에게 전달되는 경보 - 임계값만 사용: 추세 분석 없이 정적 임계값만 사용


6. OpenTelemetry

6.1 OpenTelemetry 아키텍처

┌─────────────────────────────────────────────────────────────────┐
│              OpenTelemetry (OTel) Architecture                   │
│                                                                 │
│  Application                                                    │
│  ┌─────────────────────────────────────────────────┐            │
│  │  OTel SDK                                       │            │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐        │            │
│  │  │ Traces   │ │ Metrics  │ │  Logs    │        │            │
│  │  │ API      │ │ API      │ │ API      │        │            │
│  │  └────┬─────┘ └────┬─────┘ └────┬─────┘        │            │
│  │       └─────────────┼────────────┘              │            │
│  │                     ▼                           │            │
│  │              OTLP Exporter                      │            │
│  └─────────────────────┬───────────────────────────┘            │
│                        ▼                                        │
│  ┌─────────────────────────────────────────────────┐            │
│  │           OTel Collector                        │            │
│  │  ┌────────┐  ┌────────────┐  ┌──────────┐      │            │
│  │  │Receivers│  │ Processors │  │ Exporters│      │            │
│  │  │ OTLP   │→│ Batch      │→│ Jaeger   │      │            │
│  │  │ Zipkin │  │ Filter     │  │ Prometheus│      │            │
│  │  │ Kafka  │  │ Tail-sample│  │ Loki     │      │            │
│  │  └────────┘  └────────────┘  └──────────┘      │            │
│  └─────────────────────────────────────────────────┘            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6.2 OTel Collector 설정

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1000
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  prometheus:
    endpoint: 0.0.0.0:8889
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]

6.3 계측 예제 (Python)

from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Setup tracing
provider = TracerProvider()
provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="localhost:4317"))
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

# Setup metrics
meter = metrics.get_meter(__name__)
request_counter = meter.create_counter(
    "http.requests", description="Total HTTP requests"
)
request_duration = meter.create_histogram(
    "http.request.duration", description="Request duration in ms"
)

# Usage
@tracer.start_as_current_span("process_order")
def process_order(order_id):
    request_counter.add(1, {"endpoint": "/orders", "method": "POST"})

    with tracer.start_as_current_span("validate_order") as span:
        span.set_attribute("order.id", order_id)
        validate(order_id)

    with tracer.start_as_current_span("charge_payment"):
        charge(order_id)

7. 연습 문제

문제 1: 마이크로서비스 플랫폼 모니터링 설계

20개의 마이크로서비스가 있는 이커머스 플랫폼의 관측 가능성 스택을 설계하세요.

주요 고려 사항: - 각 서비스에서 어떤 메트릭을 수집할 것인가? - 서비스 간 로그를 어떻게 상관시킬 것인가? - 트레이스에 대한 샘플링 전략은? - 체크아웃 서비스에 대한 SLO를 정의하세요.

예시 접근법:

메트릭 (각 서비스에 대한 RED):
- Rate: http_requests_total by service, method, status
- Errors: http_requests_total{status=~"5.."} / total
- Duration: http_request_duration_seconds histogram

로깅:
- 모든 로그에 trace_id를 포함한 구조화된 JSON
- Loki 또는 Elasticsearch를 통한 중앙화
- 보존: 7일 hot, 30일 warm, 90일 archive

트레이싱:
- 꼬리 기반 샘플링(Tail-based sampling): 모든 오류 + p99 지연시간 유지
- 헤드 기반: 정상 트래픽의 10% 샘플링
- 백엔드로 Jaeger 또는 Tempo

체크아웃 SLO:
- 가용성(Availability): 99.95% 성공률 (30일 윈도우)
- 지연시간(Latency): p99 < 2s, p50 < 500ms
- 오류 예산: ~21.6분/월

문제 2: 경보 설계

경보 피로를 피하는 경보 전략을 설계하세요.

예시 답변:

멀티 번-레이트(Multi-burn-rate) 경보:
- 1시간 내 2% 예산 소모  → 페이징 (critical)
- 6시간 내 5% 예산 소모 → 페이징 (warning)
- 3일 내 10% 예산 소모 → 티켓 (low)

라우팅:
- Critical → PagerDuty → 온콜 엔지니어
- Warning  → Slack #alerts → 팀 리드
- Low      → Jira ticket → 백로그

모든 경보에는 다음이 포함되어야 함:
- 런북 링크
- 대시보드 링크
- 예상 영향
- 제안된 해결 방법

문제 3: 관측 가능성 비용 최적화

팀이 관측 가능성에 월 $50K를 지출합니다. 비용을 40% 절감하세요.

예시 답변:

1. 메트릭: 사용하지 않는 메트릭 삭제 (대시보드 감사)
   - 카디널리티 감소 (라벨 값 줄이기)
   - 중요하지 않은 서비스의 스크레이프 간격 증가 (15s → 60s)

2. 로그: ELK에서 Loki로 전환
   - 전체 로그 콘텐츠 인덱싱 중지
   - 로그 상세도 감소 (프로덕션에서 DEBUG → INFO)
   - 보존 기간 단축 (규제 없는 데이터에 대해 90d → 30d)

3. 트레이스: 꼬리 기반 샘플링 구현
   - 오류 및 느린 트레이스 100% 유지
   - 성공한 트레이스의 1% 샘플링
   - Grafana Tempo 사용 (대규모에서 Jaeger보다 저렴)

4. 아키텍처:
   - OpenTelemetry Collector 자체 호스팅
   - 콜드 데이터에 객체 스토리지(S3) 사용
   - 컬렉터 레벨에서 메트릭 집계

다음 단계

참고 자료

to navigate between lessons