Production observability stack
“Why is my app slow?” — самый дорогой вопрос в production. Логи показывают что случилось, метрики — сколько этого было, трассы — где именно в распределённой системе. Без всех трёх — debugging превращается в гадание. С ними — большинство incident-ов диагностируется за минуты.
В K8s сложилась де-факто стандартная стек наблюдаемости: Prometheus для metrics, Loki (или Elasticsearch) для logs, Jaeger (или Tempo) для traces, Grafana как unified UI. Понять, как эти компоненты собираются вместе — значит знать, куда смотреть на любой production-проблеме. CKAD-scope — общее понимание архитектуры; глубокая настройка — SRE-scope. Но без верхнего уровня инженер не понимает, почему kubectl logs недостаточно для production.
eBPF: безопасная инспекция kernel в runtime
Three pillars of observability
Концепция, формализованная в 2017 году Cindy Sridharan и принятая индустрией:
- Logs — что произошло. События, ошибки, аудит. Discrete events с timestamp.
- Metrics — измерения. Counters (counter never decreases), gauges (current value), histograms (distribution).
- Traces — pathway through distributed system. Один request — много spans от разных сервисов.
Каждый pillar отвечает на свой класс вопросов:
| Вопрос | Pillar |
|---|---|
| Что случилось в этот момент? | Logs |
| Какой был CPU/memory за последний час? | Metrics |
| Куда ушёл этот request через 7 микросервисов? | Traces |
| Почему latency p99 вырос с 100ms до 2s? | Metrics → Traces → Logs |
Современный observability — корреляция между всеми тремя: tracing с trace_id → найти related logs и metrics с тем же trace_id.
Logs: Fluent Bit → Loki
K8s logging architecture основана на простой механике: stdout/stderr контейнера → файл на ноде → log aggregator → centralized storage.
Structured logging — обязательное требование
# Плохо
print(f"User {user_id} requested {url} from IP {ip}")
# Хорошо
import json
print(json.dumps({
"level": "info",
"msg": "request handled",
"user_id": user_id,
"url": url,
"ip": ip,
"duration_ms": duration,
"trace_id": current_trace_id()
}))
JSON — структурированный формат, который Fluent Bit/Vector парсит и сохраняет как отдельные поля. В Grafana/Kibana — можно делать user_id=42 точечный поиск, duration_ms > 1000 фильтр медленных requests, агрегацию по url. Текстовые логи требуют regex и не масштабируются.
Никогда не использовать print форматирование с PII в production без обдуманного masking. Логи попадают в centralized storage, к которому имеют доступ ops-команды — это может быть нарушением GDPR / PCI-DSS, если passwords, credit cards, или personal data попадают в логи.
Metrics: Prometheus stack
Полный production-grade стек — kube-prometheus-stack Helm chart (community-maintained, бывший Prometheus Operator).
Компоненты:
Prometheus
TSDB (time-series database) — пишет metrics. Каждые 15-30s scrape от target-ов через HTTP /metrics endpoint (Prometheus exposition format). Retention по умолчанию 15 дней.
# /metrics format
http_requests_total{method="GET",status="200"} 12345
http_requests_total{method="POST",status="201"} 678
node_cpu_seconds_total{cpu="0",mode="idle"} 98765.43
Scrape targets (что Prometheus собирает)
В K8s четыре основных источника:
-
kube-state-metrics — отдельный Deployment, экспонирует state K8s API objects:
kube_pod_status_phase{namespace="prod", pod="web-1", phase="Running"} 1 kube_deployment_status_replicas_available{deployment="web"} 3 kube_pod_container_status_restarts_total{pod="web-1", container="app"} 7 -
node-exporter — DaemonSet, экспонирует node OS metrics:
node_cpu_seconds_total{cpu="0", mode="user"} 12345.67 node_memory_MemAvailable_bytes 8.5e+09 node_filesystem_avail_bytes{mountpoint="/"} 5.0e+10 node_network_receive_bytes_total{device="eth0"} 1.2e+11 -
cAdvisor (встроен в kubelet) — container metrics:
container_cpu_usage_seconds_total{pod="web-1", container="app"} 234.56 container_memory_working_set_bytes{pod="web-1"} 2.5e+08 container_fs_usage_bytes{pod="web-1"} 1.2e+07 -
Application metrics — apps сами экспонируют
/metrics:http_requests_total{handler="/api/users", method="GET", status="200"} 12345 db_query_duration_seconds_bucket{le="0.1"} 9000 cache_hit_ratio 0.87
node-exporter ≠ kube-state-metrics. node-exporter — это USAGE OS-уровня (CPU, memory, disk на ноде). kube-state-metrics — STATE K8s API objects (сколько Pods в Pending, сколько replicas Deployment). Обе ставятся вместе.
Grafana
UI для visualization. Подключается к Prometheus как data source. Dashboards — JSON-конфигурации с PromQL queries.
Community mixin (e.g., kubernetes-mixin) — готовые dashboards для общих use cases. Установить через kube-prometheus-stack, чтобы не строить с нуля.
AlertManager
Принимает alerts от Prometheus (alerting rules), роутит по labels:
# Prometheus rule
groups:
- name: web-alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "High 5xx error rate on web app"
AlertManager роутит по severity=critical → Slack/PagerDuty, по severity=warning → email. Поддерживает inhibition (если alert A — не slать alert B), grouping (несколько одинаковых alerts — одно сообщение).
ServiceMonitor / PodMonitor CRDs
Prometheus Operator вводит CRDs для declarative scrape configuration:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: web
labels:
release: prometheus # match prometheus selector
spec:
selector:
matchLabels:
app: web # selector по Service labels
endpoints:
- port: metrics # port имя в Service
interval: 30s
path: /metrics
ServiceMonitor говорит Prometheus: “scrape все Service с labels app=web, port metrics, каждые 30s”. Альтернативно — PodMonitor (без Service, по Pod labels).
Это идиоматичный способ настройки в K8s — не редактируешь prometheus.yaml, а декларируешь через CR.
PromQL basics
PromQL — query language Prometheus. Core constructs:
# Instant vector — текущие значения
http_requests_total
# Range vector — массив значений за интервал
http_requests_total[5m]
# Rate — производная (events per second)
rate(http_requests_total[5m])
# Sum по labels
sum by (status) (rate(http_requests_total[5m]))
# Memory usage per Pod
sum by (pod) (container_memory_working_set_bytes{namespace="prod"})
# 95th percentile latency
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
)
# Error rate %
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
rate() — вычисляет per-second rate counter-а. sum by() — агрегирует по labels. histogram_quantile() — для histogram metrics.
Counter vs Gauge: counter monotonically increasing (http_requests_total), nelze применять sum напрямую — нужен rate(). Gauge — текущее значение (container_memory_usage_bytes), можно sum напрямую.
Traces: OpenTelemetry → Jaeger / Tempo
Trace — это lifecycle одного request через распределённую систему. Состоит из spans — единицы работы в одном сервисе.
trace_id: abc123
├── span: web-server handle request (200ms)
│ ├── span: auth-service verify token (15ms)
│ ├── span: db query users (50ms)
│ │ ├── span: pg connection acquire (5ms)
│ │ └── span: pg execute query (40ms)
│ └── span: cache get user-profile (3ms)
Сразу видно: total 200ms, 25% на DB query. Без trace это invisible.
OpenTelemetry (OTel)
Vendor-neutral standard для instrumentation. Заменил предыдущие OpenTracing + OpenCensus.
Архитектура:
- OTel SDK в app — auto или manual instrumentation создаёт spans.
- OTel Collector — DaemonSet или Deployment, принимает spans от apps, batch и forward.
- Backend — Jaeger, Tempo, Datadog, Honeycomb, etc.
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-collector
spec:
config: |
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
exporters:
otlp:
endpoint: tempo:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
Apps шлют OTLP (OpenTelemetry Protocol) на collector, collector forwarding в Tempo. Можно roundtrip переключить backend без изменения apps.
Jaeger / Tempo
Jaeger (CNCF) — UI и backend для traces. Поддерживает search by trace_id, filter by service, latency analysis.
Tempo (Grafana Labs) — modern alternative. Хранит только traces (no index), полагается на logs/metrics для search. Дёшево scale.
В Grafana — все три pillar в одном UI с корреляцией: смотришь metrics → видишь spike → клик на trace из этого момента → видишь slow span → клик на logs этого span → видишь error.
CKAD scope
На CKAD должен знать:
kubectl logs— для просмотра логов Pod (есть в модуле 14).kubectl top— для metrics через metrics-server (есть в модуле 14).- Что в production стандартный стек: Prometheus + Grafana + Loki + Jaeger.
- Structured logging — обязательное в JSON для centralized storage.
- HPA использует metrics-server (resource metrics) или Prometheus Adapter (custom metrics).
SRE/CKA scope (не для CKAD):
- Configuration Prometheus rules, AlertManager routing.
- Operator-level setup kube-prometheus-stack.
- OpenTelemetry collector configuration.
- Глубокий PromQL и dashboard authoring.
Но CKAD-инженер должен понимать общую архитектуру, чтобы effective сотрудничать с SRE-командой и instrument свои apps.
App-level requirements для observability
App должен:
- Logs в stdout/stderr в JSON формате (см. structured logging).
/metricsendpoint в Prometheus format (через client_python, prom-client, prometheus-go)./healthzдля readiness/liveness probes (через те же metrics endpoint часто).- OpenTelemetry SDK для traces — middleware для HTTP/gRPC автоматически создаёт spans.
- Correlation IDs: trace_id в logs (через context propagation), чтобы корреляция logs ↔ traces работала.
Без этих 5 пунктов app — black box в production. С ними — full observability с минимальной болью.
Killer-моменты
- Three pillars — logs/metrics/traces — не альтернативы, а complement. Каждый отвечает на свой класс вопросов. Production требует все три.
- Structured logging (JSON) — mandatory. Текстовые logs не масштабируются в централизованных storage. Use libraries:
structlog(Python),zap(Go),pino(Node.js). - node-exporter ≠ kube-state-metrics. Разные scopes: node OS vs K8s API objects.
- kube-prometheus-stack — Helm chart, который ставит весь Prometheus + Grafana + AlertManager + node-exporter + kube-state-metrics одним command. Стандарт для self-hosted.
- ServiceMonitor / PodMonitor CRDs — declarative scrape config. Альтернатива редактированию prometheus.yaml.
- PromQL
rate()для counters,sum by()для агрегаций.histogram_quantile()для percentiles. - OpenTelemetry — стандарт для tracing 2025+. Заменил OpenTracing/OpenCensus. Vendor-neutral, backend swappable.
- Корреляция через trace_id: logs/metrics/traces должны включать trace_id для cross-pillar навигации в Grafana.