Observability: eBPF, APM и OpenTelemetry

Зачем всё это?

Для реализации “настоящей” наблюдаемости в кластере Kuberntes можно воспользоваться двумя технологиями: eBPF и\или OpenTelemetry.

Без них также можно реализовать observability, но выбор инструментария будет ограничен.

Ниже рассказано о двух инструментах, дополняющими друг друга, с помощью которых можно выстроить observability для кластера Kubernetes.

eBPF

Ядро Linux имеет встроенную виртуальную машину для байткода, что позволяет пользовательскому пространству запускать небольшие скрипты eBPF (с помощью JIT) в пространстве ядра с ограниченным воздействием.

При необходимости разработки какого-либо функционала, добавлять свой модуль ядра – долго, дорого да и неудобно. А написать программу и запустить в в kernel-space – это куда быстрее и проще.

Ядро проверяет eBPF-программу с помощью статического анализа, чтобы исключить возможность повреждения памяти и утечки конфиденциальных данных при доступе за пределы границ. В eBPF не допускаются циклы и ограничено максимальное число инструкций, так что программа с логическими ошибками не может “подвесить” ядро. Программы могут запускаться как хуки на события ядра: отправка\получение пакета, различные системные вызовы и т.п.

Простой пример – CNI-плагин Cilium, который основан на eBPF + cilium hubble (UI), что в совокупности даёт полную картину происходящего в кластере. CNI Calico тоже поддерживает eBPF в новых версиях, но основной функционал в платных версиях, в отличие от Cilium.

Как это работает?

Работает это почти также, как OpenTelemetry. Только если нет возможности обогатить приложения метриками в формате OTel, запущенный агент на каждом узле кластера будет наблюдать за приложениями и строить графики со всеми запросами.

Coroot “понимает” из коробки следующие протоколы\приложения: HTTP, Postgres, MySQL, Redis, MongoDB и Memcached.

Полученные трассировки Coroot отправляет в OTel collector, а тот уже в кликхаус. Веб-приложение Coroot ходит в клик и предоставляет оттуда все полученные трассировки, метрики, логи – всё то, что смог “собрать” агент с помощью eBPF.

Как это применять в Kubernetes?

В контексте Kubernetes eBPF можно применять для наблюдения за сетевым трафиком между pods (отслеживать запросы приложений), анализа производительности и поиска утечек RAM\CPU. При это необязательно завязываться на CNI.

Помимо CNI есть различные системы на базе eBPF, которые позволяют строить карту взаимодействия service (которые в контексте k8s). Одним из таких решений является Coroot:

Преимуществом данного инструмента является возможность визуально понять схему взаимодействия в распределенной микросервисной архитектуре. Дополнительно в веб-интерфейсе также доступны метрики с узлов кластера (утилизация RAM и CPU) + по конкретному контейнеру:

А также могут быть доступны метрики JVM:

OpenTelemetry

OpenTelemetry (OTel) – стандарт сбора данных по работе приложений, сформированный из ранее существующих реализаций – OpenCensus и OpenTracing.

Основная задача – предоставить набор стандартизированных SDK, API и инструментов. Инструменты отправляют данные в сервисы для мониторинга систем. Таким образом получается стандарт распределенной трассировки и мониторинга – OTel.

Под распределенной трассировкой понимается механизм, позволяющий отслеживать прохождение одного запроса через несколько сервисов в распределенной среде.

Как это работает?

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

SDK – это как раз и есть набор библиотек для языков программирования, в которых реализованы трассы, метрики и логи.

OTel оперирует понятием трасса (например, запрос пользователя, в котором есть множество обращений к разным микросервисам) и спан (каждый из этих микросервисов).

Более подробно процесс описан далее.

  1. Трассировка (tracing): OpenTelemetry позволяет собирать данные о времени выполнения и взаимодействии различных компонентов системы. Это может включать информацию о вызовах API, баз данных, внешних сервисах и других компонентах. Примером является дерево трассировки, которое показывает путь запроса через различные компоненты системы и время, затраченное на каждую операцию.

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

  1. Метрики (metrics): OpenTelemetry позволяет собирать данные о производительности и использовании ресурсов системы. Это может включать показатели, такие как загрузка ЦП, использование памяти, количество запросов и другие метрики для бизнеса, которые помогают оценить состояние и эффективность системы.

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

  1. Журналы (logs): OpenTelemetry может собирать данные из журналов приложений и системы, что позволяет отслеживать ошибки, предупреждения и другую информацию, которая может быть полезной для отладки и мониторинга системы.
  2. Далее все эти трассы, метрики и логи поступают в коллектор. Коллектор – сервис, занимающийся сбором, обработкой и отправкой замеров/трейсов вендору. Может работать как на той же машине, что и приложение (Агент), так и на другой (Коллектор).
  3. Вендор – сервис, хранящий все собранные данные. Например, Jaeger (поддерживает OTLP) или Zipkin (для него есть правила трансформации, которые выполняет коллектор). С помощью различных сервисов вендора можно визуализировать полученные данные.

Схематический пример вышеописанного:

И пример времени ответа каждого микросервиса в приложении интернет-магазина (трассировки):

Теперь приложение обогащается сообщениями об ошибках при их наличии. И в конечном счёте предоставляет ту самую наблюдаемость.

Ещё немного теории по OpenTelemetry

Для работы трассировок в приложении есть два пути:

  1. Использовать Manual Instrumentation, т.е. добавить в код приложения нужные зависимости SDK для отдачи метрик по протоколу OTEL.
  2. Использовать Auto Instrumentation, т.е. автоматическое инструментирование

Второй вариант удобен тем, что не требуется вносить изменений в код. На примере Java это работает так: скачивается заранее необходимая библиотека для конкретного языка программирования (jar), импортируется в контейнер к основному приложению и динамически внедряет байткод для сбора телеметрии из многих популярных библиотек и фреймворков (spring, jboss & etc). Таким образом можно перехватывать данные на “границах” приложения, например входящие запросы, исходящие HTTP-вызовы, обращения к базам данных и т.д.

Для каждого ЯП (но не для абсолютно всех) доступен свой набор библиотек для автоматического инструментирования.

Как это применять в Kubernetes?

В контексте Kubernetes OpenTelemetry можно применять для получения распределенной трассировки приложения, с помощью которой можно увидеть затраченное время на выполнение каждого микросервиса (спана) от общей продолжительности запроса пользователя, зашедшего на сервис и получившего ответ в Web UI (трасса).

Каждый из http-запросов содержит в себе некий ID, в рамках которого и происходят дочерние запросы к другим микросервисам или сервисам. Так строится общая картина, т.е. трасса.

Для получения трассировок есть различные варианты. Но для всех случаев сначала необходимо установить коллектор otel, в k8s может быть задеплоен с помощью оператора.

Итак, когда в кластер установлен OpenTelemetry Operator, можно воспользоваться методом Auto Instrumentation. Для этого необходимо создать ресурс CRD под названием Instrumentation в нужном NS с указанием пути к коллектору otel

kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: my-instrumentation
spec:
  exporter:
    endpoint: <http://coroot-opentelemetry-collector.coroot.svc.cluster.local:4317>
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "0.25"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
  dotnet:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-dotnet:latest
EOF

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

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-petclinic
spec:
  selector:
    matchLabels:
      app: spring-petclinic
  replicas: 1
  template:
    metadata:
      labels:
        app: spring-petclinic
      annotations:
        instrumentation.opentelemetry.io/inject-java: "true"
    spec:
      containers:
      - name: app
        image: ghcr.io/pavolloffay/spring-petclinic:latest
EOF

Благодаря сущности Instrumentation будет создан init-контейнер, который скопирует необходимый jar со всеми либами для OTEL и разместит его в контейнере с основным приложением, для которого указана аннотация инструментирования. Помимо jar в сам деплоймент будут добавлены переменные окружения:

│   containers:                                                                                                                                                                              │
│   - env:                                                                                                                                                                                   │
│     - name: JAVA_TOOL_OPTIONS                                                                                                                                                              │
│       value: ' -javaagent:/otel-auto-instrumentation/javaagent.jar'                                                                                                                        │
│     - name: OTEL_SERVICE_NAME                                                                                                                                                              │
│       value: spring-petclinic                                                                                                                                                              │
│     - name: OTEL_EXPORTER_OTLP_ENDPOINT                                                                                                                                                    │
│       value: <http://coroot-opentelemetry-collector.coroot.svc.cluster.local:4317>                                                                                                  │
│     - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME

И в самом приложении будет изменен аргумент запуска: в java добавится путь до jar файла с либами otel.

Таким образом получается, что при однократном деплое оператора, создании Instrumentation и добавлении аннотаций выполняется автоматически вся работа.

Визуализация

Теперь метрики приложения пишутся в коллектор, указанный в переменной окружения. Далее остается вопрос их визуализации.

Вариантов опять же несколько. Можно писать метрики в формате otel, jaeger, zipkin и т.д. В документации на сайте opentelemetry есть helm-чарт с демо приложением и всеми компонентами, в составе которых jaeger для трасс, прометей и графана для метрик:

Это уже классические и устоявшиеся решения. Но есть также и более новые продукты. Один из называется SigNoz. По сути он представляет из себя UI для трассировок, логов и метрик сразу + является опенсорс.

Такие решения имеют также название APM – Application Performance Monitoring. APM нацелен конкретно на приложения, а Observability на всю систему целиком. Но оба понятия APM и Observability связаны и могут быть использованы совместно для обеспечения высокой производительности и стабильности приложений.

Как всё это запустить

В заключении можно наглядно продемонстрировать пошаговый HowTo по установке Coroot.

Подготовка № 1. Демо-приложение

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

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

kubectl create ns demo
helm upgrade onlineboutique oci://us-docker.pkg.dev/online-boutique-ci/charts/onlineboutique \\
    --install

Получится следующий набор подов:

Для доступа можно сразу прокинуть фронтовый сервис к себе локально:

kubectl port-forward -n demo service/frontend 8081:80

Подготовка № 2. Оператор OTEL

Оператор занимается управлением коллекторами OpenTelemetry и обеспечивает auto instrumentation приложений. С минимальным кол-вом настроек можно быстро и просто получить трассировки.

Оператор устанавливается в отдельный namespace: kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.79.0/opentelemetry-operator.yaml

(при этом в документации сказано, что требуется cert-manager, про него в данной статье не описано, хотя он и присутствует в кластере для других целей, не связанных с темой статьи)

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

Последняя необходимая сущность в рамках подготовки – это Instrumentation:

kubectl -n demo apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: my-instrumentation
spec:
  exporter:
    endpoint: <http://coroot-opentelemetry-collector.coroot.svc.cluster.local:4317>
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "0.25"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
  dotnet:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-dotnet:latest
EOF

В манифесте Instrumentation указывается точка, в которую необходимо пересылать метрики и трассы – это будущий сервис Coroot, который будет установлен. Помимо этого в манифесте также указываются образы, уже содержащие в себе необходимые библиотеки для получения метрик и трассировок для различных ЯП.

Coroot

Теперь когда есть демо-приложение и коллектор opentelemetry, можно приступать к установке Coroot:

helm repo add coroot <https://coroot.github.io/helm-charts>
helm repo update
kubectl create ns coroot
helm install --namespace coroot --create-namespace coroot coroot/coroot

На этом этапе можно смотреть UI Coroot (стоит выждать 3-5 минут пока метрики будут подгружены из прометеуса):

kubectl port-forward -n coroot service/coroot 8080:8080

При открытии главной страницы можно видеть карту взаимодействия сервисов демо-приложения онлайн-магазина:

Важный момент: карта будет строиться только тогда, когда есть трафик.

При открытии конкретного приложения, например, фронта, будет видно кто к нему обращается и куда далее проходит запрос:

При этом будут доступны полезные метрики, типа задержки, кол-ва запросов в секунду и т.д, не считая стандартных метрик утилизации CPU\RAM контейнера:

При этом есть возможность (благодаря перископу & eBPF) для профилирования и получения подробных низкоуровневых метрик по CPU. Например, видны какие функции приложения на Golang и сколько времени CPU они занимали при выполнении:

И еще один пример получения метрик через eBPF для Java-приложения, который показывает какие функции стали вызываться намного дольше относительно прошлого периода:

Для получения трассировок необходимо внести изменения в деплоймент. Например, возьмем микросервис adservice, который написан на Java. В аннотации пода необходимо добавить следующую запись:

spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-java: "true"

В результате произойдет следующее: оператор обнаружит аннотацию, создат инит-контейнер с нужным набором библиотек (благодаря instrumentation) и выполнит перезапуск основного пода с новыми аргументами + добавит в под переменные окружения, необходимые для работы open telemetry.

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

В результате в разделе трассировок можно видеть вызываемые методы, ответы и время выполнения:

Заключение

Про eBPF можно сказать многое, т.к. функционал не ограничивается тем, что было описано в статье.

Если на стороне приложения нет возможности инструментировать код (нет исходников или сложные процессы внедрения), в ход вступает eBPF. Программа будет перехватывать запросы приложения на уровне ядра. Но такой вариант не всегда подходит и работает из коробки, т.к. требует доработки – должна быть связь между всеми запросами для выстраивания трассы. В подобных ситуациях, когда в приложении отсутствует инструментарий OpenTelemetry, на помощь может прийти coroot-node-agent , который перехватывает исходящие запросы на уровне eBPF и экспортирует их в виде участков трассировки OpenTelemetry.

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

Как видно из графиков и возможностей, Coroot – вполне себе достаточный инструмент, использующий одновременно и OpenTelemetry и eBPF для получения сводной и первичной информации по утилизации и взаимодействию приложений внутри кластера K8s. Помимо траблшутинга, можно получать все инфраструктурные метрики: утилизация памяти, процессора, дисковой подсистемы.

Также стоит отметить, что Coroot – опенсорс, как и подкапотный инструментарий типа самого OpenTelemetry. То есть в случае чего всего можно использовать другой бэк для обработки трассировок запросов и другой фронт для визуализации. Хоть графану, в которой есть ресурсы для отрисовки трасс и спанов. Но Coroot всем хорош на начальном этапе. К тому же продукт от российских разработчиков.

Также решения типа Service Mesh могут отойти на второй план, когда eBPF умеет всё это делать из коробки и быстрее на уровне ядра, без всяких промежуточных sidecar-контейнеров.

Одним из нюансов может быть возросшая нагрузка на кластер при добавлении библиотек для Open Telemetry, но это нужно рассматривать в конкретных примерах. Для упрощения можно применить tail-сэмплинг, когда система сначала собирает все трассировки и далее уже принимает решение, нужно сохранять trace или нет. То есть не нужно собирать слишком много лишних данных и тогда ситуация с нагрузкой будет контролируемая.

Из конкурентов стоит выделить инструменты Apache SkyWalking – https://github.com/apache/skywalking-helm и SigNoz – https://github.com/SigNoz/signoz

Используемые источники

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: