Это небольшой гайд о том, как обеспечить наблюдаемость в вашей событийно-ориентированной облачной системе.

Немного теории

Cloud application

Облачные технологии позволяют организациям создавать и запускать масштабируемые приложения в современных динамических окружениях, таких как общедоступные, частные и гибридные облака…

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

– Cloud Native Computing Foundation (CNCF)

Согласно этому определению, облачные приложения – больше, чем просто приложения, которые действуют в облаке. Они также масштабируемы, слабо связаны, устойчивы, управляемы и доступны для наблюдения. Можно сказать, что наличие этих «облачных атрибутов» позволяет называть системы облачными.

Event-driven architecture (EDA)

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

Observability

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

OpenTelemetry

OpenTelemetry, также известный как OTel, представляет собой открытый стандарт CNCF, который обеспечивает распределенную трассировку и сбор метрик из ваших приложений.

Trace Context

Это метаданные о span-ах в трассировке. Например, предположим, что служба A вызывает службу B, и вы хотите отследить вызов. В этом случае OpenTelemetry будет использовать Trace Context для захвата идентификатора трассировки и текущего span-а из службы A, чтобы span-ы, созданные в службе B, могли подключаться и добавляться к трассировке.

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

Context Propagation

Распространение контекста - это основная концепция, которая обеспечивает распределенную трассировку. При распространении контекста span-ы могут быть соотнесены друг с другом и собраны в трассировку, независимо от того, где они генерируются. OpenTelemetry определяет распространение контекста с помощью двух субконцептов: Context и Propagation.

Context - это объект, который содержит информацию для отправляющей и принимающей служб, позволяющую соотнести один span с другим и связать его с трассировкой в целом.

Propagation - это механизм, который перемещает контекст между службами и процессами. Поступая таким образом, он собирает распределенную трассировку. Он сериализует или десериализует Span Context и предоставляет соответствующую информацию трассировки для распространения от одной службы к другой.

На практике

Хорошо, давайте создадим экземпляр propagation и инициализируем его:

import (
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
)

tc := propagation.TraceContext{}
// Register the TraceContext propagator globally.
otel.SetTextMapPropagator(tc)

В сервисе A, контекст которого мы хотим передать:

// GetTextMapPropagator returns the global TextMapPropagator.
prop := otel.GetTextMapPropagator()
// HeaderCarrier adapts http.Header to satisfy the TextMapCarrier interface.
headers := make(propagation.HeaderCarrier)
prop.Inject(ctx, headers)

после этого мы должны каким-то образом передать эти заголовки, в теле запроса, в заголовках запроса, все зависит от вашей реализации.

В сервисе B, в котором мы хотим получить контекст:

var headers propagation.HeaderCarrier
// we get the headers and convert them to HeaderCarrier...
prop := otel.GetTextMapPropagator()
// Extract reads cross-cutting concerns from the carrier into a Context.
ctx = prop.Extract(ctx, headers)

NATS

// Simple Async Subscriber
nc.Subscribe("foo", func(m *nats.Msg) {
    fmt.Printf("Received a message: %s\n", string(m.Data))
})

// Header represents the optional Header for a NATS message,
// based on the implementation of http.Header.
type Header map[string][]string

// Msg represents a message delivered by NATS. This structure is used
// by Subscribers and PublishMsg().
type Msg struct {
	Header  Header
}

нетрудно заметить что propagation.HeaderCarrier и nats.Header основан на реализации http.Header. Поэтому, чтобы скопировать данные из одной структуры в другую, я воспользовался реализацией http.Header.Clone()

В заключении

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

Полный код проекта доступен в моем репозитории - nats-tracing.

Так же при написании гайда я использовал открытые источники:

Комментарии (9)


  1. scruff
    00.00.0000 00:00
    +1

    Вы бы хоть показали, куда этот propagation вбивать - в терминал ssh, IDE-шку или еще куда.


    1. comdivuz
      00.00.0000 00:00

      Ну товарищ про микро сервисы пишет надо полагать в заголовках http или метаданных grpc


      1. Gariks Автор
        00.00.0000 00:00

        В OpenTelemetry есть такой термин как Instrumentation, это по факту набор готовых middleware для различных популярных библиотек. Для http, gPRC тоже есть - вот ссылка на примеры его подключения https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/google.golang.org/grpc/otelgrpc/example

        https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/net/http

        После подключения этого инструмента к http, gRPC, вы сразу увидите все трейсы ваших запросов, но они будут не связаны. Для того что бы их связать нужно просто инициализировать propagation, как показано в статье. Сам instrumentation уже внутри своей реализации производит Inject и Extract.


    1. Gariks Автор
      00.00.0000 00:00

      В код вашего проекта на Go. Теги "Go, программирование" и в конце статьи ссылка, на пример реализации. Полный проект с инструкцией, как его развернуть локально, со всей нужной инфраструктурой: jaeger, NATS.


  1. ivankudryavtsev
    00.00.0000 00:00

    NATS! И этим все сказано!

    Всё достаточно просто, если немного почитать документацию.

    Вы до заголовка NATS никак не касались событийно-ориентированного рантайма, а просто пересказали термины распределенной трассировки. Незачет.


    1. Gariks Автор
      00.00.0000 00:00

      А что значит "событийно ориентированный рантайм"? Как вы реализуете свою систему полностью зависит от вашего проекта. Обычно в самом простом случае MQ выступает в роли шины данных между сервисами. У вас может быть rabbitMQ, Kafka это не имеет никакого значения, потому что принципы context propogation в open telemetry остаются такими же. В гайде на примере кода я показал, как ими пользоваться. Если вас интересует настройка инфраструктуры, загляните в пример проекта на GitHub-е, который я прикрепил к статье.


      1. ivankudryavtsev
        00.00.0000 00:00

        Друг мой, статья плохая, а не Вы.


  1. ReDev1L
    00.00.0000 00:00

    Статья ниачом.

    Примеры, для чего, результаты где смотреть


    1. Gariks Автор
      00.00.0000 00:00

      А где вы обычно смотрите трейсы в вашей системе?

      OpenTelemery поддерживает множество экспортеров. https://github.com/open-telemetry/opentelemetry-go/tree/main/exporters - Jaeger, OTPL, stdoutrace, zipkin.

      В примере, который прикреплен в конце для трассировки используется Jaeger.