Содержание
- Мониторинг и observability — это не одно и то же
- Три столпа: логи, метрики, трейсинг
- Golden signals, SLI, SLO и SLA
- Метрики: Prometheus и Grafana
- Логи: OpenSearch и ELK
- Трейсинг: OpenTelemetry и Jaeger
- Алертинг и борьба с шумом
- Расследование инцидентов и SRE-практики
- Импортозамещение observability-стека
- FAQ
Распределённая система ломается не там, где вы смотрите. Запрос проходит через шлюз, два-три микросервиса, очередь и базу — и где-то на этом пути latency вырастает с 80 мс до двух секунд. Логи каждого сервиса по отдельности выглядят нормально, дашборд CPU зелёный, а пользователь видит таймаут. Без сквозной наблюдаемости расследование такого инцидента превращается в гадание: команда листает разрозненные логи и пытается по памяти восстановить, что за чем шло.
Эта статья — практический разбор того, как строить observability (наблюдаемость) в корпоративной системе на Java/Kotlin: чем она отличается от привычного мониторинга, что такое три столпа (логи, метрики, трейсинг), как считать golden signals и SLO, какой стек собрать (Prometheus + Grafana, OpenSearch, OpenTelemetry + Jaeger), как не утонуть в шуме алертов и как всё это разворачивается в собственном контуре в условиях импортозамещения. Стек observability почти весь open-source и self-hosted — а именно с таким мы в Новакоме чаще всего и работаем.
Мониторинг и observability — это не одно и то же
Эти слова часто используют как синонимы, и зря. Разница принципиальная.
Мониторинг отвечает на заранее заданные вопросы. Вы знаете, что хотите следить за загрузкой CPU, свободным местом на диске, размером очереди и количеством 5xx-ответов. Вы заводите метрики и пороги, рисуете дашборды и получаете алерт, когда показатель вышел за границу. Это про известные проблемы: вы заранее предположили, что может сломаться, и поставили датчик.
Observability отвечает на вопросы, которые вы не задавали заранее. Система считается наблюдаемой, если по её внешним сигналам — логам, метрикам и трейсам — можно понять, что происходит внутри, не выкатывая новый релиз ради дополнительного лога. Это про неизвестные проблемы: «почему именно у этого клиента, именно на этом эндпоинте, именно в этот час latency подскочил в 20 раз». Мониторинг скажет, что latency вырос. Observability позволит дойти до причины.
В одном из профильных каналов это сформулировали точно: в распределённых облачных приложениях команды часто игнорируют необходимость централизованной наблюдаемости — и при инциденте сложно быстро найти первопричину, восстановить последовательность событий и вовремя среагировать на деградацию. Принцип, который это лечит, звучит так: инструментировать всё — latency, error rate, бизнес-метрики и трассировки.
Для монолита разница не так заметна — там обычно хватает логов и пары дашбордов. Она становится критичной ровно тогда, когда система распадается на сервисы: например, после перехода на событийную шину на Kafka или при построении высоконагруженной микросервисной архитектуры, где один пользовательский запрос порождает цепочку из десятка внутренних вызовов.
Три столпа: логи, метрики, трейсинг
Классическая модель observability стоит на трёх типах телеметрии. Они не взаимозаменяемы — каждый отвечает на свой вопрос.
| Столп | На какой вопрос отвечает | Формат данных | Типичный инструмент |
|---|---|---|---|
| Метрики | Что и насколько? (агрегаты во времени) | Числовые временные ряды | Prometheus, Grafana |
| Логи | Что именно произошло в этом событии? | Структурированные записи событий | OpenSearch, ELK, Loki |
| Трейсинг | Где в цепочке вызовов проблема? | Дерево спанов одного запроса | OpenTelemetry, Jaeger |
Метрики — это дёшево и компактно: число в секунду по тысяче сервисов почти ничего не стоит хранить. Они дают агрегированную картину — RPS, перцентили latency, error rate, заполнение пулов. Но метрика не скажет, какой именно запрос упал.
Логи дают детали конкретного события: какой клиент, какой запрос, какой стектрейс. Их слабое место — без метаинформации (trace id, user id, имя сервиса) поиск причины 404 или 500 превращается в перебор гигабайтов текста. Структурированное логирование с обязательными полями — половина успеха.
Трейсинг — то, чего не дают первые два столпа в распределённой системе. Трейс показывает путь одного запроса через все сервисы как дерево спанов с таймингами: видно, что 1,8 секунды из двух ушли на вызов одного downstream-сервиса, а тот ждал базу. Как формулируют практики, нужен «комбайн по отлавливанию трейсов и ошибок с точным указанием, где именно ошибка, что было в запросе и какой клиент» — это и есть сквозная трассировка, связанная с логами и метриками.
Сила появляется на стыке: из алерта по метрике вы проваливаетесь в конкретный трейс, из спана трейса — в связанные логи. Поэтому три столпа имеет смысл внедрять не по отдельности, а как единый коррелируемый контур, где всё связано общим trace id.
Golden signals, SLI, SLO и SLA
Инструментировать всё — не значит смотреть на всё одновременно. Чтобы не утонуть в сотнях графиков, SRE-практика предлагает начинать с четырёх golden signals (золотых сигналов) от Google:
- Latency — время ответа. Считать отдельно для успешных и для ошибочных запросов: быстрый 500-й маскирует медленный 200-й.
- Traffic — нагрузка: запросов в секунду, сообщений в очереди.
- Errors — доля ошибочных ответов (5xx, таймауты, нарушенные контракты).
- Saturation — насколько заполнены ресурсы: пулы соединений, очереди, память, CPU.
Этих четырёх сигналов на каждый сервис достаточно, чтобы за минуты понять характер деградации. Дальше выстраивается иерархия надёжности:
- SLI (Service Level Indicator) — измеряемый показатель качества. Например: «доля HTTP-запросов с latency < 300 мс» или «доля успешных ответов за 5 минут».
- SLO (Service Level Objective) — целевое значение SLI, ваша внутренняя цель. Например: «99,9% запросов за месяц быстрее 300 мс».
- SLA (Service Level Agreement) — внешнее обязательство перед клиентом с санкциями за нарушение. SLA всегда мягче SLO: внутренняя цель строже того, что вы обещаете наружу, чтобы иметь запас.
Ключевой инструмент, который из SLO вытекает, — error budget (бюджет ошибок). Если SLO 99,9%, то 0,1% запросов в месяц «разрешено» провалить — это примерно 43 минуты недоступности. Пока бюджет не исчерпан, команда катит фичи; когда бюджет горит — приоритет смещается на надёжность. Это превращает спор «релизить или стабилизировать» из эмоций в арифметику.
Здесь же видна цена простоя. Авария Auth0, когда сервис лежал больше пяти часов, обрушила вместе с собой десятки тысяч зависимых сервисов — наглядная иллюстрация того, что SLO и бюджет ошибок считают не ради красивых цифр, а ради реальных денег и репутации.
Метрики: Prometheus и Grafana
Де-факто стандарт метрик в облачном и Kubernetes-мире — Prometheus. Модель простая: сервисы отдают метрики на HTTP-эндпоинте /metrics, а Prometheus периодически их скрейпит (опрашивает) по расписанию (pull-модель) и складывает во встроенную time-series базу. Запросы — на языке PromQL. Конфигурация скрейпинга минимальна:
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'orders-service'
metrics_path: /actuator/prometheus
static_configs:
- targets: ['orders-service:8080']
- job_name: 'payments-service'
metrics_path: /actuator/prometheus
static_configs:
- targets: ['payments-service:8080']
rule_files:
- "alert-rules.yml"
В Spring Boot инструментирование делается через Micrometer — фасад над системами метрик. Достаточно зависимости micrometer-registry-prometheus, и Actuator сам поднимет эндпоинт /actuator/prometheus с JVM-, HTTP- и пуловыми метриками. Свои бизнес-метрики добавляются парой строк:
@Component
class OrderMetrics(registry: MeterRegistry) {
private val placed = registry.counter("orders.placed.total")
private val latency = registry.timer("orders.processing.duration")
fun recordPlaced() = placed.increment()
fun <T> measure(block: () -> T): T = latency.recordCallable(block)!!
}
Grafana поверх Prometheus — это визуализация, дашборды и алерты. Один дашборд на сервис с четырьмя golden signals плюс сводный дашборд по всей системе закрывают 80% повседневных задач. PromQL для перцентиля latency и error rate выглядит так:
# p95 latency по HTTP-запросам за 5 минут
histogram_quantile(0.95,
sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))
# доля ошибок 5xx
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m]))
Отдельно про эксплуатацию: Grafana — тоже система, и за ней нужно следить. В ноябре 2025 в Grafana Enterprise нашли уязвимость CVE-2025-41115 с максимальным рейтингом 10 из 10 — при включённом SCIM-провижининге внешний идентификатор мог смапиться на uid супер-админа. Урок прозаичный: компоненты observability-стека сами входят в периметр и требуют патч-менеджмента наравне с прикладными сервисами.
Логи: OpenSearch и ELK
Метрики говорят «что-то сломалось», логи говорят «вот что именно». Чтобы логи были полезны в распределённой системе, их нужно (1) централизовать, (2) структурировать и (3) связать с трейсами.
Классический стек — ELK (Elasticsearch + Logstash + Kibana). После смены лицензии Elasticsearch индустрия во многом перешла на его открытый форк — OpenSearch (поисковое ядро + OpenSearch Dashboards), который особенно актуален для импортозамещения, о чём ниже. Более лёгкая альтернатива — Grafana Loki, который индексирует не содержимое логов, а только метки, и потому дешевле в хранении.
Главный практический принцип — структурированное логирование в JSON с обязательными полями. Лог без trace_id, service и контекста почти бесполезен при расследовании. В Spring Boot с MDC это выглядит так:
import org.slf4j.MDC
fun handle(orderId: String, traceId: String) {
MDC.put("trace_id", traceId)
MDC.put("order_id", orderId)
try {
log.info("order processing started")
// ...
} catch (e: Exception) {
log.error("order processing failed", e)
} finally {
MDC.clear()
}
}
{
"timestamp": "2026-06-28T16:04:11.482Z",
"level": "ERROR",
"service": "orders-service",
"trace_id": "a1b2c3d4e5f6",
"order_id": "ORD-77123",
"message": "order processing failed",
"exception": "DataAccessResourceFailureException: timeout"
}
Когда у каждой записи есть trace_id, расследование становится механическим: нашли проблемный трейс — отфильтровали по нему логи всех сервисов одним запросом и увидели полную картину события вместо разрозненных фрагментов. Это ровно то, чего не хватает командам, которые «забывают про метаинформацию в логировании», а потом не могут найти причину даже простого 404.
Отдельная дисциплина — управление объёмом: уровни логирования (INFO в проде, DEBUG точечно), retention-политики и сэмплирование, иначе хранилище логов становится самой дорогой частью инфраструктуры.
Трейсинг: OpenTelemetry и Jaeger
Трассировка — третий столп и единственный способ увидеть путь запроса через границы сервисов. Каждый запрос получает trace id, а каждый шаг внутри — span со своими таймингами; контекст пробрасывается между сервисами через HTTP-заголовки или метаданные сообщений Kafka.
Стандарт здесь — OpenTelemetry (OTel). Это важная веха: раньше конкурировали два несовместимых проекта, OpenTracing и OpenCensus, и они окончательно слились в единый стандарт OpenTelemetry. Сегодня это вендор-нейтральный набор API, SDK и протокола (OTLP) для сбора метрик, логов и трейсов сразу. Бэкенд для хранения и визуализации трейсов — чаще всего Jaeger (или Tempo от Grafana).
Большой плюс для JVM — автоинструментирование через java-агент: подключается без правки кода и сам оборачивает Spring MVC, JDBC, Kafka-клиенты, HTTP-вызовы спанами.
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=orders-service \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
-Dotel.metrics.exporter=otlp \
-jar orders-service.jar
В центре архитектуры стоит OpenTelemetry Collector — приёмник, который собирает телеметрию от всех сервисов, обрабатывает (фильтрует, сэмплирует, обогащает) и раздаёт по бэкендам: трейсы в Jaeger, метрики в Prometheus, логи в OpenSearch. Collector развязывает приложения и хранилища: сменить бэкенд можно, не трогая код сервисов.
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 5s
tail_sampling:
policies:
- name: errors-and-slow
type: status_code
status_code: { status_codes: [ERROR] }
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls: { insecure: true }
prometheus:
endpoint: 0.0.0.0:8889
service:
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling, batch]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
Практический нюанс — сэмплирование. Хранить 100% трейсов на высоконагруженной системе дорого и не нужно. Tail-based sampling в Collector решает это разумно: сохраняем все трейсы с ошибками и аномально медленные, а из «здоровых» — небольшую долю для статистики. Так вы не теряете ни одного проблемного запроса при разумных затратах на хранение.
Трейсинг особенно ценен в связке с механизмами устойчивости: когда срабатывает circuit breaker на Resilience4j, именно трейс показывает, какой downstream-вызов начал деградировать и потянул за собой остальную цепочку.
Алертинг и борьба с шумом
Хороший observability-стек легко превратить в генератор шума. Если алертов слишком много, команда перестаёт на них реагировать — это alert fatigue, прямой аналог MFA fatigue, когда люди подтверждают что угодно не глядя. Алерт, на который никто не смотрит, хуже, чем его отсутствие: он создаёт ложное чувство контроля.
Несколько принципов, которые держат алертинг в форме:
- Алертить на симптомы, а не на причины. Алерт «p95 latency оформления заказа > 1с» полезнее, чем десять алертов про CPU отдельных подов. Пользователь чувствует симптом, а не утилизацию.
- Привязывать пороги к SLO и error budget. Алертить не «error rate > 0», а «скорость сжигания бюджета ошибок такая, что месячный SLO будет нарушен» (burn rate alerting). Это резко снижает число ложных срабатываний.
- Разделять срочность. Page (звонок дежурному в 3 часа ночи) — только для того, что требует немедленного вмешательства человека. Остальное — тикет или дашборд, разбираемый в рабочее время.
- Каждый алерт actionable. Если на алерт нет ясного действия — это не алерт, а уведомление; ему не место в канале дежурного.
Для агрегации и маршрутизации в Prometheus-стеке используется Alertmanager: он группирует, подавляет дубли (inhibition), глушит шум на время плановых работ (silences) и направляет уведомления нужной команде. Правило для burn rate выглядит так:
# alert-rules.yml
groups:
- name: slo-burn-rate
rules:
- alert: HighErrorBudgetBurn
expr: |
(sum(rate(http_server_requests_seconds_count{status=~"5.."}[1h]))
/ sum(rate(http_server_requests_seconds_count[1h]))) > (14.4 * 0.001)
for: 5m
labels:
severity: page
annotations:
summary: "Быстрое сжигание error budget на {{ $labels.service }}"
Расследование инцидентов и SRE-практики
Observability имеет смысл лишь тогда, когда поверх неё выстроен процесс. Это территория SRE (Site Reliability Engineering) — инженерного подхода к надёжности, где эксплуатацию решают кодом и метриками, а не героизмом дежурных.
Маршрут расследования инцидента в зрелом стеке выглядит так:
- Сигнал. Сработал алерт по golden signal или горит error budget — не «пользователь написал в поддержку».
- Локализация по метрикам. Дашборд показывает, какой сервис и какой эндпоинт деградируют, когда началось, совпало ли с релизом или всплеском трафика.
- Проваливание в трейсы. По проблемному эндпоинту открываем медленные и ошибочные трейсы — видно, какой именно вызов в цепочке стал узким местом.
- Детали по логам. По
trace_idиз спана достаём связанные логи всех сервисов: точное исключение, параметры запроса, клиент. - Гипотеза и проверка. От симптома к причине: деплой, исчерпание пула, медленный SQL, упавший downstream.
Связка «метрика → трейс → лог по общему trace id» превращает то, что раньше было часами ручного перебора, в минуты направленного поиска. Это и есть практический смысл наблюдаемости.
Организационные SRE-практики, без которых стек не работает:
- Blameless postmortem. Разбор инцидента без поиска виноватых: цель — найти системную причину и устранить класс проблем, а не наказать человека. Люди честно рассказывают, что произошло, только когда за это не бьют.
- On-call с ротацией и эскалацией. Дежурство по графику, разумная нагрузка, чёткая эскалация. Дежурный, которого будят ложными алертами, выгорает за месяц — поэтому борьба с шумом это ещё и забота о людях.
- Error budget как инструмент управления. Исчерпан бюджет — фичи замораживаются в пользу стабилизации. Это снимает вечный конфликт продукта и эксплуатации.
- Runbooks. К каждому критичному алерту — короткая инструкция, что проверить и что сделать. В три часа ночи импровизировать не время.
Поднять и эксплуатировать такой контур — отдельная компетенция. Если своей дежурной команды нет, это закрывается через SRE и дежурство on-call или ИТ-аутсорсинг, когда инфраструктуру наблюдаемости ведёт внешняя команда.
Импортозамещение observability-стека
Хорошая новость для российского enterprise: ядро observability-стека изначально open-source и self-hosted, поэтому оно проще большинства категорий ПО проходит импортозамещение. Зависимости от зарубежной SaaS-аналитики (вроде Datadog или New Relic) здесь можно избежать почти полностью.
Что разворачивается в собственном контуре без внешних провайдеров:
- Метрики — Prometheus и Grafana (open-source-редакции). Важно: брать именно OSS-версии и следить за лицензированием Grafana Enterprise-фич.
- Логи — OpenSearch как открытый форк Elasticsearch (Apache 2.0) либо Grafana Loki. Оба полностью self-hosted.
- Трейсинг — OpenTelemetry (вендор-нейтральный стандарт под эгидой CNCF) плюс Jaeger или Tempo как бэкенд.
Ключевые соображения для контура без внешних зависимостей:
- Данные не покидают периметр. Метрики, логи и трейсы хранятся внутри — это снимает риски с трансграничной передачей и упрощает выполнение требований 152-ФЗ к персональным данным, которые могут попадать в логи.
- Нет единой внешней точки отказа. Аналитика недоступности не должна сама зависеть от внешнего SaaS, который может отключить доступ.
- Реестр отечественного ПО. При жёстких требованиях рассматриваются российские APM/мониторинг-платформы из реестра Минцифры; на практике их часто комбинируют с open-source-ядром.
OpenTelemetry как стандарт здесь играет стратегическую роль: вы инструментируете приложения один раз через вендор-нейтральный API, а бэкенд (хранилище и визуализацию) можете менять без правки кода. Это страховка от привязки к любому поставщику — и зарубежному, и отечественному. Та же логика, что и в общем подходе к импортозамещению корпоративного ПО: минимизировать жёсткие зависимости и держать контроль над данными и стеком.
FAQ
Чем observability отличается от мониторинга простыми словами? Мониторинг отвечает на вопросы, которые вы задали заранее («CPU выше 80%?»). Observability позволяет отвечать на вопросы, которые вы заранее не предвидели («почему именно у этого клиента на этом эндпоинте latency вырос в 20 раз?»), — по логам, метрикам и трейсам, не выкатывая новый релиз ради дополнительного лога.
Нужны ли все три столпа сразу, или можно обойтись логами? В монолите часто хватает логов и нескольких метрик. В распределённой системе нужны все три: метрики показывают, что деградирует, трейсинг — где в цепочке вызовов, логи — что именно произошло в конкретном событии. Сила появляется на их стыке, когда всё связано общим trace id.
В чём разница между SLI, SLO и SLA? SLI — измеряемый показатель (доля быстрых запросов). SLO — ваша внутренняя цель по этому показателю (99,9% за месяц). SLA — внешнее обязательство перед клиентом с санкциями за нарушение. SLA всегда мягче SLO, чтобы у команды был запас.
Обязательно ли хранить 100% трейсов? Нет, и на нагрузке это дорого. Tail-based sampling в OpenTelemetry Collector сохраняет все трейсы с ошибками и аномально медленные, а из «здоровых» берёт небольшую долю для статистики. Так вы не теряете проблемные запросы при разумных затратах на хранение.
Можно ли построить весь стек на отечественном/open-source ПО? Да. Prometheus + Grafana (OSS), OpenSearch или Loki для логов, OpenTelemetry + Jaeger для трейсинга — всё self-hosted и разворачивается в собственном контуре. OpenTelemetry как вендор-нейтральный стандарт позволяет менять бэкенд без правки кода, что снимает привязку к любому поставщику.
Если вы строите распределённую систему на Java/Kotlin и хотите видеть, что в ней происходит, мы в Новакоме проектируем и внедряем observability-контур — от инструментирования сервисов на Micrometer и OpenTelemetry до дашбордов, SLO и дежурства. Посмотрите наши услуги по SRE и on-call, ИТ-аутсорсингу и разработке на Spring или напишите нам с описанием вашего стека.