Наблюдаемость¶
Три сигнала связаны между собой так, чтобы путь от «что-то не так» до причины занимал минуты, а не полдня: метрика показывает симптом, лог объясняет поведение, трейс показывает, где именно ушло время.

Метрики¶
Приложение отдаёт метрики через Micrometer на /actuator/prometheus. Prometheus
находит поды не по адресам, а через ServiceMonitor — при канареечном релизе
новые поды попадают под наблюдение автоматически.
Три группы метрик, и каждая нужна для своего решения:
Метрики запросов (http_server_requests_seconds) — частота, ошибки,
время ответа. Гистограмма настроена на границы 50 мс, 100 мс, 300 мс и 1 с:
именно 300 мс объявлены целью, и без такой границы доля быстрых ответов
считалась бы приблизительно.
Метрики виртуальной машины Java — заполнение кучи, паузы сборщика мусора, живые данные после сборки. Последняя метрика отличает утечку от пика нагрузки: при пике куча после сборки возвращается к обычному уровню, при утечке — нет.
Прикладные метрики — составы в движении по линиям, открытые и просроченные обращения, время до закрытия обращения, опоздание такта карты. Именно они показывают, работает ли сервис по существу. Приложение может отвечать кодом 200 на все запросы и при этом показывать пустую схему; на такой случай есть отдельное правило оповещения.
Мелочь, которая ломает дашборды
В Prometheus окончания _total и _created зарезервированы, и клиентская
библиотека их срезает. Датчик metroskop.trains.total превращался в
metroskop_trains, а счётчик metroskop.incidents.created — в
metroskop_incidents_total. Дашборд при этом молча показывал «нет данных».
Метрики переименованы в metroskop.trains.running и
metroskop.incidents.registered.
Автомасштабирование по прикладной метрике¶
Загрузка процессора — плохой сигнал для сервиса, который в основном ждёт.
Поэтому prometheus-adapter публикует в API метрик Kubernetes собственную
метрику — число запросов в секунду на под, а HorizontalPodAutoscaler
добавляет реплику, когда на пода приходится больше 25 запросов в секунду.
metrics:
- type: Pods
pods:
metric:
name: http_server_requests_per_second
target:
type: AverageValue
averageValue: "25"
Пределы — от двух до трёх реплик: на узле с 4 ГБ памяти четвёртая реплика вытеснила бы Prometheus. Ограничение честнее, чем красивое число в манифесте, которое кластер не сможет выполнить.
Логи¶
Приложение пишет логи в JSON: агент OpenTelemetry подставляет в контекст
traceId и spanId, и они попадают в отдельные поля, а не в текст сообщения.
Alloy работает как набор агентов на узлах: находит поды, забирает их вывод, добавляет метки пространства имён, пода и контейнера, вытаскивает из JSON уровень записи и отправляет поток в Loki. Уровень становится меткой — по нему можно быстро отобрать ошибки; остальные поля остаются в теле записи, чтобы не разгонять число серий.
В Grafana поле traceId в логе — ссылка. Один щелчок открывает трейс того
самого запроса в Tempo.
Трейсы¶
Трассировка подключена агентом, без единой строки в коде приложения: агент OpenTelemetry добавляется в контейнер на этапе сборки образа и включается переменными окружения. Он сам размечает входящие HTTP-запросы, обращения к базе и пул соединений.
Доля собираемых трейсов — 15 %. На стенде с постоянной синтетической нагрузкой полная выборка стоила бы больше памяти, чем весь сервис, а для поиска медленных запросов этого достаточно.
Дашборды¶
Два дашборда, потому что у них разные читатели.
Цели по качеству — для дежурного: доступность, доля быстрых ответов, скорость прожигания бюджета ошибок в трёх окнах, перцентили времени ответа, куча и сборщик мусора.
Сеть и обращения — про смысл сервиса: составы по линиям, поток обращений, доля закрытых в срок, время закрытия, и рядом — логи приложения из Loki.

Дашборды лежат в репозитории как файлы и попадают в Grafana через сопроводитель, который следит за помеченными конфигурационными картами. Правка руками в интерфейсе живёт до перезапуска — это осознанно: единственный источник правды — репозиторий.