Learning Platform
Глоссарий Troubleshooting
Урок 20.05 · 25 мин
Средний
observabilitylogsmetricstracesPrometheusGrafanaLokiFluent BitOpenTelemetryJaegerAlertManagerServiceMonitorPromQLstructured logging

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 и принятая индустрией:

  1. Logs — что произошло. События, ошибки, аудит. Discrete events с timestamp.
  2. Metrics — измерения. Counters (counter never decreases), gauges (current value), histograms (distribution).
  3. 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.

Цепочка K8s logging
container stdoutПриложение пишет в stdout/stderr. Container runtime (containerd, CRI-O) перенаправляет это в файл на ноде: /var/log/containers/POD_NAME_NAMESPACE_CONTAINER-ID.log. Это симлинк на /var/log/pods/.... Формат — JSON с полями log, stream, time.
file on nodekubelet (через CRI) gerencia файлы логов. Rotation встроен — log-opts: max-size, max-file. Без них файлы могут заполнить disk.
DaemonSet on each node
Fluent Bit / VectorDaemonSet — Pod на каждой ноде. Tail файлы /var/log/containers/*.log. Парсит JSON, enrichит metadata: pod_name, namespace, container_name, labels из K8s API. Forwarding по сети в centralized storage.
enrichmentК каждой log line добавляет K8s metadata: pod, namespace, labels, node, container_id. Это позволяет в Loki/ES делать фильтрацию по {namespace="prod", app="web"}.
forward over network
Loki / ElasticsearchLoki (Grafana Labs) — log aggregator, index только по labels (не full-text по тексту). Дёшево, scalable, LogQL для запросов. Elasticsearch — full-text search, дороже но мощнее. Cloud-managed альтернативы: CloudWatch Logs, Datadog Logs.
Grafana / KibanaUI для поиска и анализа. Grafana с LogQL — стандартное решение для Loki. Kibana — для Elasticsearch.

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 и не масштабируются.

WARNING

Никогда не использовать 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 четыре основных источника:

  1. 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
  2. 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
  3. 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
  4. 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
NOTE

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.

TIP

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.

Архитектура:

  1. OTel SDK в app — auto или manual instrumentation создаёт spans.
  2. OTel Collector — DaemonSet или Deployment, принимает spans от apps, batch и forward.
  3. 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 должен:

  1. Logs в stdout/stderr в JSON формате (см. structured logging).
  2. /metrics endpoint в Prometheus format (через client_python, prom-client, prometheus-go).
  3. /healthz для readiness/liveness probes (через те же metrics endpoint часто).
  4. OpenTelemetry SDK для traces — middleware для HTTP/gRPC автоматически создаёт spans.
  5. 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.

Проверка знанийKnowledge check
App пишет логи в файл /var/log/app.log внутри Pod. Команда жалуется что в Grafana Loki ничего не видно. Почему и как починить?
ОтветAnswer
K8s logging architecture работает только с stdout/stderr — kubelet через CRI собирает stdout контейнера в файл на ноде (/var/log/containers/*.log), Fluent Bit DaemonSet tail эти файлы и forwarding в Loki. Файлы ВНУТРИ Pod (/var/log/app.log) не видны для kubelet и log aggregator — это просто файл в файловой системе контейнера, который умирает с Pod. Решения: (1) Перенастроить app писать в stdout/stderr (правильное cloud-native решение, factor XI из 12-factor). (2) Sidecar pattern: дополнительный контейнер с tail -F /var/log/app.log который выводит в stdout. Sidecar шарит volume с main контейнером. Это workaround для legacy apps. (3) Volume mount на hostPath /var/log/myapp + настройка Fluent Bit чтобы tail оттуда — antipattern, привязка к ноде. Right answer — stdout.
Проверка знанийKnowledge check
Что такое histogram metric в Prometheus и зачем он нужен для measuring latency?
ОтветAnswer
Histogram — тип Prometheus metric, который записывает distribution значений по предопределённым buckets. Counter (например http_requests_total) — это одно число. Gauge (например memory_usage_bytes) — это текущее значение. Histogram — collection counters per bucket: для каждого порога le (less or equal) хранится счётчик. Это значит: 1500 запросов под 100ms, 1800 под 500ms, 1850 под 1s, 1900 total. Зачем для latency: (1) Average latency обманчив — один запрос на 10s + 999 быстрых = average плохой, но 999 ok. (2) Percentile (p50, p95, p99) — реальная картина distribution. Histogram позволяет считать percentile через histogram_quantile — 5 процентов запросов медленнее N. Это standard для SLO/SLI. Альтернатива — summary metric (Prometheus считает percentile на client-side, точнее, но нельзя aggregate across instances).
Проверка знанийKnowledge check
В чём преимущества Loki над Elasticsearch для log aggregation в K8s?
ОтветAnswer
Loki (Grafana Labs) и Elasticsearch — оба log aggregators, но архитектурно разные. Elasticsearch: full-text inverted index по содержимому логов. Powerful search (любые слова, regex), но дорого в storage и compute — index сравним по размеру с самими данными. Loki: index ТОЛЬКО по labels (namespace, pod, app, level), содержимое логов хранится как сырой текст с compression (gzip/snappy). Преимущества Loki: (1) Cheap storage — данные в S3/GCS, минимальный compute. (2) Native K8s integration — labels автоматически из K8s metadata. (3) Тот же LogQL похож на PromQL — единый язык для logs и metrics в Grafana. (4) Multi-tenancy. Недостатки Loki: full-text search slower (для огромных volumes нужно grep по сырому тексту в storage). Когда выбирать: Loki для большинства K8s use cases (cheap, simple, integrated). Elasticsearch когда full-text search критичен (security forensic, complex queries). Cloud-managed: CloudWatch Logs, GCP Logging — managed альтернативы.
Проверка знанийKnowledge check
Что такое trace_id propagation и почему он критичен для correlation между logs, metrics, traces?
ОтветAnswer
trace_id — уникальный ID, генерируется в самом начале request lifecycle (обычно при входе во frontend / API gateway). Через все downstream calls он передаётся в HTTP header (traceparent W3C standard) или gRPC metadata. Каждый сервис propagate его дальше при call к следующим. Propagation реализуется OpenTelemetry SDK: middleware/interceptor автоматически читает traceparent header входящего request и attaches его к outgoing requests. Важно: app должна сама **логировать trace_id** в каждый log line. Тогда: в Grafana видишь metric spike → клик на trace из этого момента → видишь slow span → клик на "logs for this trace" → Grafana запрашивает Loki: {trace_id="abc123"} → видишь все log lines от всех сервисов с этим trace_id. Без propagation/logging trace_id — невозможно correlate. У 90% production-проблем root cause находится через эту корреляцию: 'видим slow trace, в логах сервиса X error при попытке подключиться к Redis'.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Какие 'three pillars of observability' и какой класс вопросов отвечает каждый?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5