Полезные инструкции, которые помогут вам в вашем путешествии по Observability
OpenTelemetry зарекомендовала себя как стандарт наблюдаемости после выхода из стандартов OpenTracing и OpenCensus. Он пытается стандартизировать различные виды сигналов (журналы, трассировки, метрики). Сигнал наблюдаемости, относящийся к этой статье, — это следы.
Чтобы лучше понять, стоит прочитать основные концепции OpenTelemetry.
Мы используем Grafana Tempo в качестве бэкенда для наших трейсов. Это позволяет нам хранить информацию о трассировке в недорогом объектном хранилище вместо размещения базы данных ElasticSearch при использовании такого инструмента, как Jaeger.
Поскольку мы предоставляем общие кластеры Kubernetes командам разработчиков приложений, еще одной очень важной функцией Grafana Tempo является мультиарендность.
Основной поток
У каждой команды разработчиков есть набор пространств имен Kubernetes с меткой tenant_id
. Приложения поддерживаются библиотекой OpenTelemetry (см. список поддерживаемых языков по этой ссылке).
Трассировки создаются в приложениях, а затем отправляются сборщику OpenTelemetry, установленному в кластере с потрясающим OpenTelemetry Kubernetes Operator. Коллекторный трубопровод состоит из трех основных частей:
- Приемники — это компоненты, которые принимают трассировки, отправленные от рабочих нагрузок. Мы используем встроенный otlpприемник, который принимает трейсы, отправленные через gRPC. Коллектор может принимать трассы из разных источников, используя разные конфигурации приемников.
- Процессоры могут преобразовывать, фильтровать или маршрутизировать трассы. Мы используем процессор для обогащения трассировок информацией об именах Kubernetes Pod и Namespace отправляющих рабочих нагрузок и извлекаем идентификатор арендатора из метки Namespace.
- Экспортеры отправляют трейсы на бэкенды вроде Grafana Tempo. Здесь мы используем otlp exporter через gRPC.
Конвейер сборщика для рабочих нагрузок извлекает идентификатор арендатора из метки пространства имен с помощью процессора k8sattributes. (Процессору требуются некоторые разрешения для запросов к серверу Kubernetes API. См. файл README процессора.)
receivers: otlp: protocols: grpc: processors: k8sattributes/default: extract: labels: - tag_name: tenantId key: "tenant-id" from: namespace routing: from_attribute: tenantId attribute_source: resource table: - value: tenant1 exporters: [logging, otlp/tenant1] - value: tenant2 exporters: [logging, otlp/tenant2] exporters: otlp/tenant1: endpoint: tempo.grafana.svc.cluster.local:4317 tls: insecure: true headers: X-Scope-OrgID: tenant1 otlp/tenant2: endpoint: tempo.grafana.svc.cluster.local:4317 headers: X-Scope-OrgID: tenant2 tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [k8sattributes/default, routing] exporters: [ otlp/tenant1, otlp/tenant2]
Далее мы определяем экспортера для каждого арендатора, который у нас есть. Все они настраивают один и тот же URL-адрес конечной точки для темпа Grafana, но имеют другое значение заголовка X-Scope-OrgID
. Процессор маршрутизации направляет трассировки различным экспортерам в зависимости от идентификатора арендатора.
Это немного громоздко, но нет возможности использовать переменные внутри блока заголовков для экспортера otlp. Это позволило бы нам иметь только одну запись экспортера с переменным содержимым в заголовке X-Scope-OrgID
.
Ingress-Nginx и OpenTelemetry
Ingress-Nginx поддерживает OpenTelemetry, начиная с версии 1.7.0 (выпущенной 24 марта 2023 г. См. запрос на включение #9062). Мы с нетерпением ждали эту новую функцию, потому что она позволяет собирать трассировочную информацию не только на уровне рабочей нагрузки, но уже на один шаг раньше, когда трафик поступает в кластер Kubernetes.
Чтобы включить интеграцию OpenTelemetry с помощью диаграммы Ingress-Nginx helm, просто добавьте следующие параметры:
controller: config: enable-opentelemetry: "true" opentelemetry-config: "/etc/nginx/opentelemetry.toml" opentelemetry-operation-name: "HTTP $request_method $service_name $uri" opentelemetry-trust-incoming-span: "true" otlp-collector-host: "otel-collector.grafana.svc.cluster.local" otlp-collector-port: "4317" otel-max-queuesize: "2048" otel-schedule-delay-millis: "5000" otel-max-export-batch-size: "512" otel-service-name: "nginx-proxy" # Opentelemetry resource name otel-sampler: "AlwaysOn" # Also: AlwaysOff, TraceIdRatioBased otel-sampler-ratio: "1.0" otel-sampler-parent-based: "false"
Хотя рабочие нагрузки всегда выполняются внутри пространства имен Kubernetes, к которому в качестве метки прикреплен идентификатор клиента, контроллер Ingress-Nginx является общим развертыванием, управляемым командой платформы, и информацию о арендаторе нельзя извлечь из пространства имен.
Нам пришлось извлечь информацию об арендаторе из другого места, чтобы направить трассировки, исходящие от входного контроллера, к правильному арендатору Grafana Tempo.
Директивы Ingress-Nginx OpenTelemetry
Хотя документации контроллера Ingress-Nginx в настоящее время недостаточно, мы нашли необходимую информацию в модуле, который используется для части OpenTelemetry. Он расположен в следующем проекте GitHub.
Нам удалось найти директивы, предоставляемые модулем OpenTelemetry. Особенно нам была интересна директива opentelemetry_attribute
. Это позволяет нам добавлять пользовательские атрибуты к промежуткам.
Мы настроили входной контроллер, чтобы добавить эти директивы в каждый блок сервера в конфигурации nginx. Пример конфигурации ниже относится к рулевой диаграмме:
controller: config: server-snippet: | opentelemetry_attribute "ingress.namespace" "$namespace"; opentelemetry_attribute "ingress.service_name" "$service_name"; opentelemetry_attribute "ingress.name" "$ingress_name"; opentelemetry_attribute "ingress.upstream" "$proxy_upstream_name";
После внесения этого изменения конфигурации трассировки, отправляемые контроллером входящего трафика, содержат информацию о пространстве имен Ingresses, имени и том, куда он направляет трафик.
Но где взять идентификатор арендатора? К счастью для нас, все наши пространства имен следуют схеме именования, такой как tenantId-<namespaceName>
. Мы можем извлечь идентификатор арендатора из атрибута пространства имен, прикрепленного к диапазонам.
Вышеупомянутый трубопровод коллектора изменен, чтобы сделать это возможным. Вот как выглядит код:
receivers: otlp: protocols: grpc: processors: k8sattributes/default: extract: labels: - tag_name: tenantId key: "tenant-id" from: namespace # in case of ingress-nginx traces the ingress namespace is in the attributes. # we need to extract the account id from there and eventually transfer it to the resources # see Resource: https://opentelemetry.io/docs/concepts/glossary/#resource # vs Attribute: https://opentelemetry.io/docs/concepts/glossary/#attribute attributes/ingress: actions: - key: "ingress.namespace" pattern: "(?P<temp_tenant_id>.{6})"<<< action: extract # this overwrites existing keys groupbyattrs: # copy the attribute containing the tenant id to the resource attribute which is used for routing keys: - temp_tenant_id resource/ingress: attributes: - key: "tenantId" from_attribute: temp_tenant_id action: insert - key: "temp_tenant_id" action: delete routing: from_attribute: tenantId attribute_source: resource table: - value: tenant1 exporters: [logging, otlp/tenant1] - value: tenant2 exporters: [logging, otlp/tenant2] exporters: otlp/tenant1: endpoint: tempo.grafana.svc.cluster.local:4317 tls: insecure: true headers: X-Scope-OrgID: tenant1 otlp/tenant2: endpoint: tempo.grafana.svc.cluster.local:4317 headers: X-Scope-OrgID: tenant2 tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [k8sattributes/default, attributes/ingress, groupbyattrs, resource/ingress, routing] exporters: [ otlp/tenant1, otlp/tenant2]
При использовании этого конвейера используется информация о пространстве имен Ingress-Nginx. Идентификатор арендатора извлекается с помощью регулярного выражения и сохраняется во временном файле attribute
. Затем attribute
копируется в resources
с помощью процессора groupbyattrs
.
Это необходимо, потому что OpenTelemetry имеет концепцию attribute
s и resources
. Процессор k8sattributes
хранит информацию в части ресурсов. Мы уже используем эти атрибуты для маршрутизации (поэтому мы настроили attribute_source: resource
).
Это немного некрасиво, но нам нужна помощь, чтобы найти лучший способ сделать это. Если у вас есть идеи, не стесняйтесь оставлять предложения в комментариях!
Следующие шаги
Приведенный выше коллекторный трубопровод можно дополнительно оптимизировать, используя следующее:
- пакетный процессор для пакетной отправки трасс
- процессор хвостовой выборки для фильтрации трассировок для конечных точек, таких как
/actuator
или/health
, которые очень часто вызываются сервером Kubernetes API, но не приносят реальной ценности для записи в Grafana Tempo
Использование Grafana Tempo и сборщика OpenTelemetry для нас сейчас в новинку. Если какие-то важные пункты выше отсутствуют, и вы видите дальнейшие оптимизации, оставьте подсказку в комментариях.
Надеюсь, инструкции помогут и вам в вашем путешествии по Observability!