Зачем всё это?
Для реализации “настоящей” наблюдаемости в кластере 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 оперирует понятием трасса (например, запрос пользователя, в котором есть множество обращений к разным микросервисам) и спан (каждый из этих микросервисов).
Более подробно процесс описан далее.
- Трассировка (tracing): OpenTelemetry позволяет собирать данные о времени выполнения и взаимодействии различных компонентов системы. Это может включать информацию о вызовах API, баз данных, внешних сервисах и других компонентах. Примером является дерево трассировки, которое показывает путь запроса через различные компоненты системы и время, затраченное на каждую операцию.
Например, в приложении есть множество интеграций и при его медленной работе сложно выявить, с чем именно проблема – с сетью, БД или сторонним сервисом, с которым настроена интеграция. Для этого нужны трассировки, в которых будут запросы ко всем зависимым сервисам со временем выполнения, заголовками, эндпоинтами и т.п.
- Метрики (metrics): OpenTelemetry позволяет собирать данные о производительности и использовании ресурсов системы. Это может включать показатели, такие как загрузка ЦП, использование памяти, количество запросов и другие метрики для бизнеса, которые помогают оценить состояние и эффективность системы.
В продолжение примера выше. У медленного работающего приложения в трассировки добавляется информация по утилизации ресурсов. Картина может вырисовываться более чем понятная для диагностики проблемы.
- Журналы (logs): OpenTelemetry может собирать данные из журналов приложений и системы, что позволяет отслеживать ошибки, предупреждения и другую информацию, которая может быть полезной для отладки и мониторинга системы.
- Далее все эти трассы, метрики и логи поступают в коллектор. Коллектор – сервис, занимающийся сбором, обработкой и отправкой замеров/трейсов вендору. Может работать как на той же машине, что и приложение (Агент), так и на другой (Коллектор).
- Вендор – сервис, хранящий все собранные данные. Например, Jaeger (поддерживает OTLP) или Zipkin (для него есть правила трансформации, которые выполняет коллектор). С помощью различных сервисов вендора можно визуализировать полученные данные.
Схематический пример вышеописанного:
И пример времени ответа каждого микросервиса в приложении интернет-магазина (трассировки):
Теперь приложение обогащается сообщениями об ошибках при их наличии. И в конечном счёте предоставляет ту самую наблюдаемость.
Ещё немного теории по OpenTelemetry
Для работы трассировок в приложении есть два пути:
- Использовать Manual Instrumentation, т.е. добавить в код приложения нужные зависимости SDK для отдачи метрик по протоколу OTEL.
- Использовать 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
Используемые источники
- https://habr.com/ru/articles/746080/
- https://github.com/inspektor-gadget/inspektor-gadget/blob/main/docs/architecture.md
- https://www.youtube.com/playlist?list=PLj6h78yzYM2Ob49OYDOur_aptXEzxAK0S
- https://www.securitylab.ru/analytics/538801.php
- https://habr.com/ru/companies/skyeng/articles/690602/
- https://signoz.io/opentelemetry/java-agent/
- https://signoz.io/blog/opentelemetry-spring-boot/
- https://signoz.io/docs/tutorial/opentelemetry-operator-usage/#overview
- https://earthly.dev/blog/k8s-distributed-tracing/
- https://www.youtube.com/watch?v=fh3VbrPvAjg
- https://highload.today/blogs/telemetriya-v-raspredelennyh-sistemah-kakie-vozmozhnosti-ona-daet-razbiraem-demo-s-opentelemetry/
- https://habr.com/ru/companies/hh/articles/686414/
- https://medium.com/@rahul.pawar_38180/distributed-tracing-best-practices-for-2023-56bf108f1efb