Skip to content

✍️ 필사 모드: Observability 완벽 가이드 — Metrics, Logs, Traces, Profiling: Prometheus, OpenTelemetry, Jaeger, Loki, Grafana 모든 것 (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며 — 보지 못하면 운영할 수 없다

분산 시스템의 첫 번째 진실은 이렇다: 시스템이 항상 부분적으로 망가져 있다. 한 노드가 죽고, 한 네트워크가 끊기고, 한 디스크가 가득 차고, 한 라이브러리가 메모리 누수가 있다. 이 모든 것이 동시에 일어난다. 그리고 사용자는 "왜 느리지?"라고 묻는다.

이 질문에 답하려면 시스템의 모든 부분을 동시에 볼 수 있어야 한다. Observability는 그 능력이다. 시스템의 외부 출력 (metrics, logs, traces, profiles)을 통해 내부 상태를 추론할 수 있는 정도 — 이것이 학술적 정의이다.

이 글은 모던 observability의 모든 것을 다룬다. Prometheus의 시계열 DB부터 OpenTelemetry의 통합 표준, Jaeger의 distributed tracing, Loki의 label 인덱싱, Grafana 시각화, SRE의 SLI/SLO 모델, eBPF 기반 zero-instrumentation까지. 1,400줄에 달하지만 모든 절은 독립적으로 읽을 수 있다.

이 글은 Kubernetes 내부 구조 글의 자연스러운 후속이다. 클러스터를 띄웠으면 그 안에서 무슨 일이 일어나는지 봐야 한다. 이 글이 그 풍경을 그린다.


1. Monitoring과 Observability의 차이

1.1 옛날의 모니터링

Monitoring은 "알고 있는 문제"를 추적한다. CPU > 80%면 알림. 디스크 95% 차면 알림. HTTP 500 에러가 N개 넘으면 알림.

이 모델은 "어떤 문제가 있을지 미리 안다"를 가정한다. 미리 정의된 metric, 미리 정의된 임계값, 미리 정의된 대시보드.

1.2 분산 시스템에서의 한계

마이크로서비스 환경에서는 이 모델이 깨진다. 수십 개 서비스, 수백 개 의존성, 수천 가지 실패 모드. 미리 다 정의할 수 없다.

새 종류의 실패가 매주 등장한다. 어제 본 적 없는 패턴이 오늘 production을 망친다. "이런 문제가 있을 것 같으니 미리 metric을 박자"가 통하지 않는다.

1.3 Observability의 약속

Observability는 "내가 아직 모르는 문제도 디버깅 가능"을 약속한다. 충분히 풍부한 데이터를 수집하고, 임의로 쿼리/필터/aggregate할 수 있어야 한다.

이를 가능하게 하는 세 가지 (또는 네 가지) 데이터 종류:

  • Metrics: 시계열 숫자
  • Logs: 시간 순 텍스트 이벤트
  • Traces: 요청의 경로
  • Profiles: 코드 수준 자원 사용

이 네 가지가 함께 있어야 진정한 observability.

1.4 "Unknown Unknowns"

Donald Rumsfeld의 유명한 분류:

  • Known knowns: 알고 있는 것
  • Known unknowns: 모른다는 것을 아는 것
  • Unknown unknowns: 모른다는 것조차 모르는 것

Monitoring은 known knowns와 known unknowns를 다룬다. Observability는 unknown unknowns까지 다뤄야 한다. 이것이 두 개념의 본질적 차이.

1.5 한 줄 요약

Monitoring은 "이미 알고 있는 문제"를 추적한다. Observability는 "아직 모르는 문제"도 디버깅할 수 있게 한다. 모던 분산 시스템에서는 둘 다 필요하다.

★ Insight ─────────────────────────────────────

  • 이름의 정치: "Observability"라는 단어가 마케팅적으로 유행하면서 "그냥 monitoring + tracing"을 그렇게 부르는 경우가 많다. 진짜 observability는 "임의의 새 질문을 즉석에서 던질 수 있다"는 능력이고, 그러려면 cardinality와 표현력이 매우 풍부해야 한다.
  • Honeycomb의 영향: Charity Majors가 이끄는 Honeycomb은 high-cardinality column store로 진정한 observability를 표방한 첫 회사. "events with rich attributes"라는 모델. Prometheus의 시계열 모델과는 다른 접근. 둘 다 가치가 있다.
  • 세 기둥은 부분 답에 불과: metrics + logs + traces가 "세 기둥"이라고 부르지만, 이 셋만으로는 모든 디버깅 시나리오를 커버하지 못한다. Profiling과 events가 추가 기둥이 되고 있다. ─────────────────────────────────────────────────

2. 네 가지 (또는 다섯 가지) 기둥

2.1 Metrics

시간에 따른 숫자 시계열. 예: http_requests_total, cpu_usage_seconds, memory_bytes.

특징:

  • 저비용: 압축이 잘 됨
  • 빠른 쿼리: 시계열 DB에 최적화
  • aggregation 친화적: avg, sum, percentile 등

한계:

  • 개별 요청 추적 불가: aggregate된 데이터
  • cardinality 한계: label이 많아지면 폭증

2.2 Logs

시간 순서의 텍스트 이벤트. 예: "User 123 logged in at 2026-04-15T10:30:00Z".

특징:

  • 풍부한 컨텍스트: 자유로운 메시지
  • 개별 이벤트: 각 사건이 독립적으로 보임

한계:

  • 저장 비용: text는 압축이 어렵다
  • 쿼리가 느림: 인덱싱 안 된 검색은 느림
  • 신뢰성: 누락 가능성

2.3 Traces

분산 시스템에서 요청이 거치는 경로. 한 요청이 service A → B → C → D를 거치는 동안 각 단계의 시간과 메타데이터를 기록.

특징:

  • 인과 관계: 어떤 요청이 어떤 백엔드를 호출했는지
  • bottleneck 식별: 어디서 시간이 걸렸는지
  • 서비스 의존성: 자동 그래프 생성

한계:

  • 샘플링 필요: 모든 요청을 추적하면 비용 폭발
  • 계측 부담: 코드에 trace 코드 삽입 필요 (옛날에는)

2.4 Profiles (네 번째 기둥)

CPU 사용, 메모리 할당, lock contention 등의 코드 수준 분석.

특징:

  • 함수 수준: 어떤 함수가 가장 많은 시간을 쓰나
  • flame graph: 시각화의 표준
  • continuous profiling: 항상 켜놓고 수집

도구: Pyroscope, Parca, Polar Signals.

2.5 Events (다섯 번째 기둥?)

배포, 스케일링, 설정 변경 같은 이산적 사건.

특징:

  • 전후 비교: "X가 일어난 후 latency가 늘었나?"
  • 상관관계 추론: 이벤트와 metric을 연결

Kubernetes events, audit logs, deployment events 등.

2.6 다섯 기둥의 협력

각 데이터 종류는 다른 종류로 답할 수 없는 질문에 답한다. 진정한 observability는 다섯 모두를 잘 통합해야 한다.

예시 디버깅 시나리오:

  1. Metric: p99 latency가 100ms → 500ms로 튀었다.
  2. Trace: 느린 요청 한 개를 따라가서 어느 단계에서 200ms를 썼는지 확인.
  3. Profile: 그 단계의 어떤 함수가 비싸졌는지 확인.
  4. Logs: 그 함수에서 에러 로그가 있는지 확인.
  5. Events: 그 시점에 배포가 있었는지 확인.

다섯 기둥이 모이면 30분 안에 root cause 식별. 한 기둥만 있으면 며칠.


3. 역사 — 모니터링에서 observability까지

3.1 1990s — Nagios와 Cacti

Nagios (1999)와 Cacti (2001)가 첫 산업용 모니터링. Nagios는 service health check, Cacti는 SNMP 기반 graphing. 이 둘이 사실상 표준이었다.

3.2 2000s — Munin, Ganglia

Munin (2004)과 Ganglia (2000s)가 더 가벼운 대안. Ganglia는 대규모 클러스터 (HPC)를 목표.

3.3 2010 — Graphite

Etsy/Orbitz에서 출발. 단순한 시계열 DB + 그래프. push 기반 (애플리케이션이 metric을 보냄). PromQL 이전의 사실상 표준.

3.4 2012 — Prometheus

SoundCloud가 만들어 2012년 공개. Google Borgmon에서 영감. Pull 기반, 라벨 기반, 강력한 PromQL. 곧 Cloud Native의 사실상 표준.

3.5 2010-2015 — ELK Stack

Elasticsearch + Logstash + Kibana. 로그 수집과 분석의 기본 옵션. 무겁지만 강력. Splunk의 오픈소스 대안.

3.6 2010 — Dapper

Google이 발표한 distributed tracing 시스템 논문. 모든 후속 tracing 시스템 (Zipkin, Jaeger 등)의 직접 조상.

3.7 2012 — Zipkin

Twitter가 Dapper를 따라 만든 오픈소스 tracing. 첫 산업용 distributed tracing.

3.8 2017 — Jaeger

Uber가 Zipkin의 후속으로 만든 tracing 시스템. CNCF 졸업 프로젝트.

3.9 2019 — OpenTelemetry

OpenTracing과 OpenCensus의 합병. 모든 tracing/metric/logging의 통합 표준 시도. 2024년 시점, 사실상 표준.

3.10 2020+ — eBPF 기반

eBPF 글에서 봤듯, eBPF로 zero-instrumentation observability가 가능해짐. Pixie, Beyla, Parca 등.


4. Metrics 깊이 — Counter, Gauge, Histogram

4.1 네 가지 metric 타입

4.1.1 Counter

단조 증가 (decrementing 안 됨). 누적 카운트.

http_requests_total = 12345

값 자체보다 rate가 의미 있다. "초당 몇 번 증가하나?"

rate(http_requests_total[5m])

용례:

  • http_requests_total
  • errors_total
  • bytes_received_total

4.1.2 Gauge

오르락내리락. 현재 값.

memory_bytes = 1234567890
queue_depth = 42

평균, max, current 등 직접 의미.

용례:

  • 메모리 사용량
  • 큐 깊이
  • 활성 연결 수

4.1.3 Histogram

값의 분포. bucket으로 cardinality 폭증을 회피.

http_request_duration_seconds_bucket{le="0.005"} 12
http_request_duration_seconds_bucket{le="0.01"} 24
http_request_duration_seconds_bucket{le="0.025"} 48
http_request_duration_seconds_bucket{le="0.05"} 89
...
http_request_duration_seconds_bucket{le="+Inf"} 100
http_request_duration_seconds_count 100
http_request_duration_seconds_sum 12.5

각 bucket이 "이 값 이하인 관측값의 수". 누적 형태.

쿼리:

histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

4.1.4 Summary

Histogram과 비슷하지만 client side에서 quantile을 계산.

http_request_duration_seconds{quantile="0.5"} 0.05
http_request_duration_seconds{quantile="0.9"} 0.15
http_request_duration_seconds{quantile="0.99"} 0.35
http_request_duration_seconds_count 100
http_request_duration_seconds_sum 12.5

장점: 정확한 percentile. 단점: 여러 인스턴스 간 aggregation 불가능 (histogram은 가능).

대부분의 경우 histogram이 더 유용.

4.2 Pull vs Push 모델

Pull 모델 (Prometheus):

  • Prometheus가 주기적으로 target을 scrape (HTTP GET /metrics)
  • Service discovery로 target 자동 발견
  • 각 service가 stateless

Push 모델 (Graphite, StatsD, OpenTelemetry):

  • Application이 metric을 collector로 push
  • 짧은 수명의 job (CronJob, Lambda)에 적합

Prometheus는 push gateway로 push 모델도 부분 지원.

각자 트레이드오프:

  • Pull은 정상 상태 모니터링이 자연스러움. Service가 죽으면 scrape 실패가 자동 신호.
  • Push는 짧은 수명 워크로드에 유리. Lambda 같은 경우 pull이 어렵다.

4.3 Cardinality 폭증 — 가장 흔한 함정

각 unique label 조합이 새 시계열을 만든다. label의 카디널리티가 곱해진다.

예: http_requests_total{method, status, path, user_id}

  • method: 4 (GET, POST, PUT, DELETE)
  • status: 5 (200, 201, 404, 500, 503)
  • path: 50
  • user_id: 1,000,000

총 시계열 수: 4 × 5 × 50 × 1,000,000 = 10억 개.

이는 Prometheus가 처리할 수 없다. 시스템이 OOM되거나 매우 느려진다.

규칙: label은 cardinality가 낮은 dimension만 사용. user_id, request_id, IP 등은 절대 label로 쓰지 않음.

4.4 Recording Rules

자주 쓰는 쿼리를 미리 계산해서 새 metric으로 저장. 대시보드 응답 속도 개선.

groups:
- name: http
  rules:
  - record: instance:http_requests:rate5m
    expr: rate(http_requests_total[5m])

대시보드는 이 미리 계산된 metric을 쿼리. 매번 raw rate를 계산하지 않음.


5. Prometheus 깊이

5.1 아키텍처

[Targets] ← scrape ← [Prometheus Server] → [Time Series DB]
                       [Alert Rules]
                       [Alertmanager]
                  [Slack, Email, PagerDuty, ...]

5.2 시계열 DB 내부

Prometheus의 데이터 저장:

  • Block 단위 저장: 2시간 단위 blocks
  • WAL (Write-Ahead Log): durability
  • mmap 기반: 빠른 쿼리
  • 압축: 같은 시계열의 연속 sample들을 delta 인코딩

각 sample은 16바이트 (timestamp 8 + value 8). 압축 후 보통 1-2바이트.

5.3 PromQL — 쿼리 언어

PromQL의 핵심 연산:

5.3.1 Instant vector

특정 시점의 값.

http_requests_total

이는 모든 series의 현재 값.

5.3.2 Range vector

시간 범위의 값들.

http_requests_total[5m]

지난 5분 동안의 모든 sample.

5.3.3 Rate

Counter의 초당 증가율.

rate(http_requests_total[5m])

irate는 마지막 두 sample만 사용 (더 민감), rate는 전체 범위 평균 (더 안정).

5.3.4 Aggregation

sum(rate(http_requests_total[5m]))
sum by (status) (rate(http_requests_total[5m]))
sum without (instance) (rate(http_requests_total[5m]))

by는 보존할 dimension, without은 제거할 dimension.

5.3.5 Filtering

라벨 필터:

http_requests_total{status="200"}
http_requests_total{status!="200"}
http_requests_total{status=~"5.."}
http_requests_total{status!~"4.."}

=, !=, =~ (regex), !~ (negative regex).

5.3.6 Histogram quantile

histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

p99 latency.

5.4 PromQL 실전 예시

에러율 계산

sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))

노드별 CPU 사용률

1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

Top 5 cost API 엔드포인트

topk(5, sum by (path) (rate(http_request_duration_seconds_sum[5m])))

Predictable disk fill

predict_linear(node_filesystem_free_bytes[6h], 4 * 3600) < 0

이는 "현재 추세대로면 4시간 안에 디스크가 0이 될 곳"을 찾는다.

5.5 Service Discovery

Prometheus는 target을 어떻게 알까? Service discovery로 동적 발견.

지원:

  • Static config: 명시적 target list
  • DNS: SRV/A 레코드
  • Kubernetes: Pod, Service, Endpoint 자동 발견
  • AWS EC2, GCE, Azure: 인스턴스 자동 발견
  • Consul, etcd: 서비스 레지스트리
  • File-based: 외부 도구가 파일 갱신

Kubernetes 환경에서는 kube-state-metrics + node-exporter + Prometheus Operator가 표준 조합.

5.6 Federation과 long-term storage

Prometheus 자체는 단일 인스턴스 + 짧은 retention (보통 15일). 장기 저장과 글로벌 view는:

  • Federation: 여러 Prometheus를 hierarchy로 묶음
  • Thanos: object storage (S3) 기반 장기 저장 + global query
  • Cortex / Mimir: 멀티테넌트 수평 확장
  • VictoriaMetrics: Prometheus 호환 + 더 효율적인 저장

대부분의 production은 Prometheus + Thanos 또는 Mimir.


6. Logs — 옛것과 새것

6.1 Logging의 역사

옛날: printf → 파일. syslog. logrotate.

분산 시대: 중앙 수집. Fluentd, Fluent Bit, Filebeat 같은 agent가 수집해서 중앙으로.

저장: Elasticsearch, Loki, Splunk, S3.

6.2 Structured Logging

옛날: [2026-04-15 10:30:00] ERROR User 123 failed to login

structured: {"timestamp":"2026-04-15T10:30:00Z","level":"error","user_id":123,"event":"login_failed"}

JSON으로 인코딩하면 파싱이 쉽다. 검색이 쉽다. 분석이 쉽다.

거의 모든 모던 로깅 라이브러리가 structured 지원. zap (Go), zerolog (Go), structlog (Python), pino (Node).

6.3 ELK Stack (Elasticsearch + Logstash + Kibana)

가장 오래된 메이저 logging 스택.

  • Filebeat (또는 Logstash, Fluentd): 로그 수집
  • Logstash: 파싱과 변환
  • Elasticsearch: 검색 인덱스
  • Kibana: UI

장점: 매우 강력한 검색. 풍부한 쿼리 언어. 단점: 리소스 헤비. 큰 클러스터에서 비싸다.

6.4 Loki — Prometheus의 logging 형제

Grafana Labs가 만든 가벼운 logging 시스템. Prometheus의 디자인 철학을 logging에 적용.

핵심 차이: 로그 전체를 인덱싱하지 않는다. label만 인덱싱. 본문은 그냥 압축해서 저장.

{job="nginx", instance="server-01"} | "ERROR"

이는 (job, instance) label로 stream을 찾고, 그 stream에서 "ERROR"가 들어간 줄을 grep.

장점:

  • ES 대비 10-100배 가벼움
  • Prometheus와 같은 label 모델
  • Grafana와 자연스러운 통합

단점:

  • 풀텍스트 검색이 ES만큼 빠르지 않음
  • 표현력이 제한

6.5 LogQL — Loki의 쿼리 언어

{app="nginx"} |= "error" | json | status >= 500

흐름:

  1. {app="nginx"} — label로 stream 선택
  2. |= "error" — line filter
  3. | json — JSON 파싱
  4. | status >= 500 — 파싱된 필드로 필터

PromQL과 매우 비슷. Grafana 안에서 둘을 자연스럽게 섞을 수 있다.

6.6 Logs vs Metrics — 무엇을 언제 쓰나

기준MetricsLogs
비용낮음높음
검색 가능제한적풍부
Cardinality제한무제한
압축매우 좋음보통
사용처알람, 대시보드디버깅, 감사

규칙: metric으로 알람, log로 디버깅. 알람이 발생하면 그 시점의 로그를 본다.


7. Traces — 분산 시스템의 X-ray

7.1 동기

마이크로서비스 환경에서 한 사용자 요청이 service A → B → C → D → E를 거친다. 어디서 느려졌는지 어떻게 알 수 있나?

각 서비스의 로그를 따라가는 것은 거의 불가능. 모두 다른 시각, 다른 형식, 다른 trace ID.

Distributed tracing은 한 요청에 unique trace ID를 부여하고, 그 ID로 모든 단계를 연결한다.

7.2 핵심 개념

  • Trace: 한 요청의 전체 경로
  • Span: 한 단계 (한 서비스 안의 한 작업)
  • Trace ID: trace를 식별
  • Span ID: span을 식별
  • Parent Span ID: span 사이의 인과 관계
Trace ID: abc123
├── Span 1: service-a (root)
│   ├── Span 2: service-b
│   │   └── Span 3: database query
│   └── Span 4: service-c
│       └── Span 5: external API

7.3 Context Propagation

trace ID와 span ID를 service 간에 전달. HTTP 헤더로 보통:

traceparent: 00-abc123-def456-01
tracestate: vendor=value

W3C Trace Context 표준. 거의 모든 모던 tracing 시스템이 사용.

7.4 Sampling

모든 요청을 trace하면 비용이 폭발한다. 일부만 샘플링.

전략:

  • Head sampling: 요청 시작 시 결정 (예: 1%만 trace)
  • Tail sampling: 요청 끝난 후 결정 (예: 에러나 느린 요청만 keep)
  • Adaptive sampling: 부하에 따라 동적 조정

Tail sampling이 더 가치 있지만 구현이 복잡 (모든 span을 일단 모았다가 결정).

7.5 Jaeger

Uber가 만든 distributed tracing. CNCF graduated.

컴포넌트:

  • Agent: 각 host에서 도는 collector
  • Collector: agent에서 받은 span을 저장
  • Storage: Cassandra, Elasticsearch, Badger
  • Query: 검색 인터페이스
  • UI: 시각화

특징:

  • 풍부한 시각화
  • 여러 storage backend 지원
  • OpenTelemetry 호환

7.6 Tempo — Grafana Labs의 tracing

Grafana Labs가 만든 tracing 시스템. Loki와 같은 철학 — 인덱스 최소화.

특징:

  • 매우 가벼움
  • object storage (S3) 기반
  • trace ID로만 lookup (검색은 약함)
  • Loki/Prometheus와 자연스러운 통합

비용 최적화에 매우 유리. 구체적인 trace ID를 알면 빠르고, 모르면 다른 도구로 찾아야 함.

7.7 Zipkin

가장 오래된 오픈소스 tracing. Twitter가 만들었다. Jaeger보다 단순하지만 기능이 적다. 여전히 일부 환경에서 사용.


8. OpenTelemetry — 통합 표준

8.1 동기

각 vendor가 자기 SDK를 만들었다. application 코드가 vendor에 lock-in. 다른 backend로 옮기려면 모든 instrumentation을 다시 작성.

OpenTelemetry는 이를 해결. 표준 API + 표준 데이터 모델 + 표준 protocol. backend는 갈아끼울 수 있다.

8.2 역사

  • OpenTracing (2016): tracing의 vendor-neutral API
  • OpenCensus (2017): Google이 만든 metrics + tracing 표준
  • OpenTelemetry (2019): 두 프로젝트의 합병

지금은 사실상 표준. 모든 메이저 vendor가 OpenTelemetry를 받는다.

8.3 컴포넌트

  • API: application이 호출하는 인터페이스
  • SDK: API의 reference 구현
  • Collector: 데이터를 받아서 backend로 보내는 중간 layer
  • Exporters: 다양한 backend (Jaeger, Prometheus, OTLP)
  • Instrumentation libraries: HTTP, gRPC, DB 등 자동 instrument

8.4 데이터 모델

OpenTelemetry는 metrics, traces, logs를 모두 다룬다. 통합 데이터 모델:

  • Resource: 데이터를 생성한 entity (service.name, service.version, host.name 등)
  • Attribute: 키-값 메타데이터
  • Temporality: cumulative vs delta

8.5 OTLP (OpenTelemetry Protocol)

데이터 전송 protocol. gRPC 기반 (HTTP도 지원). 모든 telemetry 종류를 한 protocol로.

8.6 Collector 패턴

[Application] → [Collector (sidecar 또는 daemonset)] → [Backend]

Collector가 중간에 있으면:

  • Application은 단일 endpoint로 보냄 (단순화)
  • Collector가 sampling, batching, transformation
  • Backend 갈아끼우기 쉬움 (Collector만 수정)
  • 여러 backend로 fan-out 가능

거의 모든 모던 OTel 배포가 Collector를 쓴다.

8.7 자동 Instrumentation

Java, Python, Node.js 등에서 코드 수정 없이 자동으로 trace를 생성. agent를 application에 붙이면 자동으로 HTTP/gRPC/DB 호출이 trace됨.

java -javaagent:opentelemetry-javaagent.jar -jar my-app.jar

OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT 등 환경 변수로 설정.

8.8 OpenTelemetry의 영향

OpenTelemetry는 observability 생태계의 가장 큰 통합 노력이다. 이제 거의 모든 새 프로젝트가 OTel을 default로. 옛 OpenTracing/OpenCensus는 deprecated.

★ Insight ─────────────────────────────────────

  • OpenTelemetry는 Kubernetes의 OCI 같은 역할: vendor lock-in을 줄이고 표준화를 가져왔다. application 코드는 한 번 작성하면 backend는 자유롭게 갈아끼울 수 있다. 이는 사용자에게는 유연성, vendor에게는 차별화 압박을 의미한다.
  • Collector는 진짜 가치 layer: SDK는 단순한 인터페이스이지만 Collector가 모든 정교한 작업 (sampling, batching, routing, transformation)을 한다. Collector는 자체로 작은 ETL 파이프라인. 이를 잘 운영하는 것이 OTel 도입의 핵심.
  • 세 기둥의 통합이 OpenTelemetry의 진짜 야심: 옛날에는 metrics, logs, traces가 다른 도구. OTel은 같은 SDK, 같은 protocol, 같은 collector로 셋 다 처리. 이는 cross-correlation을 매우 쉽게 만든다 — "이 trace가 일어난 시점의 metric은?", "이 metric 변화 원인 로그는?" 같은 쿼리. ─────────────────────────────────────────────────

9. Profiling — 네 번째 기둥

9.1 동기

CPU 100%인 것을 metric으로 알 수 있다. 그러나 어떤 함수가 CPU를 쓰는지는 모른다. 메모리 사용량도 마찬가지 — total은 알지만 어떤 객체가 메모리를 차지하는지는 모른다.

Profiling은 이를 해결. 코드 수준에서 자원 사용을 측정.

9.2 Profile의 종류

  • CPU profile: 어떤 함수가 CPU를 쓰는가
  • Memory (heap) profile: 어떤 함수가 메모리를 할당하는가
  • Lock profile: 어디서 lock contention이 일어나는가
  • I/O profile: 어떤 파일/소켓이 IO를 일으키는가
  • Goroutine profile (Go): 어떤 함수에서 goroutine이 멈춰 있는가

9.3 Continuous Profiling

옛날: 문제가 생기면 그 순간에 프로파일 기록. "post-mortem" 모델.

Continuous profiling: 항상 프로파일을 기록. 작은 비용 (CPU 1% 미만)으로 영원히. 문제가 생기면 그 시점 + 이전 시점을 비교.

9.4 도구

  • pprof (Go의 표준): Go 프로그램의 모든 profile 종류
  • Pyroscope: continuous profiling backend, 많은 언어 지원
  • Parca: eBPF 기반 continuous profiling, zero-instrumentation
  • Polar Signals: 상용 (Parca 회사)
  • Datadog Profiler: 상용

9.5 Flame Graph

profiling 결과의 표준 시각화. Brendan Gregg가 발명.

[main]
├── [parse_input]                     ← 10%
└── [process]                          ← 90%
    ├── [validate]                    ← 20%
    └── [transform]                   ← 70%
        ├── [encode]                  ← 30%
        └── [decompress]              ← 40%

각 박스의 너비가 그 함수에서 보낸 시간 비율. 높이가 호출 깊이.

flame graph만 봐도 "어디가 비싼지" 즉시 보인다.

9.6 eBPF와 profiling

eBPF 글에서 봤듯, eBPF는 profiling에 매우 적합. perf event sampling으로 모든 프로세스의 stack trace를 거의 무비용으로 수집.

perf record + perf report가 전통적 도구. Parca, Pyroscope의 eBPF 백엔드도 같은 메커니즘.

9.7 어떻게 활용하나

[Continuous profile dashboard]
[알람 시점의 profile 보기]
[1시간 전 profile과 diff]
[새로 비싸진 함수 식별]
[그 함수의 코드 검토]
[수정]

이 워크플로우가 production debugging의 새 표준이 되어가고 있다.


10. Grafana — 시각화의 표준

10.1 Grafana가 무엇인가

대시보드 + 알람 + 탐색 도구. Grafana Labs가 만들고 사실상 표준. 거의 모든 metric을 표현할 수 있는 백엔드 agnostic 도구.

10.2 Data Sources

지원하는 backend:

  • Prometheus
  • Loki
  • Tempo
  • Mimir
  • Elasticsearch
  • InfluxDB
  • ClickHouse
  • BigQuery
  • Postgres / MySQL
  • AWS CloudWatch
  • Datadog
  • 수십 개 더

이 광범위한 지원이 Grafana를 표준으로 만들었다.

10.3 대시보드

Panels의 모음. 각 panel은:

  • Query
  • Visualization (graph, gauge, table, heatmap, ...)
  • Threshold

JSON으로 정의 가능 → 코드로 관리 가능 (GitOps).

10.4 Variables

대시보드를 동적으로 만드는 매개변수. namespace, service, instance 등을 선택할 수 있는 드롭다운.

쿼리에서 변수 참조:

http_requests_total{namespace="$namespace"}

(달러 사인 + 변수명. Grafana template 문법.)

10.5 Alerting

옛날에는 Grafana가 단순한 alerting만 지원. Grafana 8+부터 unified alerting — 모든 backend의 alert를 한 곳에서.

조건 기반 alert + Alertmanager 통합. Slack, PagerDuty, Opsgenie 등으로 routing.

10.6 Grafana Cloud

Grafana Labs의 managed 서비스. Prometheus, Loki, Tempo, Pyroscope을 모두 호스팅. 작은 회사가 self-hosting 부담 없이 사용 가능.


11. SRE의 SLI / SLO / SLA

Google이 발전시킨 신뢰성 공학 모델. 모던 observability의 핵심 프레임워크.

11.1 SLI (Service Level Indicator)

서비스의 정량적 지표. 예:

  • availability: 성공한 요청 비율
  • latency: 응답 시간 percentile (p99 등)
  • correctness: 올바른 결과 비율
  • freshness: 데이터의 최신성

11.2 SLO (Service Level Objective)

SLI의 목표. 예:

  • "p99 latency < 200ms over 30 days"
  • "availability >= 99.9% over 30 days"

이는 사용자 경험 약속이지 기술적 목표가 아니다. 사용자가 견딜 만한 수준.

11.3 SLA (Service Level Agreement)

SLO 위반 시 발생하는 contractual obligation. 보통 환불 또는 credit. 외부 고객과의 계약.

대부분의 회사는 SLO만 가지고 SLA는 외부 고객용에만.

11.4 Error Budget

SLO가 99.9%이면, 0.1%의 "에러 예산"이 있다. 30일 기준 약 43분.

이 예산이 다 소진되지 않은 상태에서는 새 기능을 배포해도 된다. 다 쓰면 release를 멈추고 안정화에 집중.

이는 "안정성 vs 속도" 갈등에 대한 객관적 해결책. 데이터 기반 결정.

11.5 Burn Rate

에러 예산을 얼마나 빠르게 쓰고 있나. 예:

  • Burn rate 1: 30일 후에 정확히 예산 소진
  • Burn rate 14.4: 30일 예산을 2일에 소진 (즉시 페이저)
  • Burn rate 6: 5일 만에 소진 (긴급)

Multi-window burn rate alerts가 모던 alerting의 표준 패턴.

(
  sum(rate(http_requests_total{status=~"5.."}[1h]))
  /
  sum(rate(http_requests_total[1h]))
) > 14.4 * (1 - 0.999)

이는 "지난 1시간 동안의 burn rate가 14.4를 넘으면 alert".

11.6 SLO 예산을 활용한 제품 결정

  • 예산이 많이 남았다 → 위험한 실험 가능
  • 예산이 거의 없다 → 보수적 운영
  • 예산이 다 소진 → release freeze, 안정화 우선

이 모델은 엔지니어와 PM의 생산적 대화를 만든다. "그냥 우리 시스템이 안정적으로 느껴진다"가 아니라 객관적 데이터.


12. Alerting — 경보 디자인

12.1 좋은 alert의 조건

  • Actionable: 받은 사람이 무엇을 해야 할지 명확
  • Symptom-based: cause 추적이 아니라 user-visible 증상
  • Rare: false positive가 적어야 함
  • Urgent: 즉시 대응이 필요한 것만 page

12.2 Alert Fatigue

너무 많은 alert가 와서 진짜 문제를 놓치는 현상. 가장 흔한 운영 실패.

회피:

  • alert 수 제한 (한 팀에 하루 5개 미만 page가 이상적)
  • 정기 리뷰로 useless alert 제거
  • alert grouping
  • multi-window burn rate (단일 spike에 트리거 안 됨)

12.3 Alertmanager (Prometheus)

Prometheus의 alert routing/grouping/silencing.

기능:

  • Routing: alert를 적절한 팀/채널로
  • Grouping: 비슷한 alert를 묶어서 한 번만 전송
  • Silencing: 알려진 문제 일시 무음
  • Inhibition: 더 큰 문제가 있을 때 작은 alert 억제
route:
  receiver: default
  group_by: ['alertname', 'cluster']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
  - match:
      severity: critical
    receiver: pagerduty
  - match:
      severity: warning
    receiver: slack

12.4 PagerDuty / Opsgenie

상용 incident management. on-call rotation, escalation, post-mortem 도구. Alertmanager가 보내는 alert를 받아 사람에게 전달.

12.5 Runbook

Alert에 첨부되는 문서. "이 alert를 받았을 때 무엇을 해야 하나". 모든 alert에 runbook이 있어야 한다.

annotations:
  summary: "High error rate"
  runbook_url: "https://wiki.company.com/runbooks/high-error-rate"

13. eBPF 기반 zero-instrumentation observability

13.1 동기

전통적 instrumentation은 코드에 trace/metric 코드를 삽입해야 한다. 작업이 많고, 모든 언어/라이브러리가 지원해야 한다.

eBPF는 다른 접근: 커널 수준에서 모든 프로세스의 동작을 관찰. 코드 수정 없음.

13.2 도구들

  • Pixie: New Relic이 인수. 자동 HTTP/gRPC/DB instrumentation.
  • Beyla: Grafana Labs. 자동 OpenTelemetry trace.
  • Parca: continuous profiling (eBPF 기반).
  • Hubble (Cilium): 네트워크 흐름 관찰.
  • Tetragon (Cilium): 보안 이벤트.

13.3 무엇을 볼 수 있나

  • 모든 HTTP 요청과 응답
  • 모든 SQL 쿼리
  • 모든 system call
  • 모든 네트워크 연결
  • 모든 파일 액세스
  • CPU/메모리 사용 (함수 단위)

13.4 한계

  • L7 protocol decode가 어려움: HTTP는 가능하지만 application-specific protocol은 어려움
  • TLS encrypted traffic: 일부 우회 가능 (uprobe로 TLS 함수 가로채기)
  • JIT compiled languages: stack trace 해석이 어려움 (해결되고 있음)

13.5 미래

eBPF 기반 observability는 아직 초기 단계지만 빠르게 성숙하고 있다. 향후 5-10년 동안 "instrumentation을 거의 안 해도 풍부한 데이터"가 표준이 될 가능성.


14. 사례 — 실전 디버깅 시나리오

14.1 시나리오: p99 latency 스파이크

증상: 어제 오후 3시부터 p99 latency가 100ms → 500ms.

조사:

  1. Metric: Grafana에서 latency 그래프 확인. 특정 시점에서 정확히 jump.
  2. Events: 그 시점의 deployment 이벤트 확인. v1.5.0 배포가 있었음.
  3. Trace: 그 시점의 느린 trace 분석. database query에 시간이 가장 많이 걸림.
  4. Profile: continuous profile 비교. v1.5.0 이후 새 함수 compute_recommendation이 CPU 50%를 차지.
  5. Code review: 그 함수가 N+1 query를 한다는 것 발견.
  6. 수정: query batching으로 변경. 배포.
  7. 검증: latency가 다시 100ms로 복귀.

총 시간: 약 30분. 다섯 데이터 종류가 모두 필요.

14.2 시나리오: 메모리 누수

증상: pod이 매일 OOM kill 됨.

조사:

  1. Metric: memory 사용량 그래프. 시간에 따라 선형 증가 (sawtooth).
  2. Profile: heap profile 시계열. 매일 같은 함수의 할당이 늘어남.
  3. Code: 그 함수 검토. global cache에 객체를 추가하지만 절대 제거 안 함.
  4. 수정: LRU cache로 교체.

14.3 시나리오: 간헐적 에러

증상: 1% 요청이 503 에러. 패턴이 안 보임.

조사:

  1. Logs: 에러 메시지 검색. "connection refused".
  2. Trace: 에러 trace 분석. 특정 백엔드 서비스 호출 시.
  3. Metric: 그 백엔드의 ready replicas 그래프. 가끔 0 → 1 → 0.
  4. Events: 그 서비스의 Pod restart 이벤트 확인. liveness probe 실패로 재시작.
  5. kubectl describe: probe 설정 확인. timeout이 1초로 너무 짧음.
  6. 수정: timeout을 5초로 늘림.

15. 비용과 Cardinality 관리

15.1 Observability의 비용

큰 회사에서 observability는 인프라 비용의 5-15%를 차지한다. 무시할 수 없는 금액.

비용 driver:

  • Metric cardinality: 시계열 수에 비례
  • Log volume: 로그 양에 비례
  • Trace sampling: trace 양에 비례
  • Storage retention: 보관 기간

15.2 Cardinality 통제

규칙:

  1. 고cardinality dimension을 label로 쓰지 말 것: user_id, IP, request_id 등은 절대 안 됨
  2. Histogram bucket 신중히 선택: 너무 많으면 cardinality 폭증
  3. Dynamic label에 주의: URL path, error message 등은 cardinality가 폭증할 수 있음

15.3 Sampling 전략

  • Metric: aggregation은 sampling 안 함. raw event를 metric으로 사전 집계.
  • Log: 자주 발생하는 정상 로그는 sampling. 에러는 100% 보관.
  • Trace: tail sampling으로 흥미로운 trace만 보관 (에러, 느림, 관심 사용자).

15.4 Tiered Storage

  • Hot: 최근 7일. 빠른 검색. SSD.
  • Warm: 30일. 보통 검색. HDD 또는 cheap SSD.
  • Cold: 1년+. 느린 검색. S3 같은 object storage.

오래된 데이터를 자동으로 cold로 옮기면 비용이 크게 줄어든다.

15.5 비용 모니터링

비용 자체를 metric으로 추적. "오늘 우리 cardinality는 몇 시계열인가?", "오늘 우리 log volume은 몇 GB인가?". 이 메타-metric으로 비용 폭증을 미리 잡는다.


16. 미래 — Observability의 다음 단계

16.1 통합 (Unification)

지금은 metrics, logs, traces, profiles가 다른 backend, 다른 도구. OpenTelemetry가 통합 표준을 만들고 있지만, 여전히 백엔드는 분리.

미래: 통합 storage, 통합 query language. ClickHouse, DuckDB 같은 column store가 가능성을 보여준다.

16.2 AI 기반 anomaly detection

수동으로 알람을 정의하는 것이 점점 부담. ML로 자동 anomaly detection. 이미 많은 vendor가 시도. 결과는 mixed — false positive가 여전히 많다.

16.3 Causal inference

"왜 latency가 늘었나?"에 자동으로 답하는 시스템. 통계적 인과 추론. 활발한 연구 분야.

16.4 eBPF의 보편화

zero-instrumentation observability가 점점 표준이 되어간다. 5년 후에는 "수동 instrumentation은 옛날 것"이 될 가능성.

16.5 비용 최적화

cardinality 관리, 자동 sampling, tiered storage의 자동화. 사용자가 비용을 직접 관리하지 않게 하는 방향.


17. 결론 — Observability는 운영의 신경계다

이 글을 다 읽었다면 다음 질문에 답할 수 있을 것이다:

  • Monitoring과 observability의 차이는?
  • Metrics, logs, traces, profiles는 각각 무엇에 적합한가?
  • Prometheus의 pull 모델은 왜 설계되었나?
  • PromQL의 핵심 연산자는?
  • Cardinality 폭증을 어떻게 회피하나?
  • OpenTelemetry는 무엇을 표준화하나?
  • SLI/SLO/SLA의 차이는?
  • Error budget이 왜 유용한가?
  • eBPF 기반 observability의 약속은?

Observability는 단순한 도구가 아니다. 분산 시스템을 운영할 때 보지 못하는 것을 보게 해주는 신경계이다. metric, log, trace, profile이 모이면 시스템의 모든 부분을 동시에 볼 수 있게 된다. 그리고 그 능력이 곧 시스템의 안정성을 결정한다.

이 글로 Kubernetes 글의 자연스러운 후속이 완성되었다. 클러스터를 띄우는 것도 중요하지만, 그 안에서 무슨 일이 일어나는지 보는 것은 더 중요하다. 둘이 모이면 모던 cloud-native 운영의 풍경 전체가 그려진다.

다음 글에서는 [SRE 핸드북 — 인시던트 대응과 post-mortem 문화] 또는 [GitOps와 ArgoCD]를 다룰 예정이다. Observability 위에 쌓인 운영 layer들을 더 깊이 들여다볼 차례이다.


부록 A — 참고 자료

부록 B — 자주 묻는 질문

Q: Prometheus와 Grafana, 어디서 시작해야 하나? A: Prometheus 단독 설치 → node-exporter로 OS metric → Grafana로 시각화. 이 기본 스택을 먼저 익히는 것이 첫 단계.

Q: ELK과 Loki, 어떤 것을 써야 하나? A: 풍부한 풀텍스트 검색이 필요하면 ES. 비용과 단순함이 중요하면 Loki. Kubernetes 환경은 Loki가 자연스러움.

Q: Jaeger와 Tempo의 차이는? A: Jaeger는 풍부한 검색 (인덱싱). Tempo는 trace ID lookup 위주 (인덱스 최소). 비용이 결정 요인.

Q: OpenTelemetry를 도입해야 하나? A: 새 코드면 무조건. 기존 코드는 점진적. vendor agnostic이 큰 가치.

Q: SLO를 어떻게 설정해야 하나? A: 사용자가 감지할 수 있는 최저 수준. 너무 엄격하면 release 못 함, 너무 느슨하면 사용자 불만. 데이터로 시작.

Q: cardinality 폭증을 어떻게 예방하나? A: 모든 새 metric에 cardinality 검토. recording rule로 raw cardinality를 줄임. high-cardinality dimension은 trace나 log에 두고 metric에는 두지 말 것.

Q: alerting의 가장 흔한 실수는? A: 너무 많은 alert. false positive. cause-based alert (SREbook이 권장하는 것은 symptom-based).

Q: Prometheus가 큰 클러스터에서도 충분한가? A: 단일 인스턴스로는 한계. Thanos 또는 Mimir로 수평 확장 가능. 또는 VictoriaMetrics 같은 대안.


부록 C — 미니 용어집

  • Observability: 외부 출력으로 내부 상태를 추론하는 능력.
  • Monitoring: 알고 있는 문제 추적.
  • Metric: 시계열 숫자.
  • Counter: 단조 증가 metric.
  • Gauge: 오르락내리락 metric.
  • Histogram: 분포를 bucket으로 표현.
  • Summary: 분포를 client side quantile로 표현.
  • Cardinality: unique label 조합 수.
  • Log: 시간 순 텍스트 이벤트.
  • Structured logging: JSON 형식 로그.
  • Trace: 요청의 분산 경로.
  • Span: trace의 한 단계.
  • Trace ID / Span ID: trace/span 식별자.
  • Context propagation: trace 정보를 service 간 전달.
  • Sampling: trace 일부만 보관.
  • Profile: 코드 수준 자원 사용.
  • Continuous profiling: 항상 켜진 profiling.
  • Flame graph: profile의 시각화.
  • Prometheus: 시계열 metric 시스템.
  • PromQL: Prometheus 쿼리 언어.
  • Recording rule: 미리 계산된 metric.
  • Alertmanager: alert routing/grouping.
  • OpenTelemetry: 통합 telemetry 표준.
  • OTLP: OpenTelemetry Protocol.
  • Collector: telemetry 중간 layer.
  • Loki: Grafana Labs의 logging.
  • LogQL: Loki 쿼리 언어.
  • Jaeger: 분산 tracing.
  • Tempo: Grafana Labs의 tracing.
  • Grafana: 시각화 도구.
  • SLI: Service Level Indicator.
  • SLO: Service Level Objective.
  • SLA: Service Level Agreement.
  • Error budget: SLO 위반 허용량.
  • Burn rate: error budget 소진 속도.
  • Runbook: alert 대응 문서.
  • eBPF observability: zero-instrumentation 관찰.
  • Pyroscope / Parca: continuous profiling 도구.
  • ELK: Elasticsearch + Logstash + Kibana.

이 글이 Kubernetes 글의 자매 작품이다. 클러스터를 구축하는 것과 그 안에서 일어나는 일을 보는 것 — 둘 다 cloud-native 운영의 핵심 능력이다. 다음 글에서는 SRE 운영 문화나 GitOps 같은 더 위 layer를 다뤄볼 것이다.

현재 단락 (1/587)

분산 시스템의 첫 번째 진실은 이렇다: **시스템이 항상 부분적으로 망가져 있다**. 한 노드가 죽고, 한 네트워크가 끊기고, 한 디스크가 가득 차고, 한 라이브러리가 메모리 누수...

작성 글자: 0원문 글자: 23,933작성 단락: 0/587