Если у вас большой сложный продукт, который разрабатывают несколько команд, бывает трудно избежать ситуации, когда продакшен лежит, бизнес стоит, а инженеры несколько часов перекидывают стрелки друг на друга. При этом каждый считает, что проблема на другой стороне. Чтобы найти верное решение, нужен не столько подходящий инструмент, сколько общий подход для мониторинга всех частей приложения.
В этой статье расскажу, как мы объединили несколько разных команд разработки Райффайзен Онлайн общим Observability и с помощью исключительно технических метрик отслеживаем здоровье бизнес-процессов. Как всё это помогает мгновенно находить первопричину сбоя. Как устроен OpenTelemetry и как с его помощью рассчитать доступность приложения в девятках, а также MTTR (Mean Time to Recovery).
Когда мы поняли, что мониторим не то
Пару лет назад один из наших сервисов вдруг стал настолько популярен, что это стало для нас неожиданностью. Приложение не было готово к возросшей нагрузке отчасти из-за техдолга, отчасти из-за первоначально выбранной архитектуры. Частые сбои стали настоящей проблемой.
Чтобы её устранить, нужно было сделать три вещи:
Провести рефакторинг и убрать самые частые причины неполадок.
Внедрить мониторинг, который позволял бы понять, насколько эффективно сработало исправление.
В момент аварии быстро понимать, где именно возникла неисправность.
Рефакторинг оставим разработчикам и поговорим про мониторинг. Он должен был отвечать основным требованиям:
Отражать текущую доступность процесса End-to-End для клиента. То есть не «работает ли приложение X», «может ли клиент совершить операцию Y».
Считать доступность на длительном временном отрезке — те самые «девятки».
Показывать точку отказа — конкретный микросервис, метод или ресурс, который вызвал сбой.
Поскольку наши приложения написаны на Java, мы использовали стандартную связку Micrometer + Prometheus + Grafana. Каждый сервис при этом мониторился отдельно. В нашем распоряжении были десятки дашбордов — слишком много, чтобы это было удобным.
Когда в чат с алертами приходит сообщение, что в микросервисе N с ошибками отваливается 10% запросов, ещё непонятно, как это влияет на возможность совершить платеж. Может быть, это ошибки, не связанные с платежами. Польза для дежурной команды тоже близка к нулю, потому что непонятно, от какого приложения этот микросервис, какая команда за него отвечает и кому звонить в три часа ночи, чтобы быстро устранить проблему. Бывали ситуации, когда на поиск причины и правильных людей уходило несколько часов.
Выбор инструмента для End-to-End мониторинга
Вначале мы пытались снабдить Micrometer дополнительными лейблами. С его помощью даже смогли построить Service Map (оказывается так можно), но это тоже оказалось не информативно. К тому же приходилось менять код во многих приложениях, в том числе в legacy (о божечки), в разных командах и на нескольких версиях Spring Framework. А на уровне всей компании это столько работы, что проект мог затянуться на годы, даже с использованием стартеров.
Трейсинг, Span и сбор метаданных
Мы решили попробовать трейсинг, чтобы понять, что сможем из него почерпнуть для наших целей.
Трейсинг — метод, позволяющий собрать большой объем телеметрии и метаданных о каждом этапе обработки клиентского запроса. В отличие от обычных метрик, которые охватывают только начало и конец обработки запроса, трейсинг предоставляет подробные данные о внутренних вызовах, инициализации классов, SQL-запросах, обработке сообщений и других важных процессах внутри приложения. Особенность трейсинга в том, что он позволяет отслеживать телеметрию через всю цепочку интеграций, которые вызываются в процессе обработки запроса. Поэтому, если у вас сложное приложение, построенное на микросервисной архитектуре, вы сможете отследить всю цепочку. Примерно как на картинке:
А чтобы лучше разобраться в том, что такое трейсинг, надо познакомиться с понятием Span.
Span — это элемент, который показывает телеметрию по минимальному участку обработки запроса в приложении. Вызов rest-контроллера, создание SQL-подключения, отправка сообщения в MQ, в общем, почти любого метода в коде. По каждому такому вызову можно собирать метаданные.
Они включают в себя:
Обобщенное название метода. Здесь это входящий запрос на целиком рестовую ручку "name": "/v1/sys/health". Но это не обязательно имя реста. Как я уже говорил, спаны собираются вообще для всего, и имя может быть самого разного вида.
Уникальные идентификаторы. Это ID самого спана — "span_id" и идентификатор вышестоящего спана, из которого вызван метод — "parent_id" и сквозной идентификатор, одинаковый на всем протяжении обработки клиентского вызова — "trace_id".
Время запуска "start_time" и окончания вызова "end_time".
Результат исполнения, то есть успех/ошибка – "status_code".
Таким образом, трейсинг позволяет собрать все нужные метаданные для мониторинга, который будет подробно отслеживать всё, что происходит внутри сервиса.
Как передается сквозной TraceID
Для передачи сквозного TraceID и других метаданных используется метод передачи контекста — Context Propagation. Есть несколько основных протоколов, которые позволяют это осуществить. В случае OpenTelemetry доступны:
W3C Context (по умолчанию);
X-B3-* - формат Zipkin;
AWS X-Ray;
Jaeger, фактически legacy, так как Jaeger уже перешел на W3C;
OpenTracing — предшественник OpenTelemetry, также legacy.
Важно отметить, что OpenTelemetry — универсальный и открытый стандарт, который объединяет различные инструменты и протоколы.
Как внедряется трейсинг
Мы решили собирать трейсы с помощью Opentelemetry Javaagent из-за простоты внедрения: не нужно вмешиваться в код приложения. В случае Java, чтобы трейсинг заработал и начал собирать данные, достаточно положить рядом jar-файл агента и добавить два параметра JVM через окружение или через аргументы запуска:
$ export JAVA_TOOL_OPTIONS="-javaagent:path/to/opentelemetry-javaagent.jar"
$ export OTEL_SERVICE_NAME="your-service-name"
$ java -jar myapp.jar
Или:
$ java \
-javaagent:path/to/opentelemetry-javaagent.jar \
-Dotel.service.name=your-service-name \
-jar myapp.jar
Java Agent запускается в одном процессе с приложением и переопределяет часть классов, чтобы собирать информацию о запуске, окончании и результате вызова, собирать необходимые метаданные и передавать в систему сбора трейсинга.
Нужно учитывать, что Java Agent предоставляет неограниченный доступ к перехвату вызовов и модификации байт-кода. Поэтому важно использовать этот механизм осторожно. По крайней мере, не стоит запускать агенты от непроверенных разработчиков.
Даже несмотря на то, что OpenTelemetry — это известный и проверенный инструмент, нужно учитывать, что это Open Source-проект, в который активно контрибьютит сообщество. В ответственных средах рекомендуется проводить дополнительные проверки, независимое сканирование кода и отключать инструментации неиспользуемых библиотек.
Как развернуть Java Agent в Kubernetes
Раз уж мы затронули тему развертывания агентов, нельзя не упомянуть, как это можно удобно сделать в Kubernetes. Для этого команда OpenTelemetry разработала оператор. Он автоматически добавляет init-контейнер с Java-агентом и устанавливает необходимые переменные окружения для целевого пода. Для включения этого механизма достаточно добавить аннотацию:
apiVersion: v1
kind: Pod
metadata:
name: myapp
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
...
Такая автоматизация на момент написания статьи существует для самых популярных языков — Java, Python, NodeJS и .NET. Возможно, в скором времени появится автоматическая инструментация и для других языков.
Также оператор предоставляет CRD для быстрого развертывания OpenTelemetry-коллектора в виде деплоймента, daemon-сета или даже sidecar для каждого отдельно взятого пода:
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: simplest-collector
spec:
config: |
receivers:
otlp:
protocols:
http:
processors: [...]
exporters: [...]
OpenTelemetry
Чем примечателен OpenTelemetry среди прочих систем трейсинга?
OpenSource. В современных условиях это особенно важно, потому что многие коммерческие вендоры отказались от поставок ПО в Россию. OpenSource — это сейчас спасение.
CNCF (Cloud Native Computing Foundation). Несмотря на то что проект на стадии incubating, он уже считается пригодным для использования в продакшн-системах.
Поддержка протокола OTLP (OpenTelemetry Protocol). Протокол поддерживается во многих open source и коммерческих системах сбора трейсов. Open Telemetry — это просто агенты для сбора и обработки, а бэкенд для хранения может быть почти любой.
Служба “единого окна”. OTLP позволяет передавать не только трейсы, но также логи и метрики.
Кроме этого, Open Telemetry поддерживает все основные фреймворки и позволяет глубоко детализировать трейсинг.
К примеру, в Open Telemetry есть поддержка следующих фреймворков для Java:
Spring Web (MVC, WebFlux);
MQ: Kafka, ActiveMQ, JMS;
БД: JDBC, Hikari, Hibernate, NoSQL;
Cassandra, Mongo, Redis.
Только для Java поддерживается инструментация более 100 библиотек и фреймворков.
Кроме Java, поддерживается: С++, .Net, Go, Python, JS, PHP, Rust и многие другие.
Таким образом, это достаточно универсальный механизм. Даже если разные сервисы написаны на разных языках, использование сбора трейсов не станет проблемой.
Хранилище
Для того чтобы собирать данные и хранить трейсы, одного агента недостаточно. Ещё нужен бэкенд — большое специальное хранилище или база данных. Трейсов будет много, потому что каждый вызов может генерировать в несколько раз больше данных телеметрии, чем обработал сам сервис.
Вот только несколько из поддерживаемых бэкендов:
Grafana Tempo (traces);
Grafana Loki (logs);
Zipkin (traces);
Jaeger (OTLP);
Sentry (traces);
DataDog (traces, metrics, logs);
Elasticsearch (traces, logs);
Prometheus (metrics).
Важно понимать, что ни одно из хранилищ не лучше и не хуже другого. Выбор хранилища зависит в большей степени от вашей инфраструктуры и существующего ландшафта. Например, если у вас уже есть Grafana Tempo, то нет смысла разворачивать DataDog или Elasticsearch.
В нашем варианте связка такая:
Для изучения возможных вариантов и инструментов для построения пайплайна телеметрии, можно воспользоваться сервисом https://openapm.io/landscape.
Для чего нужен OpenTelemetry Collector
Технически возможно отправлять телеметрию и без него, но коллектор предоставляет возможность делать маршрутизацию и обработку данных. Например, получив данные в одном формате, отправить их в другом:
в системы, которые не поддерживают OTLP;
направить логи, метрики и трейсы в разные системы;
продублировать данные в разные системы — в систему трейсинга и в аналитическую систему.
В коллекторе есть три типа модулей:
Receivers, чтобы принимать данные от агентов.
Exporters, для отправки в хранилища.
Processors, чтобы делать обработку данных.
Примеры Collector Processors
На ресиверах и экспортерах останавливаться не буду, а вот процессоры, как раз самое интересное. Приведу несколько примеров.
Attributes и Transform. Процессоры для редактирования атрибутов могут добавлять атрибуты, удалять их, переименовывать поля для лучшей совместимости. Для того чтобы соотносить логи с трейсами или чтобы было проще искать метрики. Или когда в разных инструментациях по-разному называются поля. Чтобы привести это в единый вид, можно менять и переименовывать атрибуты:
processors:
attributes/accountid:
actions:
- action: insert
key: account_id
value: 224
metricstransform/rename:
transforms:
action: update
include: system.cpu.usage
new_name: system.cpu.usage_time
Filters. Если какое-то приложение отправляет слишком много данных или если по каким-то библиотекам, по каким-то вызовам нам данные собирать не нужно, мы можем их отфильтровать, убрать лишние и уменьшить объём хранилища, которое необходимо для такого сбора.
processors:
filter/regexp_record:
logs:
exclude:
match_type: regexp
record_attributes:
- Key: name
Value: prefix_.*
Routing. Коллектор для маршрутизации (роутинга) позволяет одновременно слать данные в несколько бэкендов. Это могут быть бэкенды в разном формате, в разных местах, для разных целей.
processors:
routing:
from_attribute: X-Tenant
default_exporters:
- jaeger
table:
- value: acme
exporters: [jaeger/acme]
exporters:
jaeger:
endpoint: localhost:14250
jaeger/acme:
endpoint: localhost:24250
SpanMetrics. Очень крутой коллектор, который позволяет делать метрики из трейсов.
...
processors:
spanmetrics:
metrics_exporter: prometheus
dimensions:
- name: http.method
- name: http.status_code
...
Изначально трейсы — это просто цепочка вызовов, но напрямую из этого метрики в моменте не собираются. Какие метрики приложение передало в коллектор, такие и будут отправлены в Prometheus, никакой разницы с Micrometer. Например, в случае с микрометром opentelemetry-javaagent просто отправит те же самые метрики, что и actuator.
Процессор SpanMetrics позволяет сделать метрики из спанов. Для вызовов http мы можем настроить параметр dimensions, чтобы для каждой метрики добавлялись поля, которые берутся из соответствующих метаданных спана.
Как это выглядит в виде prometheus-метрик:
calls_total {http_method="/Address", http_status_code="200", status_code="STATUS_CODE_UNSET" } 142
calls_total {http_method="/Address", http_status_code="500", status_code="STATUS_CODE_ERROR" } 220
latency_bucket {http_method="/Address", http_status_code="200", status_code="STATUS_CODE_UNSET", le="2" } 327
latency_bucket {http_method="/Address", http_status_code="200", status_code="STATUS_CODE_UNSET", le="6" } 751
latency_bucket {http_method="/Address", http_status_code="200", status_code="STATUS_CODE_UNSET", le="10" } 1195
latency_bucket {http_method="/Address", http_status_code="200", status_code="STATUS_CODE_UNSET", le="100"} 10180
latency_bucket {http_method="/Address", http_status_code="200", status_code="STATUS_CODE_UNSET", le="250"} 10180
Это метрики по количеству вызовов, по корзинам с квантилями, по времени обработки вызовов. В метаданных мы можем видеть название метода, результат выполнения — код ответа http, статус вызова метода в коде и время, за которое ответило то или иное количество вызовов.
Collector — варианты дистрибутивов
Коллектор OpenTelemetry доступен в двух вариантах: базовый Opentelemetry-collector и Opentelemetry-collector-contrib.
Opentelemetry-collector содержит только базовые модули ресиверов, экспортеров и процессоров, написанные командой разработки OpenTelemetry.
Opentelemetry-collector-contrib — репозиторий, в котором содержатся community-модули для коллектора. Здесь можно найти всё разнообразие дополнительных модулей, которые написаны сообществом.
Графики по трейсам
Когда пазл более-менее складывается, получаются графики по трейсам. Ниже видим количество запросов, количество ошибок и график по latency на каждый отдельный вызов внутренних методов приложений. Маленькие участки кода мгновенно покрываются метриками, и для этого не нужно вмешиваться в код.
График latency сделан в виде квантиля — набора некоторых порогов (1 сек, 2 сек, 5 сек, 10 сек) и показывает, что 99% вызовов такого-то метода уложились в нужный порог.
Можно также увидеть всплески в определённые моменты. Например, около 10 утра какого-то дня был всплеск на секунду, и 99% запросов исполнились за 1,25 секунды. Притом, что в остальное время большинство запросов исполняется за 250 мс.
Такие графики дают точность и повышают скорость реакции. Например, если даже 2% запросов вышли за пределы допустимого порога, мы это моментально увидим.
Мониторинг и задачи бизнеса
Теперь разберём, как вся проведенная работа соотносится с тем, чего хочет бизнес. Несмотря на то, что с технической стороны пазл собран. У нас есть графики. Для каждого метода известно время выполнения, успехи и ошибки. Задача бизнеса — решить проблему клиента. Но как понять, может ли клиент произвести операцию — совершить платеж, отправить или получить деньги? И если не может, то как это починить?
Приведу упрощенный пример процесса оплаты из пяти шагов:
Открытие формы перевода, ввод суммы.
Клиент открывает в приложении форму, вводит сумму.
Валидация реквизитов и баланса.
На бэкенде валидируются реквизиты, номер телефона получателя, баланс и действия клиента. Например, что он не пытается отправить больше денег, чем у него есть.
Подписание кодом из СМС с одноразовым паролем.
На этом шаге отпускаем клиента с экраном успеха: «Мы успешно зарегистрировали ваш платёж», но платёж ещё не завершен.
Передача информации в СБП (асинхронно).
Подтверждение от СБП, что все реквизиты верные, платёж зарегистрирован корректно.
Только после этого можно списать средства, и операция завершится.
Эти последовательные шаги разделены временем реакции клиента. Он может отвлечься и ввести код из СМС позже. Как при этом быстро определить, может ли клиент сделать платеж? А как определить сбой End-to-End?
Ответ я проиллюстрирую примером «Властелина колец»: Не дойдя до Мордора, кольцо в вулкан не бросишь.
То же самое с платежом! Если платеж не дошел до НСПК (Национальной системы платежных карт) значит, у клиента где-то возникла проблема. И нужно мониторить доступность всего бизнес-процесса по последнему шагу.
Хотя это решение может показаться простым. Без трейсинга это сложно.
Мониторинг бизнес-процесса с плавающей нагрузкой
На первый взгляд, мы можем создать метрики и настроить алерты, которые будут уведомлять нас в Slack или Telegram о проблемах с платежами и аварийных ситуациях, но проблемы плавающей нагрузки это не решает.
Когда клиенты — физические лица, нагрузка всегда плавающая и нетипичная. В понедельник утром клиент будет отправлять больше платежей, чем в новогоднюю ночь. Но если в обеденное время или ночью начинает проходить по 800 платежей в минуту, надо понимать, много это или мало. В обед скорее всего нормально, а ночью — много. А ещё надо найти сбой в таких условиях.
Методика Z-Score
Здесь помогает работа Сары Кассабиан из GitLab, в которой она продемонстрировала методику поиска аномалий в Prometheus по методу стандартных отклонений, также известному как Z-Score.
Суть математической модели в следующем. Берём среднее значение в заданное время суток и в заданный день недели за последние несколько периодов — дней или недель, и сравниваем текущее отклонение от среднего значения с нормальным средним отклонением от того же значения. И отвечаем на вопрос, попадает ли текущее значение в средний диапазон количества запросов для примерно такого же времени в другие недели и дни.
Сара Кассабиан предлагает сделать несколько RewriteRule Prometheus, которые на основе существующих метрик будут по определённым формулам создавать новые метрики и записывать их в TSDB.
- alert: RequestRateOutsideNormalRange
expr: >
abs(
(job:http_requests:rate5m - job:http_requests:rate5m_prediction)
/ job:http_requests:rate5m:stddev_over_time_1w
) > 2
for: 10m
annotations:
summary: Requests for job {{ $labels.job }} are outside of expected operating parameters
- record: rate5m
expr: sum (rate(http_requests_total[5m]))
- record: rate5m:avg_over_time_1w
expr: avg_over_time(job:http_requests:rate5m[1w])
- record: rate5m:stddev_over_time_1w
expr: stddev_over_time(job:http_requests:rate5m[1w])
Подробнее об этих формулах можно прочесть по ссылке:
https://about.gitlab.com/blog/2019/07/23/anomaly-detection-using-prometheus/
Единственный недостаток — эта методика довольно громоздкая и сложная в поддержке. Приходится делать несколько RewriteRule, а необходимые значения вычисляются с помощью огромной формулы.
- record: job:http_requests:rate5m_prediction
expr: >
quantile(0.5,
label_replace(
avg_over_time(job:http_requests:rate5m[4h] offset 166h)
+ job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 1w, "offset", "1w", "", "")
or
label_replace(
avg_over_time(job:http_requests:rate5m[4h] offset 334h)
+ job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 2w, "offset", "2w", "", "")
or
label_replace(
avg_over_time(job:http_requests:rate5m[4h] offset 502h)
+ job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 3w, "offset", "3w", "", "")
)
without (offset)
Это неудобно и может стать проблемой. Потому что если где-то в коэффициентах и формулах допущена ошибка, мы не сможем пересчитать данные за прошедшие периоды. И пройдет много времени, прежде чем вы поймёте, что используете неправильные коэффициенты. В итоге метрики будут ошибочные.
Но я придумал, как это улучшить с помощью MetricsQL.
Оптимизация процесса с MetricsQL
В чистом виде Prometheus сейчас никто не использует. В серьезных продакшен-окружениях требуется внешняя TSDB, а в Prometheus она до сих пор не кластеризуется.
В VictoriaMetrics реализован более развитый язык запросов MetricsQL и в нем есть классная конструкция with:
with (
operationFilters = {service_name=~”my_service_name", operation=~"/rest/my_operation"},
rate5m = sum(irate(calls_total{operationFilters}[$__rate_interval])),
rate5m_err = sum(irate(calls_total{operationFilters, http_status_code=~"${error_codes}"}[$__rate_interval])),
rate5m_predict = quantile(0.25,
label_replace(avg_over_time(rate5m[2h]) offset -1h, "offset", "0d", "", "")
or label_replace(avg_over_time(rate5m[2h]) offset 23h, "offset", "1d", "", "")
or label_replace(avg_over_time(rate5m[2h]) offset 47h, "offset", "2d", "", "")
) without (offset),
rate5m_stddev = quantile(0.5,
label_replace(stddev_over_time(rate5m[2h]) offset -1h, "offset", "0d", "", "")
or label_replace(stddev_over_time(rate5m[2h]) offset 23h, "offset", "1d", "", "")
or label_replace(stddev_over_time(rate5m[2h]) offset 47h, "offset", "2d", "", "")
) without (offset)
)
scalar(abs(avg_over_time(rate5m[3m]) – rate5m[3m]) / rate5m_stddev > bool 0.3)
+ scalar((rate5m_err or vector(0) / rate5m or vector(1)) < bool 0.05) == bool 2
Один раз задать все необходимые формулы и вычисления без RewriteRule и по предварительно рассчитанным данным посчитать метрики за актуальный или предыдущий период, даже если до этого мы ничего не записывали.
scalar(abs(avg_over_time(rate5m[3m]) – rate5m[3m]) / rate5m_stddev > bool 0.3)
+ scalar((rate5m_err or vector(0) / rate5m or vector(1)) < bool 0.05) == bool 2
Кажется, это очень крутой повод поставить внешний TSDB взамен встроенного в Prometheus, если его ещё нет.
Результаты
Во-первых, эта модель позволяет построить единую метрику доступности бизнес-процесса, учитывающую даже небольшие отклонения в любое время суток.
Во-вторых, на базе этой метрики можно создать индикаторы, которые помогут измерять и отслеживать различные аспекты работы приложения. Например, можно определить показатели SLI (Service Level Indicators) для оценки доступности приложения, выраженные в человекопонятных единицах измерения. Таких как время простоя или количество аварий. Продакт-оунеру это и нужно. Получить понятные цифры и статистику, отражающую периоды простоя и сбоев в работе сервиса. Важным показателем является MTTR (Mean Time To Recovery), который показывает среднее время восстановления после сбоев.
Индикаторы SLI и таймлайн
В итоге у нас есть таймлайн и метрики. Что классно, Grafana позволяет прямо на этом таймлайне отследить каждый отдельный сбой и посмотреть, сколько он длился.
Это картина с высоты птичьего полета. Моему шефу очень понравилось.
Расчет MTTR
Расчет MTTR оказался проще, чем я ожидал.
(
count_over_time (
(availability != 1)[90d:10m]
) * 10
) / resets (
(availability == bool 1)[90d:10m]
)
Берём количество сбоев за месяц. То есть переходов от 0 к 1 по нашей метрике доступности и делим на суммарное время, в течение которого сервис лежал, когда показатель доступности не был равен 1.
Поиск причин сбоя
У нас есть единая метрика доступности бизнес-процесса, индикатор SLI по доступности приложения в «девятках», индикатор MTTR. Осталось посчитать причины сбоя. То есть сделать индикаторы, которые покажут, где всё сломалось.
Это важно, потому что, приложение за которое отвечает наша команда — это первая линия взаимодействия с клиентом. Мы обеспечиваем синхронное взаимодействие и синхронный интерфейс. Позади стоит большое количество интеграций, с которыми мы обмениваемся данными в процессе обработки. Ирония в том, что дежурная команда мониторинга при любом сбое чаще всего поднимает среди ночи именно нас, а совсем не тех, кто отвечает за корень проблемы и может эту проблему устранить.
При получении алерта об инциденте почти всегда оказывалось, что проблема была где-то глубже — в интеграции, в БД или даже на сетевом уровне. В итоге мы много раз теряли ценное время. Проходило несколько часов, сервис поднимался. Из-за этого MTTR держался на довольно высоких уровнях, потому что пока «клубок разматывался», проходили часы. Чтобы этого избежать нужен был дашборд, по которому можно быстро найти точку отказа. Тут больше всего пригодилась визуализация трейса, в которой несложно разобраться и понять, какие вызовы интеграции в какой последовательности происходят, даже если разработчик не снабдил часть операций логами. При небольшой помощи аналитиков эти вызовы можно дополнить комментариями на понятном человеку языке, что происходит при валидации платежа.
Анализ интеграций по трейсу
За запрос баланса отвечает Service A, за расчет комиссии — Service B, за проверку на лимиты — Service C. Здесь видны уровни latency и ошибок по запросам. И самое главное, из чего они складываются.
Ещё есть графики, на которых видно не только снижение уровня доступности для конечного пользователя, но и конкретный подзапрос, конкретную интеграцию, которая мешает в конкретный момент.
Разложив все этапы, которые необходимо пройти клиенту, чтобы выполнить платеж, мы получаем понятный дашборд, который можно дать дежурной команде и показать руководителю во время расследования сбоя.
Рефакторинг
Важно учесть один нюанс. В некоторых местах, разбирая трейсы, мы столкнулись с монолитными методами, которые делают сразу несколько обращений к интеграциям, запросы в БД, внутреннюю обработку, и всё это в рамках одного метода. В таких местах нам потребовалось вмешаться в код и сделать рефакторинг, чтобы эти слоёные пироги отражали суть.
Но даже если у вас legacy-приложение, которое нельзя переписать, график всё равно можно построить. Просто формулы будут сложнее. Гораздо проще сделать небольшой рефакторинг, чтобы все формулы собирались однообразно.
Итоги
OpenTelemetry — универсальный и открытый стандарт, который объединяет различные инструменты и протоколы для сбора, экспорта и анализа данных трассировки, метрик и журналов в распределенных системах. Он предназначен для обеспечения наблюдаемости и мониторинга приложений и инфраструктуры. С его помощью можно быстро находить причины аварий.
Разобравшись с техническими вопросами, мы практически без сложностей внедрили OpenTelemetry в множество сервисов команд. Почти везде без вмешательства в код. В итоге достигли целей и получили не только технические преимущества, но и вырастили продуктовые метрики:
Появились индикаторы End-to-End-доступности процессов для клиента, чтобы в любой момент понимать, может ли он совершить платеж.
Здоровый сон обрели сразу нескольких команд, потому что в случае аварий повысилась прозрачность процессов. Сразу видно, кто за что отвечает, кого будить, а кого не нужно.
Снизилась метрика MTTR (Mean Time To Recovery), которая показывает среднее время восстановления после сбоев. Повысилась Availability.
Увеличилось количество наших «девяток» и более счастливых клиентов.
Комментарии (4)
heejew
18.12.2024 07:31Золото, а не статья! Спасибо!
По красивым конечным графикам с SLI, клиентским путём сразу видна польза внедрения, возьму себе на заметку.
Сколько дней сейчас храните трейсы и сколько диска пожирает? Много ли у вас фильтров сделано, чтобы не записывать лишний шум (хелчеки всякие)?
mmorev Автор
18.12.2024 07:31Рад быть полезным) Трейсы храним неделю, но для того чтобы из них делать метрики и SLI, их вообще хранить не обязательно.
По поводу шума особо не заморачивались, spring actuator только отфильтровали, да спаны с нулевыми trace/span id. У хелсчеков околонулевая кардинальность, так что на них можно смело забить, никак не влияют на объем tsdb.
Гораздо больше проблем создают сервисы, в которых в span_name попадают какие-либо uid. В спринге при правильном использовании контроллеров с шаблонами это обезличивается автоматом, а вот в нескольких старых сервисах на Akka - нет, их пришлось полностью исключить из спанметрик, до их замены либо рефакторинга.
MaxLiar
Отличная статья! Спасибо
Тоже на данный момент занимаемся вопросом наблюдаемости и, в частности, трейсингом
Посему вопрос: применяли ли вы семплинг для трейсов? Какой? Использовали ли tail-based вариант семплинга? Если да, то каким образом это организовали
mmorev Автор
Для хранения - да, но самый простой, по процентам, а для генерации spanmetrics - нет, иначе будут искажаться данные по частоте ошибок