2. OpenTelemetry

Архитектура унифицированного стандарта телеметрии

«Стандарты замечательны тем, что их существует великое множество на любой вкус».
— Эндрю Таненбаум

В предыдущей статье мы увидели, как распределенная трассировка собирает воедино разорванные сетевыми вызовами цепочки событий. Однако на заре развития микросервисов индустрия погрузилась в хаос несовместимых стандартов: одна библиотека поддерживала OpenTracing, другая требовала OpenCensus от Google, а проприетарные APM-системы (Datadog, New Relic, Dynatrace) навязывали собственные закрытые агенты и форматы.

В 2019 году под эгидой Cloud Native Computing Foundation (CNCF) произошло историческое объединение OpenTracing и OpenCensus в единый проект — OpenTelemetry (OTel).

Сегодня OpenTelemetry — это абсолютный промышленный стандарт второго уровня после Kubernetes по активности сообщества. Для разработчика на Go это универсальный интерфейс и библиотека, позволяющая инструментировать код один раз и отправлять три ключевых столпа телеметрии (метрики, логи и трейсы) в любую выбранную систему хранения без вендор-лока (vendor lock-in).


Единый стандарт телеметрии

Главная цель OpenTelemetry — навсегда разделить процесс инструментирования кода и процесс транспортировки данных.

Код вашего Go-сервиса не должен знать, куда улетают метрики — в локальный Prometheus, в облачный Google Cloud Monitoring или в VictoriaMetrics. Он не должен знать, кто визуализирует трейсы — открытый Jaeger, Grafana Tempo или коммерческий Dynatrace. Приложение просто генерирует события в рамках стандартизированного API.


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

Архитектура OTel построена по принципу строгой модульности и разделения ответственности:

flowchart TD
    subgraph A ["Ваше приложение"]
        A1["API<br>Трассировка/Метрики/Логи"]
    end

    subgraph B ["SDK OpenTelemetry"]
        direction TB
        B1["TracerProvider"]
        B2["MeterProvider"]
        B3["LoggerProvider"]

        B4["Processors<br>Batch/Simple"]
        B5["Exporters<br>OTLP/Stdout"]
    end

    subgraph C ["Infrastructure"]
        C1["OTel Collector"]
    end

    subgraph D ["Backend"]
        D1["Jaeger / Tempo"]
        D2["Prometheus"]
        D3["Loki"]
    end

    A -->|"Вызовы API"| B
    B -->|"Данные"| B4
    B4 -->|"Трансформация"| B5
    B5 -->|"OTLP Protocol"| C1
    C1 -->|"Маршрутизация"| D1
    C1 -->|"Маршрутизация"| D2

    style A fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#fff
    style B fill:#312e81,stroke:#818cf8,stroke-width:2px,color:#fff
    style C fill:#78350f,stroke:#f59e0b,stroke-width:2px,color:#fff
    style D fill:#0f172a,stroke:#475569,color:#fff
  

1. API и SDK

Архитектура четко разделена на два слоя:

  • API (go.opentelemetry.io/otel): Это тонкие интерфейсы, которые вы используете в бизнес-коде (Tracer, Meter). Они содержат нулевую реализацию (no-op) по умолчанию. Если приложение не зарегистрировало провайдер, вызовы API мгновенно завершаются без каких-либо накладных расходов.
  • SDK: Это конкретная реализация. Она управляет сэмплированием (sampling), буферизацией и отправкой данных.

Библиотека баз данных может объявить tracer.Start(ctx, "db.query"), не заставляя приложение тащить за собой десятки мегабайт зависимостей Jaeger или gRPC-клиентов.

2. OTLP (OpenTelemetry Protocol)

OTel представил OTLP — бинарный протокол на основе Protocol Buffers (gRPC или HTTP/2).

  • Производительность: Binary формат кодируется и декодируется быстрее JSON.
  • Универсальность: Один формат для Метрик, Логов и Трейсов.

В отличие от устаревших текстовых форматов или JSON, бинарный Protobuf в OTLP минимизирует объем передаваемого по сети трафика и не требует тяжелых парсеров с обилием строковых аллокаций.


Under the Hood: Как это работает в Go

Разберем путь создания спана в Go-приложении.

1. TracerProvider

Это корневой объект. Он хранит конфигурацию и создает Tracer'ы.
В Go его принято создавать при старте приложения и закрывать (Shutdown) при graceful shutdown. Shutdown критически важен: он сбрасывает (flush) буферы процессоров, чтобы не потерять последние данные.

Go
tp := trace.NewTracerProvider(
    trace.WithBatcher(exporter),
    trace.WithResource(res),
)
defer func() {
    if err := tp.Shutdown(context.Background()); err != nil {
        log.Printf("Error shutting down tracer provider: %v", err)
    }
}()
otel.SetTracerProvider(tp)

2. SpanProcessor

Это механизм обработки спанов.

  • BatchSpanProcessor (Рекомендуемый): Собирает спаны в пачки (batch) и отправляет их раз в N секунд или при достижении лимита размера.
    • Почему? Сетевые вызовы дороги. Отправлять каждый спан по сети — убить производительность. Батчинг экономит CPU и сеть. Внутри используется кольцевой буфер и фоновая горутина-сборщик.
  • SimpleSpanProcessor: Отправляет спан сразу после завершения. Используется только для отладки или в короткоживущих CLI-утилитах.

3. Exporter

Отвечает за отправку данных в конкретный бэкенд.
Чаще всего используют OTLP Exporter, который шлет данные в OpenTelemetry Collector.


OpenTelemetry Collector

В продакшене приложение не должно знать адреса Jaeger, Prometheus или Loki. Оно должно знать только адрес otel-collector:4317.

Collector — это промежуточный прокси-сервис (написан на Go), который принимает данные в формате OTLP и маршрутизирует их.

Возможности Collector:

  1. Рецепшн (Receivers): Принимает данные (OTLP, Jaeger, Zipkin, Prometheus).
  2. Процессоры (Processors): Обрабатывает данные на лету.
    • memory_limiter: Защищает коллектор от OOM.
    • batch: Повторный батчинг.
    • attributes: Добавление/удаление атрибутов (например, удалить sensitive данные).
  3. Экспортеры (Exporters): Отправляет в бэкенды (Jaeger, ClickHouse, S3).

Semantic Conventions (Семантические соглашения)

OpenTelemetry ввел стандарты именования атрибутов. Это делает трейсы универсальными для всех инструментов.

  • http.method: "GET"
  • db.system: "postgresql"
  • db.statement: "SELECT * FROM users"
  • net.peer.name: "database-server"

Если вы назовете атрибут my_custom_http_method, бэкенд (например, Grafana Tempo) не поймет, что это HTTP-метод, и не сможет построить правильные дашборды.


Итог

OpenTelemetry — это "USB" для телеметрии.

  1. Единый API: Вы пишете код под один интерфейс, а бэкенд меняется через конфиг.
  2. OTLP: Эффективный бинарный протокол.
  3. Collector: Централизованная точка управления потоком данных и их обработки.
  4. Semantic Conventions: Словарь общего языка для мониторинга.

В следующей статье мы детально рассмотрим строительные блоки трейса — Спаны и Трейсы, и как правильно с ними работать в Go: 3. Спаны и трейсы.