옵저버빌리티
운영 클러스터의 시야를 만드는 세 축 — 메트릭 (Prometheus + kube-state-metrics + node-exporter), 로그 (Loki), 트레이스 (OpenTelemetry + Tempo) — 와 시각화 (Grafana), 알람 (Alertmanager)의 표준 스택을 정리합니다. kube-prometheus-stack의 ServiceMonitor · PrometheusRule, PromQL · LogQL의 한 줄 예시, 그리고 카디널리티 · 보존 · 알람 SNR · golden signals의 운영 가드레일까지 한 사이클로 다룹니다.
지금까지 다룬 15장 CNI 깊이, 16장 RBAC / ServiceAccount 깊이, 17장 Admission Controller, 18장 CRD와 Operator 패턴는 모두 클러스터의 동작을 만드는 컴포넌트였습니다. 이 모든 컴포넌트가 잘 굴러가는지를 들여다보는 한 층이 더 필요합니다 — 옵저버빌리티입니다. 어느 Pod가 메모리를 얼마나 쓰는가, 어느 Service의 latency가 평소보다 높은가, 한 요청이 마이크로서비스 사이를 어떻게 흘러갔는가. 이 세 차원이 메트릭 · 로그 · 트레이스이고, 그 위에 K8s 운영의 표준 스택이 자리 잡고 있습니다.
이번 챕터의 끝에서는 11장 resources.requests / limits · 12장 Health check · 13장 오토스케일링의 신호들이 모이는 한 묶음의 도구 스택이 손에 들어옵니다. 매니페스트 한 장으로 클러스터의 시야를 한 단 깊게 만드는 출발점입니다.
옵저버빌리티의 세 축 #
옵저버빌리티는 흔히 세 가지 데이터 종류로 갈라 이야기합니다.
| 축 | 무엇인가 | 질문 |
|---|---|---|
| Metrics | 시간에 따른 수치 시계열 | “지금 무슨 일이 일어나고 있는가” |
| Logs | 이벤트의 텍스트 기록 | “그 이벤트의 자세한 사정은 무엇인가” |
| Traces | 한 요청이 여러 서비스를 거치는 경로 | “왜 이 요청이 느렸는가” |
이 셋은 서로 보완 관계입니다. 메트릭으로 이상을 발견하고, 로그로 자세한 상황을 짚고, 트레이스로 요청 경로의 어느 구간이 문제인지 좁히는 흐름이 일상의 디버깅 패턴입니다. 운영 클러스터에서 셋을 모두 갖추는 게 표준이고, 각 축의 K8s 도구는 거의 굳어 있습니다. 27장 kubectl 디버깅 패턴의 진단 트리도 이 세 축을 같이 보는 흐름 위에 서 있습니다.
Metrics — Prometheus 중심의 표준 스택 #
K8s 메트릭의 사실상 표준은 Prometheus입니다. CNCF 졸업 프로젝트이고, K8s 자체의 컴포넌트 (API 서버, kubelet, controller-manager, scheduler)가 모두 Prometheus가 이해할 수 있는 형식으로 메트릭을 노출합니다. Prometheus의 모델은 단순합니다.
- Pull 기반 — Prometheus가 정기적으로 각 타깃의
/metrics엔드포인트를 HTTP로 긁어 옵니다. - 시계열 데이터베이스 — 긁어 온 데이터는 라벨이 붙은 시계열로 저장됩니다.
- PromQL — 시계열을 질의하는 자체 쿼리 언어입니다.
표준 스택의 컴포넌트 #
운영 클러스터에 Prometheus를 설치하면 거의 항상 다음 컴포넌트가 같이 들어옵니다.
| 컴포넌트 | 역할 |
|---|---|
| Prometheus Server | 메트릭 수집 + 저장 + 질의 |
| kube-state-metrics | K8s 객체 (Deployment, Pod, Node 등)의 상태를 메트릭으로 노출 |
| node-exporter | 각 노드의 시스템 메트릭 (CPU, 메모리, 디스크) 노출. 8장 DaemonSet으로 노드마다 한 개 |
| Alertmanager | 알람 라우팅, 묶음, 침묵 처리 |
| Pushgateway (선택) | 짧게 사는 Job의 메트릭을 push로 받음 |
이 묶음을 한 번에 설치해 주는 표준 매니페스트가 kube-prometheus-stack Helm 차트입니다. 운영 클러스터의 도입 첫 단계로 거의 표준이 되어 있습니다. EKS 실전 셋업에서는 25장 모니터링·알람에서 한 번 더 다룹니다.
ServiceMonitor / PodMonitor — Prometheus Operator의 역할 #
Prometheus의 scrape 대상을 직접 매니페스트로 적는 대신, Prometheus Operator가 도입한 CRD가 두 개 있습니다. 18장 CRD와 Operator 패턴의 모델이 옵저버빌리티 스택에도 그대로 적용되는 예입니다.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app
namespace: my-app
labels:
release: prometheus # kube-prometheus-stack의 selector와 일치
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 30s
path: /metrics이 ServiceMonitor를 적용하면 Prometheus Operator가 자동으로 Prometheus 설정을 갱신해서 그 Service의 Pod에서 30초마다 메트릭을 긁어 옵니다. 매니페스트로 scrape 대상을 선언하는 K8s-native 방식입니다.
PodMonitor는 Service 없이 Pod에 직접 붙는 변형입니다. 이 두 CRD 덕에 애플리케이션 팀은 Service 옆에 ServiceMonitor 한 장을 같이 적기만 하면 메트릭 수집이 자동으로 시작됩니다.
한 줄짜리 PromQL 예시 #
PromQL은 그 자체로 깊은 주제이지만, 운영에서 가장 자주 쓰는 패턴 몇 가지를 짚어 둡니다.
sum(container_memory_usage_bytes{namespace="payments"}) by (pod)sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)rate()는 시계열의 초당 증가량을, histogram_quantile()은 히스토그램에서 특정 분위수를 계산합니다. 이 두 함수가 PromQL 활용의 70% 정도를 덮습니다. 11장 §“메모리 사용량 vs 메모리 limits"에서 짚었던 container_memory_working_set_bytes 시계열 분석이 이 PromQL 위에서 본격적으로 가능해집니다.
Logs — Loki 중심의 새 스택 #
로그 수집의 옛 표준은 EFK 스택 (Elasticsearch + Fluentd + Kibana)이었습니다. 강력하지만 무겁습니다 — Elasticsearch는 모든 로그 본문을 풀텍스트 인덱싱하므로 디스크와 메모리가 많이 듭니다.
Loki는 Grafana Labs가 만든 가벼운 대안입니다. 모델이 다릅니다 — 로그 본문은 인덱싱하지 않고, 라벨만 인덱싱합니다. 검색 시에는 라벨로 좁힌 뒤 본문을 그룹 검사합니다. Prometheus와 비슷한 라벨 모델을 로그에 가져온 셈입니다.
Loki 스택의 컴포넌트 #
| 컴포넌트 | 역할 |
|---|---|
| Loki | 로그 저장 + 질의 |
| Promtail (또는 Fluent Bit) | 각 노드에서 로그를 읽어 Loki로 보냄. DaemonSet |
| Grafana | LogQL 쿼리 + 시각화 |
Promtail은 노드의 /var/log/containers/를 읽어서 K8s 메타데이터 (Pod 이름, 네임스페이스, 컨테이너 이름, 라벨)를 자동으로 라벨로 붙여 Loki로 전송합니다. 별도 애플리케이션 변경 없이 모든 컨테이너의 stdout / stderr가 그대로 수집됩니다. 3장 kubectl과 첫 Pod의 kubectl logs가 노드별로 흩어진 로그를 한 Pod 단위로 보여 주는 도구라면, Loki + Promtail은 그 흐름을 클러스터 전체로 확장한 모양입니다.
LogQL — Loki의 쿼리 언어 #
PromQL과 비슷한 결입니다.
{namespace="payments"} |= "ERROR"sum(rate({pod="checkout-abc123"} |= "ERROR" [5m])){...}로 라벨 필터, |=로 본문 substring 매칭, |~로 정규식 매칭. rate()로 메트릭처럼 다룰 수 있어서 Grafana 대시보드에서 메트릭 차트와 같이 그릴 수 있습니다.
Loki vs EFK — 선택의 결 #
| 차원 | Loki | EFK |
|---|---|---|
| 인덱싱 | 라벨만 | 전체 본문 |
| 디스크 비용 | 낮음 | 높음 |
| 풀텍스트 검색 | 그룹 검사 (느림) | 빠름 |
| 운영 부담 | 낮음 | 높음 (Elasticsearch 클러스터 운영) |
| Grafana 통합 | 1급 | 가능 |
신규 도입은 Loki가 표준에 가깝습니다. 풀텍스트 검색이 핵심 요구라면 EFK 또는 OpenSearch가 더 적합하지만, K8s 운영의 일상적인 디버깅에는 Loki의 라벨 + 그룹 모델이 충분합니다.
Traces — OpenTelemetry 중심의 통합 #
분산 추적의 옛 표준은 두 갈래로 나뉘어 있었습니다 — OpenTracing과 OpenCensus. 두 프로젝트가 합쳐져 만들어진 것이 **OpenTelemetry (OTel)**입니다. 지금은 분산 추적 · 메트릭 · 로그를 같이 다루는 단일 표준이고, CNCF에서도 가장 활발한 프로젝트 중 하나입니다.
OpenTelemetry의 핵심 개념은 셋입니다.
- Instrumentation 라이브러리 — 각 언어별 SDK가 애플리케이션 코드에 삽입되어 trace를 만듭니다. 자동 instrumentation 도구 (Java agent 등)가 코드 변경 없이 붙는 경우도 많습니다.
- OpenTelemetry Collector — 애플리케이션이 보내는 데이터를 받아서 처리 · 라우팅. 일반적으로 K8s에 DaemonSet 또는 Deployment로 띄웁니다.
- 백엔드 — 실제 trace를 저장하고 시각화. Jaeger, Tempo, Datadog, Honeycomb 등.
Trace의 모델 — Span의 트리 #
분산 추적의 단위 데이터는 Span입니다. 한 요청이 여러 서비스를 거치는 동안 각 단계마다 span 한 개가 만들어지고, 부모-자식 관계로 묶여 트리를 이룹니다.
[gateway] /api/orders POST (200ms)
├─ [orders-service] create order (180ms)
│ ├─ [postgres] INSERT orders (15ms)
│ ├─ [postgres] INSERT items (12ms)
│ └─ [kafka] publish order.created (45ms)
└─ [auth-service] verify token (10ms)이 트리를 보면 200ms 중 어느 구간에서 시간이 가장 많이 들었는지가 한눈에 들어옵니다. P99 latency가 평소보다 높을 때 어느 서비스가 원인인지 좁히는 데 trace가 결정적입니다.
Tempo — Loki와 같은 방향의 trace 저장소 #
Grafana Labs는 trace 저장소도 가벼운 모델로 만들었습니다 — Tempo입니다. Loki가 로그에 한 일을 trace에 한 셈입니다. 인덱스를 최소화하고 객체 스토리지 (S3 / GCS)에 trace 본문을 저장합니다. trace ID로 직접 조회하는 데 최적화되어 있고, Loki · Prometheus와 같이 쓸 때 Grafana에서 메트릭 → 로그 → trace로 자연스럽게 흘러가는 흐름이 만들어집니다.
Grafana — 시각화의 표준 #
세 축의 데이터를 한곳에서 들여다보는 도구가 Grafana입니다. Prometheus, Loki, Tempo, Elasticsearch, CloudWatch 등 거의 모든 데이터 소스를 한 대시보드에 묶을 수 있고, 각 패널이 자기 쿼리 언어로 데이터를 가져옵니다.
운영 클러스터의 표준 대시보드 셋은 보통 다음 정도로 구성됩니다.
- 클러스터 개요 — 노드별 CPU · 메모리 · 디스크, Pod 개수, 네임스페이스별 자원 사용
- 워크로드 개요 — Deployment / StatefulSet 별 replica 상태, 재시작 횟수, OOMKilled
- API 서버 헬스 — request rate, error rate, P99 latency, etcd lag
- 각 애플리케이션 — 비즈니스 메트릭 + 4 golden signals (latency, traffic, errors, saturation)
kube-prometheus-stack을 도입하면 클러스터 · 워크로드 · API 서버 대시보드는 사전 구성된 상태로 같이 들어옵니다. 애플리케이션 대시보드만 도메인에 맞춰 새로 만들면 됩니다.
Alerting — Alertmanager의 역할 #
메트릭이 어떤 조건을 만족할 때 알람을 보내는 일은 Prometheus 자체가 아니라 Alertmanager가 담당합니다. Prometheus가 알람 룰을 평가해서 발생한 알람을 Alertmanager로 보내고, Alertmanager가 라우팅 · 묶음 · 침묵 · 반복 처리를 합니다.
PrometheusRule — 알람 정의의 CRD #
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-alerts
namespace: my-app
labels:
release: prometheus
spec:
groups:
- name: my-app
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5..", app="my-app"}[5m]))
/ sum(rate(http_requests_total{app="my-app"}[5m])) > 0.05
for: 5m
labels:
severity: warning
team: payments
annotations:
summary: "High 5xx rate on my-app ({{ $value | humanizePercentage }})"
description: "5xx rate over the last 5 minutes is above 5%."expr이 Prometheus에서 평가되는 조건, for: 5m이 “이 조건이 5분 연속 참이어야 알람 발생"이라는 의미입니다. labels의 severity와 team이 Alertmanager에서 라우팅 키로 쓰입니다.
Alertmanager의 라우팅 #
Alertmanager의 설정에서는 라벨 기반으로 알람을 어디로 보낼지 정합니다.
route:
receiver: default
group_by: ['alertname', 'team']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: critical
receiver: pagerduty
- match:
team: payments
receiver: payments-slack
receivers:
- name: default
slack_configs:
- channel: '#alerts'
- name: pagerduty
pagerduty_configs:
- service_key: ...
- name: payments-slack
slack_configs:
- channel: '#payments-alerts'이 모델 덕에 알람의 라우팅이 코드 (매니페스트)로 관리되고, Slack / PagerDuty / Email 같은 채널로 분기됩니다.
운영 시 잡아 둘 원칙 #
옵저버빌리티 스택은 한 번 잘 갖춰 두면 클러스터의 시야가 단숨에 깊어지지만, 잘못 운영하면 클러스터 자원의 큰 부분을 잡아먹는 도구가 됩니다. 다음 네 가지를 잡아 두는 게 좋습니다.
1. 메트릭 카디널리티 폭발 주의 #
Prometheus는 라벨 조합 하나당 별도 시계열을 만듭니다. 라벨에 user ID, request ID, UUID 같은 고카디널리티 값을 넣으면 시계열 수가 폭발하고, Prometheus가 메모리를 빠르게 소진합니다. 운영 가이드의 첫 번째 원칙은 라벨에 고카디널리티 값을 넣지 마라입니다.
topk(10, count by (__name__)({__name__=~".+"}))이 쿼리로 시계열 수가 많은 메트릭을 주기적으로 점검하는 게 운영의 일부입니다.
2. 보존 기간과 원격 저장소 #
Prometheus의 로컬 저장소는 기본 15일 보존입니다. 그 이상 보관하려면 원격 저장소 (Thanos, Cortex, Mimir, VictoriaMetrics)에 같이 보내야 합니다. 마찬가지로 Loki와 Tempo도 객체 스토리지 (S3 / GCS)에 long-term storage를 두는 게 표준입니다.
보존 기간은 비용과 직결됩니다. 메트릭 6개월, 로그 30일, trace 7일 정도가 일반적인 출발점이고, 각 축의 보존을 따로 정할 수 있습니다. 비용 관점에서의 가드레일은 28장 비용 최적화에서 한 번 더 다룹니다.
3. 알람의 SNR — 너무 많은 알람은 무알람과 같다 #
알람을 너무 많이 만들면 운영자가 알람을 무시하기 시작하고, 정작 중요한 알람도 놓치게 됩니다. 알람 설계의 표준 원칙은 **“알람 한 건 = 사람의 즉시 대응 한 번”**입니다.
- Symptom-based — 원인이 아니라 증상에 알람. “DB 연결 풀이 80% 차 있다"가 아니라 “API의 5xx 비율이 5% 넘는다”.
for기간으로 잡음 제거 — 짧은 스파이크에 알람이 울리지 않도록.- severity 갈래 —
critical은 깨워야 하는 것,warning은 내일 아침에 보면 되는 것. 갈래가 흐릿해지면 무시되기 시작합니다.
4. golden signals를 표준으로 #
Google SRE 문화에서 출발한 4 golden signals는 거의 모든 워크로드 모니터링의 출발점입니다.
| 시그널 | 의미 |
|---|---|
| Latency | 요청 처리 시간 (P50 / P95 / P99) |
| Traffic | 초당 요청 수 |
| Errors | 실패 비율 |
| Saturation | 자원 포화도 (CPU, 메모리, 큐 길이) |
이 넷을 모든 서비스에 같은 형태로 노출해 두면 대시보드도 알람도 표준화됩니다. 도메인 메트릭은 그 위에 얹습니다.
HPA · probe · 디버깅과의 매듭 #
본 챕터의 옵저버빌리티 스택은 본 책 전반의 신호들이 모이는 한 곳이 됩니다. 짧게 묶어 둡니다.
- 11장 resources.requests / limits —
container_memory_working_set_bytes,container_cpu_cfs_throttled_seconds_total시계열이 Prometheus로 들어옵니다. OOMKilled와 CPU throttling의 사후 분석은 본 챕터의 PromQL 위에서 합니다. - 12장 Health check — probe 실패 이벤트가 메트릭 (
kube_pod_container_status_ready)과 로그 양쪽으로 노출됩니다. 알람 룰의 첫 줄이 보통 이 readiness 메트릭입니다. - 13장 오토스케일링 — HPA의 입력 메트릭이 metrics-server 외에 Prometheus Adapter를 거치는 흐름이 본 챕터의 ServiceMonitor와 직접 묶입니다. KEDA도 같은 위치입니다.
- 17장 Admission Controller — webhook latency P99와 거부율을 Alertmanager로 묶는 게 본 챕터의 표준입니다.
- 27장 kubectl 디버깅 패턴 —
describe·logs로 시작하는 진단 트리가 옵저버빌리티 스택의 메트릭 · 로그 시계열로 확장되는 흐름이 자연스럽습니다.
연습문제 #
- 본인의 클러스터에 kube-prometheus-stack을 Helm으로 설치한 뒤,
kubectl get servicemonitor -A와kubectl get prometheusrule -A로 어떤 ServiceMonitor와 PrometheusRule이 사전 구성되어 있는지 확인합니다. 그중 한 PrometheusRule의expr을 직접 읽어 보고, 그 알람이 어떤 증상을 보는지 §“알람의 SNR"의 Symptom-based 원칙과 맞춰 한 단락으로 정리합니다. - 본인 클러스터의 어느 워크로드 한 개에
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))를 직접 쿼리해 P95 latency를 확인합니다. 11장의 CPU throttling 시계열 (container_cpu_cfs_throttled_seconds_total)과 같은 시간대에 보고, throttling이 응답 지연을 키우는 모양이 PromQL 위에서 어떻게 보이는지 한 단락으로 정리합니다. - PrometheusRule 한 장을 직접 적어 봅시다 — “Pod의 OOMKilled가 5분 안에 3회 이상이면 알람” 같은 룰입니다.
expr에 어떤 시계열을 쓸지 (kube_pod_container_status_last_terminated_reason="OOMKilled")를 고르고,for,severity를 §“운영 시 잡아 둘 원칙"의 가드레일에 맞춰 설정합니다. Alertmanager의 라우팅에서 이 알람을 어느 채널로 보낼지도 한 단락으로 결정합니다.
한 줄 요약: K8s 옵저버빌리티의 표준 묶음은 메트릭의 Prometheus + kube-state-metrics + node-exporter, 로그의 Loki, 트레이스의 OpenTelemetry + Tempo, 시각화의 Grafana, 알람의 Alertmanager 다. kube-prometheus-stack Helm 차트가 도입의 첫 단계이고, ServiceMonitor · PodMonitor · PrometheusRule의 CRD가 18장의 Operator 모델 위에서 매니페스트 차원의 옵저버빌리티를 만든다. 운영의 네 가드레일은 카디널리티 폭발 회피 · 보존 기간과 원격 저장소 · 알람 SNR · golden signals 다.
다음 챕터 #
본 챕터까지 클러스터의 시야를 만드는 도구를 정리했습니다. 다음 챕터는 그 클러스터가 어떻게 변경되는가의 운영 모델을 다룹니다. 지금까지의 흐름은 사람이 kubectl apply로 매니페스트를 적용하는 모델이었습니다. 그러나 여러 사람 · 여러 환경 · 여러 클러스터가 같이 굴러가는 운영에서는 매니페스트의 source of truth가 사람의 손이 아니라 git에 있어야 합니다.
20장 GitOps에서는 ArgoCD와 Flux의 모델을 다룹니다. git의 매니페스트가 클러스터에 자동으로 동기화되는 흐름, drift detection, sync policy, multi-cluster 운영 패턴, 18장의 status subresource가 ArgoCD의 drift 검출과 어떻게 맞물리는지까지 한 사이클로 정리하며 3부를 마무리합니다.