Coroot: eBPF-профилирование, логи, трейсы в приложениях в kubernetes

Классический мониторинг отвечает на вопрос «что сломалось»: метрики показывают рост CPU, логи — стек ошибок, трейсы — медленный сервис. Но когда нужно ответить «почему именно этот сервис стал медленным», почти все инструменты пасуют. Вы видите, что контейнер потребляет 2 ядра CPU, но не видите, какая строка кода их загружает.
Coroot — open-source observability-платформа, которая из метрик, логов и трейсов делает выводы о том, что чинить. Её ключевая особенность — непрерывное профилирование без изменений кода: eBPF-профилировщик снимает CPU-профили всех процессов на ноде, а языковые профилировщики (Go, Java) добавляют память и блокировки. В результате флеймграф показывает нагрузку до точной строки кода, а предустановленные инспекции находят типовые проблемы — утечки памяти, лишние аллокации, блокировки.
Coroot ставится в любой Kubernetes-кластер. В этой статье мы развернём Coroot через официальный coroot-operator (Community Edition), а затем задеплоим четыре намеренно «сломанных» приложения — на Nuxt (Node.js), Python, Go и Java — и посмотрим, как их проблемы всплывают в профилировании.
Coroot vs Pyroscope vs Parca vs Pixie vs Perforator
Метрика | Coroot (Community Edition) | Grafana Pyroscope | Parca | Pixie | Perforator (Yandex) |
|---|---|---|---|---|---|
Профилирование | eBPF CPU + Go (heap/pprof) + Java (async-profiler) | языковые SDK, Grafana Alloy, OTLP; eBPF через Alloy/OTel | eBPF + pprof | eBPF-автоинструментация k8s, CPU-профили | eBPF kernel + userspace, CPU, sPGO/AutoFDO |
Нужны ли изменения кода | Нет (eBPF CPU + Go heap), только для blocking/mutex/goroutine — опционально pprof | Да — SDK/агент (eBPF только через Alloy/OTel) | Нет (eBPF) | Нет (eBPF) | Нет (eBPF) |
Метрики + логи + трейсы | есть, в одном UI | нет (только профили) | нет (только профили) | частично (eBPF-метрики, запросы и трейсы) | нет (только профили) |
Автодиагностика (инспекции) | есть, 80%+ типовых проблем | нет | нет | частично (готовые PxL-скрипты) | нет |
SLO-алертинг | есть | нет | нет | нет | нет |
Service Map | есть | нет | нет | частично (по eBPF-трафику) | нет |
Хранилище профилей | ClickHouse | S3-совместимое | object storage | локально в кластере (краткосрочное) | ClickHouse (метаданные профилей) + PostgreSQL (метаданные бинарей) + S3-совместимое (сырые профили) |
Self-hosted | есть | есть | есть | есть | есть |
Два профилировщика с eBPF-сбором: Pixie — open-source eBPF-автоинструментация для Kubernetes (метрики, запросы и CPU-профили без изменений в подах); Perforator от Yandex — production-ready continuous profiling для больших датацентров (десятки тысяч нод), вдохновлённый Google-Wide Profiling, с размоткой стека без frame pointers и sPGO-профилями для PGO-сборки.
Два способа профилирования: eBPF и user-space
Coroot собирает профили двумя дополняющими способами: eBPF покрывает CPU всех процессов на ноде без изменений в коде, а языковые профилировщики добирают память и блокировки конкретных рантаймов. Оба механизма живут внутри агентов Coroot и обращаются к чужому процессу снаружи:
Go heap —
coroot-node-agentчитаетruntime.MemProfileиз памяти процесса (/proc/<pid>/mem), в приложение ничего не подключаетсяGo pprof —
coroot-cluster-agentскрейпит стандартный/debug/pprof(CPU/blocking/mutex), который нужно лишь экспортировать и пометить аннотациямиJava —
coroot-node-agentнаходит HotSpot JVM и динамически подгружаетlibasync-profiler.soчерез JVM Attach API (CPU/alloc/lock)Python — eBPF-инструментирование резолвит Python-фреймы через Pyroscope eBPF-профайлер, включённый в
coroot-node-agent
Предварительные требования
Для развёртывания демо-окружения понадобятся:
Часть 1. Разворачиваем Coroot в Kubernetes
Архитектура
Coroot в кластере состоит из нескольких компонентов, которые разворачивает coroot-operator:
coroot — сам сервер: UI, API, инспекции
coroot-node-agent — DaemonSet на каждой ноде: eBPF CPU-профилировщик (плюс Go heap-профайлер, Python-инструментирование и Java через async-profiler), метрики, логи, трейсы
coroot-cluster-agent — Deployment: кластерная телеметрия + pprof-скрейп Go-приложений
Prometheus — хранилище метрик
ClickHouse — хранилище логов, трейсов и профилей (+ clickhouse-keeper для координации)
Шаг 1. Установка Coroot в кластер
Coroot ставится вручную через Helm. Сначала создаём namespace и Secret с паролем администратора:
kubectl create namespace coroot
kubectl -n coroot create secret generic coroot-admin-secret \
--from-literal=admin-password=<пароль-админа>Затем создаём coroot-values.yaml.
Файл coroot-values.yaml:
metricsRefreshInterval: "30s"
# Retention: все данные Coroot хранятся не дольше 1 часа
cacheTTL: "1h" # TTL метрического кэша Coroot
tracesTTL: "1h" # TTL таблиц трейсов в ClickHouse
logsTTL: "1h" # TTL таблиц логов в ClickHouse
profilesTTL: "1h" # TTL таблиц профилей в ClickHouse
authBootstrapAdminPasswordSecret:
name: coroot-admin-secret
key: admin-password
ingress:
className: traefik
host: coroot_url
path: /
clickhouse:
shards: 1
replicas: 1
keeper:
replicas: 1
storage:
size: "20Gi"
prometheus:
retention: "1h"
storage:
size: "10Gi"
storage:
size: "10Gi"
# Java-профилирование: node-agent динамически подгружает async-profiler в HotSpot JVM
nodeAgent:
env:
- name: ENABLE_JAVA_ASYNC_PROFILER
value: "true"Устанавливаем оператор и сам Coroot:
# Оператор Coroot (управляет Coroot CR, node-agent, cluster-agent, Prometheus, ClickHouse)
helm install coroot-operator oci://ghcr.io/coroot/charts/coroot-operator \
--version 0.9.10 -n coroot
# Coroot CE: чарт рендерит Coroot CR (spec — из coroot-values.yaml)
helm install coroot oci://ghcr.io/coroot/charts/coroot-ce \
--version 0.3.3 -n coroot -f coroot-values.yamlШаг 2. Coroot CR и retention 1 час
Helm-чарт coroot-ce рендерит Custom Resource Coroot, которым управляет оператор. Что здесь важно:
Retention ограничен 1 часом в трёх местах: TTL таблиц ClickHouse (
logsTTL/tracesTTL/profilesTTL), метрический кэш (cacheTTL) и retention встроенного Prometheus (prometheus.retention: "1h").Нюанс по Prometheus: данные хранятся двухчасовыми блоками, поэтому при
prometheus.retention: "1h"фактический горизонт метрик лежит в диапазоне ~2–4 часа.
Шаг 3. Проверяем
В UI входим с логином admin и паролем администратора, заданным на Шаге 1 (admin-password). Оператор уже сконфигурировал Prometheus и ClickHouse и создал проект default, поэтому ничего настраивать не нужно — сразу переходим к приложениям.

Страница Applications в целом понятна, но отметим пару нюансов.
shortage в колонке CPU (подчёркнуто красным) — не «процент загрузки», а нехватка процессорного времени: сколько времени процессы ждали CPU, но не получали его. Причины — троттлинг по лимиту CPU либо конкуренция с другими контейнерами на ноде.
Latency против Net — это разные вещи. Latency — время ответа самого приложения на запросы клиентов (сколько ждут вызывающие стороны). Net — сетевой round-trip time на TCP-уровне между приложением и сервисами, от которых оно зависит: время обработки приложением в него не входит, измеряется только сетевая компонента. Поэтому demo-golang имеет Latency 5ms, но Net <0.1ms — приложение быстрое, сеть не задерживает.
Incidents

В Coroot управление инцидентами (Incidents) построено вокруг концепции SLO-based alerting (оповещений на основе целей уровня обслуживания). Вместо сотен разрозненных алертов на каждое событие процессора Coroot создаёт единый инцидент, только когда приложение действительно «страдает» с точки зрения пользователя.
Alerts

Встроенная система алертинга на базе преднастроенных алертов автоматически выявляет проблемы в приложениях и инфраструктуре Kubernetes и показывает первопричины сбоев.
Для отправки алертов наружу необходимо зайти Project Settings → Integrations: Slack, Microsoft Teams, PagerDuty, Opsgenie, webhook. Маршрутизация по категориям приложений и типам событий: Incidents, Deployments, Alerts. Источники алертов: инспекции, новые паттерны в логах, Kubernetes-события, кастомный PromQL.
Service Map

Service Map — автоматическая визуализация связей между компонентами, без ручной настройки и без изменений кода.
Шаг 4. OpenTelemetry Collector
Скорее всего, у вас уже установлен OpenTelemetry Collector, поэтому конфигурируем отправку трейсов через него — он принимает трейсы от всех четырёх приложений по OTLP/HTTP (порт 4318), батчит их и пересылает в Coroot. Конфигурация — в otel-collector-values.yaml в корне репозитория (используется config чарта open-telemetry/opentelemetry-collector, который сливается с дефолтным конфигом: ненужные дефолтные ресиверы jaeger/zipkin/prometheus и pipelines logs/metrics явно обнулены через null, остаётся только HTTP-ресивер трейсов).
Файл otel-collector-values.yaml:
mode: deployment
# Короткое и предсказуемое имя ресурсов (иначе будет <release>-opentelemetry-collector).
fullnameOverride: otel-collector
image:
repository: otel/opentelemetry-collector-contrib
config:
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
exporters:
otlp_http/coroot:
endpoint: "http://coroot-coroot.coroot:8080"
service:
pipelines:
# Дефолтные pipelines logs/metrics не нужны — глушим.
logs: null
metrics: null
# traces сливается с дефолтным, поэтому список receivers переопределяем
# целиком (без jaeger/zipkin) и меняем exporters на coroot.
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp_http/coroot]Устанавливаем коллектор:
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm install otel-collector open-telemetry/opentelemetry-collector \
--version 0.173.1 -n otel --create-namespace -f otel-collector-values.yamlКоллектор слушает OTLP/HTTP на 4318 в namespace otel. Приложения обращаются к нему по адресу http://otel-collector.otel:4318/v1/traces, а сам коллектор пересылает батчи в Coroot на внутренний сервис coroot-coroot.coroot:8080.
Обзор Tracing с установленными 4 demo приложениями. В разделе трассировок пять вкладок:

OVERVIEW — HeatMap распределения запросов во времени со статусами и длительностью. По тепловой карте сразу видны аномалии; выделив область, можно посмотреть входящие в неё трейсы.

TRACES — просмотр отдельных трейсов по выделенной области: путь запроса по сервисам, спаны и их длительности.
ERROR CAUSES — автоматически анализирует все затронутые запросы в выделенной области и находит спаны с ошибками — одного ли типа все ошибки или происходят разные сбои одновременно.

LATENCY EXPLORER — сравнивает длительность операций с остальными запросами; задержка визуализируется как latency-флеймграф, замедлившиеся операции подсвечиваются красным.

COMPARE ATTRIBUTES — сравнение атрибутов трасс внутри выделенной области с остальными запросами, полезно при разном поведении для конкретных клиентов, браузеров или feature flag.
Часть 2. Четыре «сломанных» приложения
Чтобы продемонстрировать профилирование, задеплоим четыре приложения с намеренно внесёнными проблемами. Исходники — в каталоге apps, деплой — Helm-чартом chart. Вместе с приложениями чарт поднимает генераторы нагрузки — по одному Kubernetes Job на каждое включённое приложение.
Обзор приложения и SLO
При открытии приложения Coroot показывает SLO (Service Level Objectives) — целевые показатели надёжности сервиса. По умолчанию отслеживаются два SLO: Availability (99% запросов должны быть обслужены без ошибок) и Latency (99% запросов должны обслуживаться быстрее 500 мс). Coroot считает SLI по eBPF-метрикам на уровне приложения и показывает фактическое соблюдение объектива, latency в виде гистограммы с фиксированными бакетами (5 мс — 10 с) и остаток error budget.
Шаг 1. Nuxt (Node.js)
Приложение на Nuxt 3 с единственным API-эндпоинтом /api/cpu, который считает наивный Фибоначчи (fib(35) — ~30 млн рекурсивных вызовов). Экспоненциальная сложность мгновенно видна в CPU-профиле.
Ключевой момент — символизация JS-фреймов. eBPF-профилировщик снимает нативные стектрейсы, но без perf-map названия JS-функций не резолвятся. Node.js умеет генерировать perf-map сам, если запустить его с флагами.
Файл chart/values.yaml (фрагмент):
nuxt:
env:
NODE_OPTIONS: "--perf-basic-prof-only-functions --interpreted-frames-native-stack"Файл apps/nuxt/Dockerfile (фрагмент):
ENV NODE_OPTIONS="--perf-basic-prof-only-functions --interpreted-frames-native-stack"С этими флагами во флеймграфе будут реальные имена функций fib/fib, а не анонимные адреса.
Минимальная рабочая версия — 18.19+ / 20.10+ / 21.1+.
Трейсы подключаются через OpenTelemetry: Nitro-плагин запускает NodeSDK с HttpInstrumentation, который на каждый запрос создаёт server-span, а в обработчике добавляется вложенный span fib.
Файл apps/nuxt/server/plugins/otel.ts (фрагмент):
export default defineNitroPlugin(() => {
const sdk = new NodeSDK({
instrumentations: [new HttpInstrumentation()],
})
sdk.start()
})Экспорт — в OpenTelemetry Collector через OTLP.
Файл chart/values.yaml (фрагмент):
nuxt:
env:
OTEL_SERVICE_NAME: "demo-nuxt"
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-collector.otel:4318/v1/traces"
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: "http/protobuf"Что видно в Coroot

На overview-slo видно соблюдение двух SLO (Availability и Latency), остаток error budget и гистограмму latency с фиксированными бакетами.

Инспекция CPU показывает две проверки: Node CPU utilization — ok (загрузка ноды ниже порога 80%), и Container CPU utilization — high CPU utilization of 1 container: контейнер demo-nuxt превышает 80% своего CPU-лимита.

Флеймграф показывает, что почти всё CPU-время (~100%) уходит в event loop Node.js: стек идёт через uv__io_poll → uv__read → uv__stream_io, затем через microtask-очередь попадает в Nitro/Nuxt. Далее профиль делится на двух потребителей CPU: ~33% — обработчик /api/cpu (api/cpu.get.mjs), где почти все сэмплы приходятся на рекурсивный fib (api/cpu.get.mjs:924 — наивные числа Фибоначчи); остальное — обвязка Nitro (nitro.mjs:1645) и накладные расходы V8/libuv. Узкое место — CPU-bound рекурсия fib, а не HTTP-цикл.

На вкладке Tracing у demo-nuxt — server-span на каждый запрос и вложенный span на вычисление. Из аномалии открывается флеймграф, из медленного span'а — логи и профили.
Шаг 2. Python
Python-приложение на стандартном http.server с эндпоинтом /cpu: наивный fib(30) плюс busy-loop с math.sqrt. eBPF-профилировщик Coroot снимает CPU-профиль Python-процесса без каких-либо агентов и изменений кода, а Pyroscope eBPF-профайлер резолвит Python-фреймы, так что во флеймграфе виден именно naive_fib.
Трейсы — автоинструментация OpenTelemetry: приложение запускается через opentelemetry-instrument, который сам инструментирует http.server и экспортирует server-span'ы в OpenTelemetry Collector через OTLP. В app.py обработчик дополнительно оборачивается во вложенный span через trace.get_tracer(...).
Файл apps/python/app.py (фрагмент):
from opentelemetry import trace
tracer = trace.get_tracer("demo-python")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with tracer.start_as_current_span(self.path):
...Файл apps/python/Dockerfile (фрагмент):
CMD ["opentelemetry-instrument", "--traces_exporter", "otlp_proto_http", "--metrics_exporter", "none", "--logs_exporter", "none", "python", "app.py"]Файл chart/values.yaml — тот же фрагмент, что для Nuxt, только OTEL_SERVICE_NAME: "demo-python".
Что видно в Coroot

На overview-slo Availability SLO в статусе OK (доля успешных запросов ≥ 99%), а Latency SLO нарушен: error budget burn rate 100.0x в течение 1 часа, так как доля запросов быстрее 500 мс ниже 99% — вызов /cpu с наивным fib(30) и busy-loop выходит за предел в 500 мс.

В инспекции CPU те же две проверки: Node CPU utilization — ok (загрузка ноды ниже порога 80%), и Container CPU utilization — high CPU utilization of 1 container: контейнер demo-python превышает 80% своего CPU-лимита.

Флеймграф Profiling за выбранный интервал покажет CPU в naive_fib и в busy-loop с math.sqrt; режим Comparison подсветит красным функции, которые стали есть больше CPU относительно прошлого интервала.

На вкладке Tracing у demo-python — server-span от автоинструментации http.server и вложенный span на вычисление. По аномалии можно перейти во флеймграф, по медленному span'у — в логи и профили.
Шаг 3. Golang
Go-приложение с тремя проблемами сразу:
утечка памяти — фоновый цикл каждую секунду добавляет 1 MiB в слайс, который никогда не освобождается (лимит пода 2 GiB). Генератор нагрузки ещё дергает
/leak, поэтому растут и горутины — под упирается в память и перезапускаетсяутечка горутин — эндпоинт
/leakзапускает горутину, которая блокируется навсегдаCPU-нагрузка — эндпоинт
/cpuс бесполезным циклом на 5 млн итераций
Для Go Coroot собирает профили по трём каналам, которые частично пересекаются:

Тип профиля | eBPF (node-agent) | Go heap-профайлер (node-agent, | pprof-скрейп (cluster-agent, |
|---|---|---|---|
CPU | есть, все процессы, любой язык | нет | есть, |
heap / Memory | нет | есть, | есть, |
blocking | нет | нет | есть, |
mutex | нет | нет | есть, |
goroutine | нет | нет | есть, |
Выбор профилей зависит от задачи:
Нужны профили | Что делать | Изменения в коде |
|---|---|---|
heap / CPU | ничего: | не нужны |
blocking / mutex / goroutine | eBPF их не даёт — нужен pprof-скрейп ( |
|
Для heap и CPU Go-приложение не требует ни правки кода, ни profile-scrape — оба канала работают извне (eBPF + чтение /proc/<pid>/mem). pprof-скрейп нужен только ради профилей, которых нет в eBPF: blocking, mutex и goroutine.
Файл chart/values.yaml (фрагмент):
golang:
podAnnotations:
coroot.com/profile-scrape: "true"
coroot.com/profile-port: "8080"Сам код подключает net/http/pprof одной строкой:
import _ "net/http/pprof"Трейсы — ручное инструментирование OpenTelemetry (автоинструментации для Go в Coroot нет): маршрутизатор оборачивается в otelhttp.NewHandler, а OTLP-экспортер читает endpoint из env и шлёт спаны в OpenTelemetry Collector. Каждый входящий запрос на /cpu и /leak становится трейсом в Coroot.
Файл apps/golang/main.go (фрагмент):
exporter, err := otlptrace.New(ctx, otlptracehttp.NewClient())
// ...
handler := otelhttp.NewHandler(http.DefaultServeMux, "http-server")Привязка минимального Go к версии opentelemetry-go:
Версия | Минимальный Go |
|---|---|
v1.38.0 | Go 1.23 |
v1.42.0 | Go 1.24 |
v1.46.0 (используется в проекте) | Go 1.25 |
v1.47.0+ | Go 1.26 |
Файл apps/golang/Dockerfile (фрагмент):
FROM golang:1.25-alpine AS buildФайл chart/values.yaml (фрагмент):
golang:
env:
OTEL_SERVICE_NAME: "demo-golang"
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-collector.otel:4318/v1/traces"
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: "http/protobuf"Memory-профиль показывает устойчивый рост alloc_space: куча растёт на ~1 MiB/сек за счёт фонового growLeak. Флеймграф memory-профиля указывает точное место — main.growLeak, где происходит append в leakBuf.
Что видно в Coroot

На overview-slo видно, как рост памяти и утечка горутин деградируют соблюдение двух SLO (Availability и Latency); вызовы /cpu и /leak уходят за objective 500 мс.

Горутины-утечки видны косвенно: число горутин растёт (/healthz отдаёт runtime.NumGoroutine()), а Coroot связывает это с ростом потребления и деградацией SLO.

На вкладке Tracing — server-span на каждый запрос (otelhttp.NewHandler). HeatMap и выделение области — как в Шаге 1. Из CPU-аномалии открывается флеймграф, из медленного span'а — логи и профили.
Шаг 4. Java
Java-профилирование включается флагом ENABLE_JAVA_ASYNC_PROFILER=true в coroot-values.yaml (см. Шаг 1). Java-агент для профилирования не нужен — coroot-node-agent сам находит HotSpot JVM по libjvm.so в /proc/<pid>/maps и подгружает async-profiler через JVM Attach API. (Java-агент в apps/java/Dockerfile — это OpenTelemetry-инструментация для трейсов, к профилированию отношения не имеет.)
Java-приложение на встроенном com.sun.net.httpserver с тремя эндпоинтами:
/cpu— наивныйfib(35)плюс цикл на 5 млн итераций/alloc— фоновая аллокация массивов (видна в Memory-профиле какalloc_space/alloc_objects)/lock— два потока намеренно конкурируют за один монитор (synchronized+sleep) и создают Lock-профиль
Профилирование работает и без JVM-флагов — async-profiler подгружается в JVM динамически (через JVM Attach API). Но без них флеймграф деградирует: dynamical attach не гарантирует, что у уже скомпилированного JIT-кода есть подходящие символы и frame pointer, поэтому горячие сэмплы не резолвятся в имена методов и падают в [unknown] (а naiveFib может вообще исчезнуть как отдельный фрейм, будучи заинлайненным в cpuBurn). Флаги ниже минимизируют потери и оставляют профиль «до точной строки кода»:
Файл apps/java/Dockerfile (фрагмент):
ENTRYPOINT ["java", \
"-javaagent:/app/opentelemetry-javaagent.jar", \
"-XX:+UnlockDiagnosticVMOptions", \
"-XX:+DebugNonSafepoints", \
"-XX:+PreserveFramePointer", \
"-XX:TieredStopAtLevel=1", \
"-XX:CompileCommand=dontinline,DemoJava.naiveFib", \
"-cp", "/app", "DemoJava"]-XX:+UnlockDiagnosticVMOptionsразблокирует диагностические опции — без него JVM не примет-XX:+DebugNonSafepoints.-XX:+DebugNonSafepointsзаставляет JIT сохранять debug-информацию и в несейфпоинтах — без этого часть JIT-фреймов не резолвится в[unknown].-XX:+PreserveFramePointerсохраняет RBP для раскрутки стека — без этого async-profiler (и eBPF-профилировщик) не могут пройтись по нативному стеку, и сэмплы уходят в[unknown].-XX:TieredStopAtLevel=1оставляет только C1-компиляцию: простой машинный код легче маппится обратно в методы, чем агрессивно оптимизированный C2.-XX:CompileCommand=dontinline,DemoJava.naiveFibзапрещает инлайнитьnaiveFib— иначе метода не будет видно отдельным фреймом.
Для demo-java Coroot показывает сразу несколько типов профилей: async-profiler отдаёт CPU, память и блокировки, а приставка «Java» отличает их от CPU-профиля eBPF-профилировщика. На вкладке Profiling в капле выбора типа профиля шесть позиций:

Тип профиля | eBPF (node-agent) | async-profiler (node-agent, JVM Attach API) |
|---|---|---|
CPU (eBPF) | есть, все процессы, любой язык | нет |
Java CPU | нет | есть, событие |
Java Lock (contentions) | нет | есть, событие |
Java Lock (delay) | нет | есть, событие |
Java Memory (alloc_objects) | нет | есть, событие |
Java Memory (alloc_space) | нет | есть, событие |
Выбор профилей зависит от задачи:
Нужны профили | Что делать | Изменения в коде |
|---|---|---|
CPU (eBPF) | ничего: eBPF-профилировщик снимает CPU всех процессов без дополнительной настройки | не нужны |
Java CPU / Memory / Lock | включить | не нужны; JVM-флаги ( |
Для всех типов профилей Java правка кода не нужна: CPU снимает eBPF-профилировщик, а всё с префиксом «Java» — async-profiler, который node-agent динамически подгружает в HotSpot JVM. JVM-флаги из предыдущего абзаца не обязательны — они лишь минимизируют [unknown] и оставляют профиль «до точной строки кода».
Трейсы — автоматическая инструментация через OpenTelemetry Java-агент: jar скачивается в образе и подключается флагом -javaagent, так что менять код не нужно — спаны HTTP-запросов генерируются автоматически и уходят в OpenTelemetry Collector через OTLP.
Файл apps/java/Dockerfile (фрагмент):
RUN wget -q -O /opentelemetry-javaagent.jar \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jarФайл chart/values.yaml — тот же фрагмент, что для Nuxt, только OTEL_SERVICE_NAME: "demo-java".
Что видно в Coroot

На overview-slo Availability SLO в статусе OK (доля успешных запросов ≥ 99%), а Latency SLO нарушен: error budget burn rate 65.9x в течение 1 часа, так как доля запросов быстрее 500 мс ниже 99% — вызов /cpu с наивным fib(35) уходит за objective 500 мс, а /alloc даёт рост потребления памяти.

Инспекция CPU фиксирует то же самое: Node CPU utilization — ok (загрузка ноды ниже порога 80%), и Container CPU utilization — high CPU utilization of 1 container: контейнер demo-java превышает 80% своего CPU-лимита.

Инспекция JVM выводит две проверки: JVM availability — ok (число недоступных инстансов JVM не превышает 0), и JVM safepoints — ok (время остановки приложения на safepoint-операциях не превышает 50 мс).

На вкладке Tracing — server-span на каждый запрос (OTel Java-агент, без изменений кода). Аномалия CPU ведёт во флеймграф, медленный span — в логи и профили.

CPU (eBPF) — нативный CPU-профиль с eBPF-профайлера поверх всех процессов ноды: почти всё время уходит в naiveFib (рекурсия с экспоненциальной сложностью), как и у Python/Node.js.

Java Lock (contentions) — число контеншенов монитора: два потока конкурируют за один synchronized-блок на эндпоинте /lock.

Java Memory (alloc_objects) — рост числа аллоцированных объектов по стеку аллокаций в DemoJava.allocate (эндпоинт /alloc). Парный профиль Java Memory (alloc_space) показывает тот же стек в байтах.
На вкладке Profiling async-profiler отдаёт те же типы профилей: CPU (почти всё время в naiveFib), Memory (рост alloc_space/alloc_objects по стеку аллокаций в DemoJava.allocate) и Lock (время ожидания монитора и число контеншенов на synchronized-блоке).
Масштабирование и обновление
Реплики и ClickHouse
Для продакшена имеет смысл clickhouse.shards/replicas: 2 и keeper.replicas: 3 (по умолчанию), а также несколько реплик Coroot (replicas: 2), для чего потребуется вынести конфигурацию из SQLite в PostgreSQL (postgres.* в CR).
Заключение
Coroot отвечает на вопрос «почему медленно» — на него классический мониторинг не отвечает. Непрерывное eBPF-профилирование снимает CPU-профили без изменений кода, языковые профилировщики добавляют память и блокировки, а предустановленные инспекции находят типовые проблемы. Всё это — с метриками, логами и трейсами в одном UI.
Ключевые особенности:
Без инструментирования — eBPF снимает CPU-профили всех процессов без изменений в коде
Флеймграф до строки кода — CPU и память по клику, сравнение с базовой линией
Предустановленные инспекции — находят ~80% типовых проблем автоматически
Все сигналы в одном месте — метрики, логи, трейсы и профили связаны между собой
Один Helm-чарт — coroot-operator управляет всем стеком
Полезные ссылки:
GitHub: github.com/coroot/coroot
Документация: docs.coroot.com
Helm-чарты (OCI): ghcr.io/coroot/charts
Operator: github.com/coroot/coroot-operator
Live demo: demo.coroot.com
Подписывайтесь на ТГ: https://t.me/notes_devops_engineer
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.