Управляйте своими сервисами с помощью OTEL, Jaeger и Prometheus

Давайте обсудим важный вопрос: как мы можем мониторить наши сервисы, если что-то пойдет не так?

С одной стороны, у нас есть Prometheus с оповещениями и Kibana для панелей и других полезных функций. Мы также знаем, как собирать логи — стек ELK – наше стандартное решение. Однако простое логирование не всегда достаточно: оно не предоставляет всестороннего взгляда на путь запроса по всей экосистеме компонентов.

Больше информации о ELK вы можете найти здесь.

Но что, если мы хотим визуализировать запросы? Что, если нам нужно коррелировать запросы, передвигающиеся между системами? Это относится как к микросервисам, так и к монолитам — не важно, сколько у нас сервисов; важно, как мы управляем их задержкой.

Действительно, каждый пользовательский запрос может проходить через целую цепочку независимых сервисов, баз данных, очередей сообщений и внешних API.

В такой сложной среде становится чрезвычайно сложно точно определить, где возникают задержки, выявить, какая часть цепи действует как узкое место производительности, и быстро найти причину сбоев, когда они происходят.

Для эффективного решения этих задач нам необходима централизованная, последовательная система для сбора телеметрических данных — трассировок, метрик и логов. Именно здесь нам на помощь приходят OpenTelemetry и Jaeger.

Давайте рассмотрим основные понятия

Существуют два основных термина, которые нам необходимо понять:

Идентификатор трассировки

Идентификатор трассировки – это 16-байтовый идентификатор, часто представленный в виде 32-символьной шестнадцатеричной строки. Он автоматически генерируется в начале трассировки и остается неизменным для всех спанов, созданных в рамках конкретного запроса. Это упрощает отслеживание пути запроса через различные службы или компоненты в системе.

Идентификатор спана

Каждая отдельная операция в рамках трассировки получает свой собственный идентификатор спана, который обычно является случайно сгенерированным 64-битным значением. Спаны имеют общий идентификатор трассировки, но каждый из них имеет уникальный идентификатор спана, поэтому вы можете точно определить, какая часть рабочего процесса представлена каждым спаном (например, запрос к базе данных или вызов другой микрослужбы).

Как они связаны между собой?

Идентификатор трассировки и Идентификатор спана дополняют друг друга. 

Когда запрос инициируется, генерируется и передается идентификатор трассировки всем участвующим службам. Каждая служба ihreredze собственный спан с уникальным идентификатором спана, связанным с идентификатором трассировки, что позволяет визуализировать полный жизненный цикл запроса от начала до конца.

Хорошо, а почему бы просто не использовать Jaeger? Зачем нам нужен OpenTelemetry (OTEL) и все его спецификации? Отличный вопрос! Давайте разберем его пошагово.

Узнайте больше о Jaeger здесь.

Кратко

  • Jaeger – это система для хранения и визуализации распределенных трассировок. Она собирает, хранит, ищет и отображает данные, показывающие, как запросы “путешествуют” через ваши службы.
  • OpenTelemetry (OTEL) – это стандарт (и набор библиотек) для сбора телеметрических данных (трассировок, метрик, журналов) из ваших приложений и инфраструктуры. Он не привязан к какому-либо конкретному инструменту визуализации или бэкэнду.

Просто говоря:

  • OTEL – это как “универсальный язык” и набор библиотек для сбора телеметрических данных.
  • Jaeger – это бэкэнд и пользовательский интерфейс для просмотра и анализа распределенных трассировок.

Зачем нам нужен OTEL, если у нас уже есть Jaeger?

1. Единый стандарт для сбора

В прошлом существовали проекты типа OpenTracing и OpenCensus. OpenTelemetry объединяет эти подходы к сбору метрик и трассировок в единый универсальный стандарт.

2. Легкая интеграция

Вы пишете свой код на Go (или другом языке), добавляете библиотеки OTEL для автоматической инъекции перехватчиков и спанов, и все. После этого не имеет значения, куда вы хотите отправить эти данные – Jaeger, Tempo, Zipkin, Datadog, пользовательский бэкэнд – OpenTelemetry позаботится о всей “сантехнике”. Вам нужно просто заменить экспортер.

3. Не только трассировки

OpenTelemetry охватывает трассировки, но также обрабатывает метрики и логи. Вы получаете единый набор инструментов для всех ваших потребностей в телеметрии, а не только в трассировке.

4. Jaeger как бэкэнд

Jaeger – отличный выбор, если вас в первую очередь интересует визуализация распределенного трассирования. Но он не предоставляет кросс-языковую инструментацию по умолчанию. OpenTelemetry, с другой стороны, предоставляет вам стандартизированный способ сбора данных, после чего вы решаете, куда их отправить (включая Jaeger).

На практике они часто работают вместе:

Ваше приложение использует OpenTelemetry → общается через протокол OTLP → отправляется в OpenTelemetry Collector (HTTP или grpc) → экспортируется в Jaeger для визуализации.


Техническая часть

Дизайн системы (немного)

Давайте быстро набросаем несколько служб, которые будут делать следующее:

  1. Служба покупки – обрабатывает платеж и записывает его в MongoDB
  2. CDC с Debezium – отслеживает изменения в таблице MongoDB и отправляет их в Kafka
  3. Процессор покупок – получает сообщение из Kafka и вызывает Службу аутентификации для поиска user_id для проверки
  4. Служба аутентификации – простая пользовательская служба

В итоге:

  • 3 службы на Go
  • Kafka
  • CDC (Debezium)
  • MongoDB

Часть кода

Давайте начнем с инфраструктуры. Чтобы объединить все в одну систему, мы создадим большой файл Docker Compose. Мы начнем с настройки телеметрии.

Примечание: Весь код доступен по ссылке в конце статьи, включая инфраструктуру.

YAML

 

Также мы настроим коллектор — компонент, собирающий телеметрию.

Здесь мы выбираем gRPC для передачи данных, что означает, что общение будет происходить по HTTP/2:

YAML

 

Убедитесь, что адреса настроены правильно, и базовая конфигурация завершена.

Мы уже знаем, что OpenTelemetry (OTEL) использует два ключевых концепта — Идентификатор трассировки и Идентификатор спана— которые помогают отслеживать и мониторить запросы в распределенных системах.

Реализация кода

Теперь давайте посмотрим, как сделать это в вашем коде на Go. Нам понадобятся следующие импорты:

Go

 

Затем мы добавляем функцию для инициализации нашего трейсера в main() при запуске приложения:

Go

 

Настроив трассировку, нам просто нужно разместить спаны в коде для отслеживания вызовов. Например, если мы хотим измерить вызовы к базе данных (поскольку обычно это первое место, на которое мы смотрим при проблемах с производительностью), мы можем написать что-то вроде этого:

Go

 

У нас есть трассировка на уровне сервиса — замечательно! Но мы можем пойти еще глубже, инструментируя уровень базы данных:

Go

 

Теперь у нас есть полное представление о пути запроса. Перейдите в интерфейс Jaeger, запросите последние 20 трассировок для auth-service, и вы увидите все спаны и их связи в одном месте.

Теперь всё видно. Если вам это нужно, вы можете включить весь запрос в теги. Тем не менее, помните, что не следует перегружать телеметрию — добавляйте данные осознанно. Я просто демонстрирую возможности, но включение полного запроса таким образом обычно не рекомендуется.

gRPC клиент-сервер

Если вы хотите увидеть трассировку, охватывающую два сервиса gRPC, это довольно просто. Вам просто нужно добавить готовые перехватчики из библиотеки. Например, на стороне сервера:

Go

 

На стороне клиента код так же короток:

Go

 

Вот и всё! Убедитесь, что ваши экспортеры настроены правильно, и вы увидите один идентификатор трассировки, зарегистрированный в этих сервисах при вызове клиента сервера.

Обработка событий CDC и трассировка

Хотите также обрабатывать события из CDC? Одним из простых подходов является встраивание идентификатора трассировки в объект, который хранит MongoDB. Таким образом, когда Debezium захватывает изменение и отправляет его в Kafka, идентификатор трассировки уже является частью записи.

Например, если вы используете MongoDB, вы можете сделать что-то подобное:

Go

 

Затем Debezium забирает этот объект (включая trace_id) и отправляет его в Kafka. Со стороны потребителя вам просто нужно разобрать входящее сообщение, извлечь trace_id и объединить его в ваш контекст трассировки:

Go

 

Go

 

Альтернатива: Использование заголовков Kafka

Иногда проще хранить идентификатор трассировки в заголовках Kafka, а не в самом полезном нагрузке. Для рабочих процессов CDC это может быть недоступно из коробки — Debezium может ограничивать то, что добавляется в заголовки. Но если вы контролируете сторону производителя (или если вы используете стандартный производитель Kafka), вы можете сделать что-то подобное с Sarama:

Внедрение идентификатора трассировки в заголовки

Go

 

Извлечение идентификатора трассировки на стороне потребителя

Go

 

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

  1. Встраивайте идентификатор трассировки в запись базы данных, чтобы он естественно проходил через CDC.
  2. Используйте заголовки Kafka, если у вас больше контроля над стороной производителя или если вы хотите избежать раздувания полезной нагрузки сообщения.

В любом случае, вы можете поддерживать консистентность ваших трассировок между несколькими службами — даже когда события обрабатываются асинхронно через Kafka и Debezium.

Заключение

Использование OpenTelemetry и Jaeger предоставляет подробные трассировки запросов, помогая вам точно определить, где и почему возникают задержки в распределенных системах.

Добавление Prometheus завершает картину метриками — ключевыми показателями производительности и стабильности. Вместе эти инструменты образуют комплексный стек наблюдаемости, позволяющий быстрее обнаруживать и решать проблемы, оптимизировать производительность и повышать общую надежность системы.

Могу сказать, что этот подход значительно ускоряет устранение неполадок в среде микросервисов и является одним из первых шагов, которые мы реализуем в наших проектах.

Ссылки

Source:
https://dzone.com/articles/control-services-otel-jaeger-prometheus