Перейти к содержанию

05. Observability-стек локально

Как работать с Prometheus / Grafana / Loki / Tempo в локальном docker- compose: куда заходить, какие базовые запросы, как связать три сигнала. Reference по инструментации — в ../conventions/observability.md. Полный debugging flow production-инцидента — там же в разделе «Debugging production issue».

Содержание

Доступ

Все компоненты поднимаются docker compose up в сервис-репо (см. 03-local-stack.md):

Компонент URL Login
Grafana http://localhost:3000 admin / admin (при первом входе смена)
Prometheus http://localhost:9090
Loki http://localhost:3100 — (через Grafana)
Tempo http://localhost:3200 — (через Grafana)
pprof (сервис) http://localhost:\<service-port>/debug/pprof/ BasicAuth из .env

Datasource'ы в Grafana уже настроены в compose-конфиге: Prometheus как default, Loki и Tempo как дополнительные. Проверить — Grafana → Connections → Data sources.

Grafana

Дашборды per-сервис

Открой Grafana → Dashboards. Там предзаливаются типовые dashboard'ы:

  • HTTP API — RPS, latency p50/p95/p99, error rate per route.
  • Kafka — lag, publish/consume rate, DLQ size.
  • Runtime — goroutines, heap, GC, FD count.
  • Outbox — unpublished rows, forwarder lag (если сервис использует outbox).

Если dashboard'а нет — создай через Import из infra-репо или собери «с нуля» (Add new panel → выбираешь metric).

Explore

Compass icon слева → Explore. Это interactive query UI, быстрее дашборда, когда копаешь конкретный запрос.

  • Data source dropdown вверху — Prometheus / Loki / Tempo.
  • Split view (кнопка справа) — два запроса рядом, например, logs + traces одного запроса.

Дата-пикер

Стандартно — Last 15m, Last 1h, Last 24h. Для инцидентов — точные границы (Custom range → окно инцидента ±10 минут).

Prometheus

UI

http://localhost:9090 — graph, alerts, targets, rules.

  • Status → Targets — все ли scrape-endpoint'ы отвечают зелёным? Красный таргет = сервис не доступен / /metrics не отвечает / wrong port.
  • Graph — вводишь PromQL, получаешь график + table.
  • Alerts — какие alert-rule'ы сейчас firing или pending.

Базовые PromQL-запросы

Rate запросов за 5 минут:

rate(http_requests_total{service="review"}[5m])

Error rate:

sum(rate(http_requests_total{service="review",code=~"5.."}[5m]))
  / sum(rate(http_requests_total{service="review"}[5m]))

p99 latency:

histogram_quantile(0.99,
  sum by (le, route) (
    rate(http_request_duration_seconds_bucket{service="review"}[5m])
  )
)

Top-5 endpoint'ов по ошибкам:

topk(5,
  sum by (route) (rate(http_requests_total{code=~"5.."}[5m]))
)

Goroutines:

go_goroutines{service="review"}

Kafka lag:

kafka_consumer_lag{service="notification"}

Outbox:

outbox_unpublished_rows{service="review"}

Подробнее — ../conventions/observability.md и ../conventions/slo-and-budget.md.

Интерпретация графиков

  • Плоский 0 — метрика не экспонируется. Проверь /metrics вручную: curl http://localhost:<port>/metrics | grep <metric>.
  • Плоский NaN — делитель нулевой. Обычно: error rate при 0 RPS.
  • Пилообразный график — counter пересобран (pod рестарт). Используй rate, он сглаживает.
  • Лестница — точная фиксация между scrape-интервалами. Ничего страшного, rate[5m] размажет.

Loki

Loki — log aggregation, доступ через Grafana Explore → Loki datasource.

LogQL: основные запросы

Все логи сервиса:

{service="review"}

Логи одного request'а по correlation:

{service="review"} | json | correlation_id="01HZ3G..."

Только errors:

{service="review"} | json | level="error"

Error с подстрокой в message:

{service="review"} |= "deadline" | json | level="error"

Группировка ошибок за окно (top error-messages):

topk(10,
  sum by (msg) (
    count_over_time({service="review"} |= "ERROR" | json [5m])
  )
)

Grafana Explore → «Explain query» объясняет план выполнения и рекомендует индексы.

Особенности

  • Поиск идёт по label'ам → всегда начинай с {service="..."}.
  • Фильтр |= "..." — поиск подстроки в теле.
  • | json — парсит JSON-поля как структурные.
  • Ретенция — 7 дней локально.

Подробнее — ../how-to/read-logs.md.

Tempo

Tempo — distributed tracing, доступ через Grafana Explore → Tempo datasource.

Поиск trace

Варианты ввода:

  • TraceID — если знаешь, вводишь в «Trace ID» поле. Быстрее всего.
  • TraceQL — язык запросов:
{ resource.service.name = "review" && name = "POST /v1/reviews" }

Найдёт trace'ы handler'а POST /v1/reviews.

  • Фильтры в UI — service name, span name, min duration — для случаев «покажи медленные запросы к review».

Чтение waterfall

Trace = дерево span'ов. В Grafana видишь:

  • Horizontal bar — длительность span'а.
  • Nested — parent/child span'ы (HTTP-handler → service → pgx → Kafka-publish).
  • Logs (справа при клике на span) — span events, error если был.
  • Node graph — топология сервисов, через какие прошёл запрос.

Типичный debugging: открыть trace проблемного запроса → найти самый длинный span → понять, где время проводится (SQL / внешний HTTP / Kafka).

Retention

Локально — 24 часа. В prod — зависит от Tempo-tier, обычно 7 дней.

Подробнее — ../how-to/read-traces.md.

Связка трёх сигналов

Через общий trace_id и correlation_id можно прыгать между системами:

  1. Prometheus alert сработал → ссылка «runbook» в alert → соответствующий troubleshooting/runbook.
  2. Grafana Dashboard → аномалия на графике → клик на точку → «View logs in Loki» / «View traces in Tempo» (если включены derived links в datasource).
  3. Tempo trace → в span'е есть лог event → клик → Loki с тем же trace_id.
  4. Loki лог → содержит trace_id → клик в Grafana → Tempo.

Derived links настраиваются в Grafana → Connections → Data sources → Loki / Tempo → Derived fields / Trace to logs.

Типовые сценарии

«5xx на endpoint'е»

  1. Grafana → Review Dashboard → panel «Error rate by route»: где именно?
  2. Explore → Loki: {service="review"} | json | level="error" за 15 минут до пика.
  3. В логе находим request_id, trace_id.
  4. Explore → Tempo → вставить trace_id → waterfall. Какой span упал?
  5. Если SQL — §§db-slow-query.md. Если внешний HTTP — retry-and-circuit-breaker.md. Если panic — smoke test и фикс.

«Latency выросла»

  1. Dashboard → panel p99 latency per route: какой route?
  2. Explore → Prometheus:
    histogram_quantile(0.99,
      rate(http_request_duration_seconds_bucket{route="/v1/reviews"}[5m]))
    
  3. Tempo → выбрать медленный trace в окне.
  4. Какой span дольше обычного? (pgx / HTTP / Redis).
  5. Troubleshooting по конкретному span'у.

«Consumer лаг растёт»

  1. Dashboard → panel kafka_consumer_lag → какой topic/group.
  2. Prometheus:
    rate(messages_processed_total{group="notification-on-review"}[5m])
    
    Упал до 0 — consumer стоит; колеблется — rebalance.
  3. Loki: {service="notification"} | json | level="error" — что в логах?
  4. ../troubleshooting/kafka-consumer-stuck.md или ../troubleshooting/kafka-rebalancing.md.

«Память растёт»

  1. Dashboard runtime → panel heap_inuse + go_goroutines.
  2. Если растёт — см. ../troubleshooting/memory-leak.md.

pprof

Локально pprof открыт без auth (за loopback):

# heap snapshot
go tool pprof -http=:8080 http://localhost:<port>/debug/pprof/heap

# goroutine текстом
curl -s 'http://localhost:<port>/debug/pprof/goroutine?debug=1' | less

# CPU profile (30 секунд сбора)
go tool pprof -http=:8081 http://localhost:<port>/debug/pprof/profile?seconds=30

Подробнее — ../how-to/profile-service.md и ../troubleshooting/memory-leak.md.

Troubleshooting самого стека

Grafana показывает «no data»

  • Сервис не экспортирует метрику: curl http://localhost:<port>/metrics | grep <metric_name>.
  • Prometheus не скрейпит: Status → Targets → твой сервис «DOWN»?
  • Неправильный PromQL: синтаксис, label'ы.

Tempo не показывает trace'ы

  • OTEL_EXPORTER_OTLP_ENDPOINT не указывает на Tempo: docker compose logs <service> | grep otel.
  • Tempo не поднялся: docker compose ps tempo → healthy?
  • SampleRate в конфиге слишком маленький (1%): подними до 100% локально.

Loki не видит логи

  • Сервис пишет в stdout? (Правильно.) JSON-format? (Правильно.)
  • Promtail (log-shipper) работает? docker compose ps promtail / docker compose logs promtail.

Графики «трудно читать» — нет цветов / делений

Grafana 10+ default-theme иногда кривой. Settings → Preferences → Theme → Dark / Light, Refresh dashboard.

См. также