OpenTelemetry: единый стандарт трейсов, метрик и логов

Зачем нужен единый стандарт наблюдаемости

Компания «МикроСерв» — 40 микросервисов, 3 языка (Go, Python, Java). Текущий стек наблюдаемости:

  • Метрики: Prometheus + Grafana
  • Логи: ELK (Elasticsearch + Logstash + Kibana)
  • Трейсы: Jaeger (только 8 из 40 сервисов инструментированы)

Проблемы:

  • Нет корреляции: ошибка в логах → невозможно найти трейс → невозможно понять, какой сервис сломался
  • Vendor lock-in: переход с Jaeger на Grafana Tempo потребует переписать инструментацию во всех сервисах
  • Разные SDK: Go-сервисы используют один Jaeger SDK, Python — другой, Java — третий

OpenTelemetry (OTel) — единый стандарт (CNCF), который решает все три проблемы: один SDK для всех языков, один протокол (OTLP), один Collector для маршрутизации данных в любой бэкенд.

# Архитектура OTel
# App (SDK) → OTLP → Collector → Prometheus (metrics)
#                              → Tempo/Jaeger (traces)
#                              → Loki (logs)
#
# Смена бэкенда = изменение конфига Collector, не кода

OTel Collector: центральный хаб телеметрии

OTel Collector принимает, обрабатывает и экспортирует телеметрию. Три пайплайна: traces, metrics, logs.

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  resource:
    attributes:
      - key: environment
        value: production
        action: upsert

exporters:
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheusremotewrite]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, resource, batch]
      exporters: [loki]
# Запуск Collector
docker run -d --name otel-collector \
  -p 4317:4317 -p 4318:4318 \
  -v ./otel-collector-config.yaml:/etc/otelcol/config.yaml \
  otel/opentelemetry-collector-contrib:latest

Инструментация приложений: Go, Python, Java

Go-сервис с автоинструментацией HTTP и gRPC:

// main.go
import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/trace"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func initTracer() func() {
    exp, _ := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint("otel-collector:4317"),
        otlptracegrpc.WithInsecure(),
    )
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exp),
        trace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName("order-service"),
            semconv.ServiceVersion("1.2.0"),
        )),
    )
    otel.SetTracerProvider(tp)
    return func() { tp.Shutdown(ctx) }
}

// HTTP handler — автоматический трейсинг
mux := http.NewServeMux()
mux.HandleFunc("/api/orders", handleOrders)
handler := otelhttp.NewHandler(mux, "server")

Python с автоинструментацией (zero-code):

# Установка
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install  # автоустановка инструментаций

# Запуск с автоинструментацией — НЕ МЕНЯЯ КОД приложения
opentelemetry-instrument \
  --service_name payment-service \
  --exporter_otlp_endpoint http://otel-collector:4318 \
  python app.py

# Flask, Django, FastAPI, SQLAlchemy, requests, psycopg2 —
# все инструментированы автоматически
Совет: Начните с автоинструментации (zero-code) — она покрывает HTTP, gRPC, SQL-запросы, Redis, Kafka. Кастомные спаны добавляйте только для бизнес-логики, которую автоинструментация не видит.

Корреляция: трейсы + логи + метрики

Главная ценность OTel — корреляция. Trace ID связывает лог, трейс и метрику в единую историю запроса.

# Python: добавляем trace_id в логи
import logging
from opentelemetry import trace

class TraceIdFilter(logging.Filter):
    def filter(self, record):
        span = trace.get_current_span()
        ctx = span.get_span_context()
        record.trace_id = format(ctx.trace_id, '032x') if ctx.trace_id else ''
        record.span_id = format(ctx.span_id, '016x') if ctx.span_id else ''
        return True

logger = logging.getLogger(__name__)
logger.addFilter(TraceIdFilter())
formatter = logging.Formatter(
    '%(asctime)s %(levelname)s [trace=%(trace_id)s span=%(span_id)s] %(message)s'
)

В Grafana: клик на ошибку в логах → переход к трейсу → видно, какой конкретно вызов микросервиса сломался, сколько времени занял каждый этап.

Семплирование для production (не все трейсы нужно хранить):

# В OTel Collector — tail-based sampling
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors-always
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-requests
        type: latency
        latency: {threshold_ms: 1000}
      - name: sample-rest
        type: probabilistic
        probabilistic: {sampling_percentage: 10}

Результаты внедрения в «МикроСерв»

Внедрение OTel на 40 микросервисов заняло 3 недели:

  1. Неделя 1: развёртывание Collector + Grafana Tempo, автоинструментация Python-сервисов
  2. Неделя 2: инструментация Go и Java сервисов, настройка корреляции логов
  3. Неделя 3: настройка семплирования, алертов, дашбордов
МетрикаДо OTelПосле OTel
MTTR (время устранения инцидента)2-4 часа15-30 минут
Сервисы с трейсингом8 из 4040 из 40
Корреляция лог→трейсРучной поиск1 клик в Grafana
Vendor lock-inJaeger SDK в кодеЛюбой бэкенд через Collector
Стоимость хранения трейсов100% трейсов10% + все ошибки (tail sampling)

Часто задаваемые вопросы

Нет, OpenTelemetry не хранит данные — только собирает и передаёт. Prometheus остаётся хранилищем метрик. OTel заменяет клиентские SDK (вместо prometheus_client используйте OTel SDK) и добавляет единый транспорт (OTLP). Collector экспортирует метрики в Prometheus.

Зависит от нагрузки. Для 10,000 спанов/сек: ~200 МБ RAM, ~0.5 CPU. Для 100,000 спанов/сек: ~1 ГБ RAM, ~2 CPU. Рекомендуется ставить memory_limiter processor для защиты от OOM.

Tempo хранит трейсы в объектном хранилище (S3, GCS, MinIO) — дешевле, чем Elasticsearch для Jaeger. Tempo не требует индексов — ищет по trace_id через Parquet. Интеграция с Grafana нативная (exemplars, loki→tempo linking). Для новых проектов Tempo предпочтительнее.

Да, SDK может отправлять данные напрямую в бэкенд (Jaeger, Prometheus). Но Collector рекомендуется: он буферизирует, обрабатывает (семплирование, обогащение), и позволяет менять бэкенд без изменения кода.

Overhead минимальный: 1-3% CPU, 10-50 МБ RAM на Java-агент, меньше на Go/Python. Основная нагрузка — отправка спанов, которая происходит асинхронно (batch exporter). При tail sampling на Collector объём данных сокращается в 10x.

Нужна помощь с внедрением?

Настроим, оптимизируем и возьмём на поддержку вашу инфраструктуру. 15+ лет опыта, 8 серверов Dell Xeon в дата-центре МТС.

📞 Связаться с нами

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

Оставить комментарий

9 + 8 =