lenec ru

← все посты

Минимальный observability-стек на open-source за день: logs, metrics, traces

19K

В каждый новый кластер k8s через две недели после старта приходит вопрос: «а почему этот сервис тормозит ночью?». И ты понимаешь, что ничего не видишь — ни логов, ни метрик, ни трейсов. Можно подписаться на платный SaaS за $5 на хост в месяц, а можно за день поднять open-source стек, который покрывает 95% потребностей. Покажу, что я ставлю и как это конфигурирую.

Версия — k8s 1.30, Helm-чарты на момент середины 2026.

Что в стеке

Три столпа observability — logs, metrics, traces. Под каждый — отдельная база и отдельный сборщик. Я беру:

  • Метрики: Prometheus + node-exporter + kube-state-metrics. Хранение — Mimir или просто локальный TSDB в Prometheus с retention.
  • Логи: Loki + Promtail (или Vector / Alloy). Хранение — S3-совместимый object storage.
  • Трейсы: Tempo + OpenTelemetry Collector. Хранение — тоже S3.
  • Визуализация: Grafana (одна на всех).

Это всё от Grafana Labs, оно отлично интегрировано между собой и не требует extra-обвязки. Альтернатива — связка Prometheus + Elastic + Jaeger, но там больше движущихся частей.

Поднимаем за день

Шаг 1: object storage

Loki и Tempo пишут чанки в S3. В облаке берёшь S3, в on-prem — MinIO. Поднимаешь bucket, лучше два — отдельный для логов и для трейсов.

aws s3 mb s3://company-loki-chunks --region eu-central-1
aws s3 mb s3://company-tempo-blocks --region eu-central-1

Если есть отдельный bucket для метрик через Mimir — ещё один. Для маленького кластера хватит и одного на всё, разделять по prefix.

Шаг 2: Helm и kube-prometheus-stack

Базу metric-stack ставим одним чартом:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install monitoring \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --values monitoring-values.yaml

В monitoring-values.yaml я ставлю минимум:

prometheus:
  prometheusSpec:
    retention: 30d
    retentionSize: 50GB
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: gp3
          resources:
            requests:
              storage: 100Gi
    resources:
      requests:
        cpu: 500m
        memory: 2Gi
      limits:
        memory: 4Gi

grafana:
  adminPassword: 
  persistence:
    enabled: true
    size: 10Gi
  ingress:
    enabled: true
    hosts:
      - grafana.example.com

Получаешь Prometheus (с node-exporter, kube-state-metrics, операторами) и Grafana с готовыми дашбордами.

Шаг 3: Loki

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm install loki grafana/loki \
  --namespace monitoring \
  --values loki-values.yaml

В loki-values.yaml:

loki:
  schemaConfig:
    configs:
      - from: "2024-04-01"
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
  storage:
    type: s3
    s3:
      region: eu-central-1
      bucketnames: company-loki-chunks
  limits_config:
    retention_period: 30d

deploymentMode: SimpleScalable

backend:
  replicas: 2
read:
  replicas: 2
write:
  replicas: 3

SimpleScalable — это middle ground между monolithic (для совсем маленьких) и distributed (для крупных). Хорошо подходит для кластеров до пары десятков нод.

Дальше — сборщик логов. Я беру Promtail или Alloy (новый Grafana-агент).

# promtail-values.yaml
config:
  clients:
    - url: http://loki-gateway/loki/api/v1/push
  snippets:
    extraScrapeConfigs: |
      - job_name: kubernetes-pods
        kubernetes_sd_configs:
          - role: pod
        # ... стандартные relabels из чарта

Шаг 4: Tempo

helm install tempo grafana/tempo-distributed \
  --namespace monitoring \
  --values tempo-values.yaml

tempo-values.yaml:

storage:
  trace:
    backend: s3
    s3:
      bucket: company-tempo-blocks
      endpoint: s3.eu-central-1.amazonaws.com
      region: eu-central-1

retention: 168h  # 7 дней

ingester:
  replicas: 3
distributor:
  replicas: 2
querier:
  replicas: 2

Перед Tempo стоит OpenTelemetry Collector, в который льют трейсы все приложения по OTLP-протоколу.

helm install otel-collector \
  open-telemetry/opentelemetry-collector \
  --namespace monitoring \
  --values otel-values.yaml

otel-values.yaml:

mode: deployment
config:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317
        http:
          endpoint: 0.0.0.0:4318
  exporters:
    otlp:
      endpoint: tempo-distributor:4317
      tls:
        insecure: true
  service:
    pipelines:
      traces:
        receivers: [otlp]
        exporters: [otlp]

Шаг 5: подключаем datasources в Grafana

В values kube-prometheus-stack:

grafana:
  additionalDataSources:
    - name: Loki
      type: loki
      url: http://loki-gateway.monitoring.svc:80
      access: proxy
    - name: Tempo
      type: tempo
      url: http://tempo-query-frontend.monitoring.svc:3100
      access: proxy

После apply Grafana показывает все три источника, можно перепрыгивать с метрики на логи и на трейс одним кликом — это и есть «correlated observability», ради которого всё это собирается.

Инструментация приложений

Стек подняли, но пока пустой. Дальше — научить приложения отдавать данные.

Метрики Prometheus

В Go это prometheus/client_golang, эндпоинт /metrics:

import "github.com/prometheus/client_golang/prometheus/promhttp"

mux.Handle("/metrics", promhttp.Handler())

А в k8s deployment:

spec:
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/metrics"

Лучше использовать ServiceMonitor (CRD из prometheus-operator) — это типизированный вариант, без аннотаций.

Структурированные логи

JSON-логи в stdout, дальше Promtail/Alloy подхватит. В Go это slog:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("request handled",
    "method", r.Method,
    "path", r.URL.Path,
    "duration_ms", time.Since(start).Milliseconds(),
)

Loki хорошо парсит JSON и даёт LogQL для фильтрации:

{namespace="prod", app="api"} | json | duration_ms > 500

Трейсы через OTLP

В Go — go.opentelemetry.io/otel:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/trace"
)

exporter, _ := otlptracegrpc.New(ctx,
    otlptracegrpc.WithEndpoint("otel-collector.monitoring.svc:4317"),
    otlptracegrpc.WithInsecure())

tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
otel.SetTracerProvider(tp)

Дальше в обработчиках оборачиваешь критичные участки в spans, и они появляются в Tempo.

Что я ставлю в обязательном минимуме

Дашборды:

  • Из kube-prometheus-stack — Cluster, Nodes, Pods, Workloads. Это уже идёт коробочно.
  • Kubernetes Views: Pods (15760) — детальный по поду.
  • Дашборд per-сервис — копируешь шаблон, меняешь labels.

Алёрты:

  • node memory > 90% — кончается RAM на ноде.
  • pod restart count > 5 per hour — что-то постоянно падает.
  • http_5xx_rate > 1% — конкретный сервис ломается.
  • certmanager_certificate_expiry_seconds < 7d — сертификат не обновляется.

Сноска про затраты

На небольшом кластере (10 нод, 200 pods) этот стек съедает примерно:

  • ~6 vCPU и ~16 ГБ RAM суммарно (Prometheus, Loki, Tempo, Grafana).
  • ~50 ГБ S3 в месяц на логи/трейсы при retention 30/7 дней.

Это $30-50 в месяц в облаке, плюс компьют на нодах. Дешевле любого managed-сервиса того же охвата.

Что запомнить

Один день — реально, если без избыточной кастомизации. Берёшь kube-prometheus-stack, Loki, Tempo, OpenTelemetry Collector. Object storage в S3 для chunks. Один Grafana с тремя datasources. Дальше учишь приложения отдавать /metrics, JSON-логи и OTLP-трейсы. Через неделю поймёшь, что без этого жить нельзя.

Куда копать: prometheus-community helm charts, документация Grafana Labs по Loki и Tempo, OpenTelemetry. Если нужен long-term storage метрик — Mimir или Thanos.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.