Prometheus: метрики Node.js, p95 и алерты — мастерская и 9 опытов
Prometheus на практике: метрики Node.js, скорость запросов, ошибки, p95, доступность и алерты. Девять опытов на записи настоящего прогона, конструктор YAML и исходники для Windows, Linux и VPS.

Prometheus на практике: метрики Node.js, скорость запросов, ошибки, p95, доступность и алерты. Девять опытов на записи настоящего прогона, конструктор YAML и исходники для Windows, Linux и VPS.
Prometheus: что происходит с сервисом прямо сейчас
API отвечает, но пользователи жалуются на тормоза. После релиза выросло число ошибок, однако в журнале слишком много строк, чтобы быстро оценить масштаб. Один сервер перезапустился, и график количества запросов неожиданно упал. Prometheus помогает превратить такие ситуации в измеримые вопросы: сколько запросов приходит, какая доля завершается ошибкой, как меняется задержка и доступен ли сам источник метрик.
В статье мы соберём настоящий стенд на Node.js: HTTP API, счётчики, gauge, histogram, сбор метрик, запросы PromQL и правила предупреждений. На странице можно изучить девять сценариев, переключать фазу нагрузки и окно расчёта, проигрывать измерения и скачивать результат. Полные исходники позволяют запустить этот же процесс на Windows, Linux или VPS.
Практический результат — не просто красивый график. Вы научитесь отличать замедление от роста трафика, видеть влияние перезапуска, читать p95 и проверять, почему правило ещё pending либо уже firing. Отдельно разберём доступ к панели, стоимость labels, срок хранения, проверку конфигурации и границы того, что метрики действительно доказывают.
Где применять и чем метрики отличаются от логов
Для интернет-магазина полезны скорость оформления заказов, доля ошибок оплаты и длительность обращения к поставщику. Для CRM — поток заявок, время сохранения карточки и количество фоновых задач. Для небольшой внутренней системы уже достаточно нескольких показателей: доступность, скорость запросов, ошибки и задержки. Они помогают заметить проблему до десятков одинаковых сообщений от пользователей.
Метрика хранит числовое измерение во времени. Лог описывает отдельное событие с деталями, а трассировка связывает этапы конкретного запроса между сервисами. Эти инструменты дополняют друг друга: метрики показывают рост доли ошибок, после чего по журналу и ID запроса вы ищете конкретную причину. Не переносите текст исключения или ID клиента в label только потому, что их удобно видеть рядом.
Prometheus не является точным реестром продаж и оплат. Сбор происходит периодически, процессы могут перезапускаться, запросы используют экстраполяцию. Финансовые итоги и юридически значимые записи берите из транзакционной базы. Для диагностики событий на сайте есть отдельная статья о Pino и структурированных журналах Node.js.
Как устроен стенд: приложение отдаёт, Prometheus собирает
Node API обрабатывает запросы и обновляет значения в памяти. Отдельный HTTP-порт отдаёт /metrics. Prometheus раз в пять секунд обращается к нему, сохраняет измерения и вычисляет правила. Браузерный интерфейс Prometheus выполняет PromQL-запросы; наша публичная мастерская разбирает заранее сохранённые ответы такого реального прогона.
HTTP-запрос → Node API → Counter / Gauge / Histogram
↓
/metrics
↓ scrape раз в 5 секунд
Prometheus TSDB
↓
PromQL → графики и правила
Такой способ сбора называется pull: инициатором чтения является Prometheus. Адрес цели задаётся в конфигурации сборщика, а приложение не должно знать URL панели мониторинга. В примере API и метрики разделены по портам, чтобы не открыть служебную информацию вместе с обычными маршрутами приложения. В Compose metrics-порт вообще не публикуется на хост.
Это минимальная архитектура для изучения. В большом проекте появятся несколько экземпляров, discovery, централизованные dashboards и доставка уведомлений. Общий принцип сбора и модель временных рядов описаны в официальном обзоре Prometheus.
Версии: Prometheus 3.16 и новый пакет клиента Node.js
Стенд закреплён на Prometheus 3.16.0 и @prometheus-io/client 0.16.1. Это текущее имя пакета, ранее публиковавшегося как prom-client. Поэтому старые статьи с import из prom-client не следует механически смешивать с новым package-lock. Официальный репозиторий содержит описание перехода и изменений API; в нашем проекте используется один закреплённый клиент.
import client from '@prometheus-io/client';
const registry = new client.Registry();
console.log(await registry.metrics());
Браузерная часть оформлена на Bootstrap 5.3.8, сборка выполняется esbuild 0.28.2. Для проекта нужен Node.js не ниже 24.15; используйте поддерживаемую ветку Node.js 24. Зависимости устанавливаются через npm ci, а не обновляются до случайных последних версий при каждом запуске. Официальный бинарник Prometheus загружается отдельной командой со сверкой SHA-256.
Статическая мастерская и настоящий сервер разделены намеренно: для просмотра записи не надо устанавливать Prometheus. Для своих метрик запускайте API и сборщик из архива. API клиента и история имени пакета доступны в официальном Prometheus client_js.
Мини-мастерская: девять опытов и конструктор конфигурации
Выберите скорость запросов, затем сравните нормальную нагрузку и замедление. Переключитесь на gauge: при той же частоте длительные запросы дольше остаются в работе. В фазе ошибок посмотрите долю 5xx и pending/firing. В фазе остановки сравните up, пропуски измерений и сброс counter после восстановления.
Для rate, increase, доли ошибок, среднего и p95 окно 1m или 5m выбирает соответствующий реально выполненный PromQL-запрос. Counter, gauge, up и ALERTS окно не используют. Выбор участка только ограничивает видимый фрагмент записи. Курсор показывает значения в конкретный момент, а проигрывание передвигает его по сохранённым точкам. Можно скачать текущий JSON, CSV и выражение PromQL; ниже находится редактор job, target, интервала и порога, который создаёт файлы для собственного стенда.
Windows: сначала открыть готовую мастерскую
Для первого знакомства достаточно Node.js и архива. Распакуйте проект, откройте PowerShell в его каталоге и установите закреплённые зависимости. Команда npm.cmd позволяет не зависеть от политики выполнения PowerShell-файла npm.ps1. Пути с пробелами заключайте в кавычки; сам проект не требует ключей сторонних сервисов.
Set-Location 'C:/Projects/prometheus-project'
node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd run serve
Откройте http://127.0.0.1:8190/. Здесь статический сервер отдаёт UI и запись, а не собирает метрики вашей системы. Для завершения нажмите Ctrl+C в этом терминале. Через file:// загрузка JSON и модулей может быть запрещена браузером, поэтому нужен локальный HTTP-адрес.
Проверьте простой маршрут пользователя: выбрать показатель, изменить окно, перейти к фазе ошибок, передвинуть курсор, скачать JSON. В конструкторе намеренно введите неверный адрес с http://: сообщение объяснит ожидаемый hostname:port, а скачивание недействительного конфига отключится. Это полезная проверка интерфейса до запуска серверных компонентов.
Windows: настоящий API, Prometheus и генератор нагрузки
Теперь подготовьте нативный Prometheus. Загрузчик из архива поддерживает Windows и Linux x64: получает официальный релиз, сверяет архив с sha256sums.txt и затем извлекает prometheus и promtool. Для других архитектур возьмите подходящий архив на официальной странице загрузок. Исполняемые файлы не спрятаны внутри браузерного ZIP-пакета.
npm.cmd run setup:prometheus
npm.cmd run check:config
npm.cmd run test:rules
./tools/bin/prometheus.exe --version
В одном терминале выполните npm.cmd start. Во втором запустите сборщик командой ниже. В третьем запускайте профиль нагрузки. API слушает 8290, служебные метрики — 8291, панель Prometheus — 9190; все эти нативные адреса привязаны к loopback. Не запускайте второй стенд на тех же портах.
./tools/bin/prometheus.exe --config.file=config/prometheus.local.yml --storage.tsdb.path=runtime/manual --web.listen-address=127.0.0.1:9190 --storage.tsdb.retention.time=7d --storage.tsdb.retention.size=512MB
npm.cmd run load -- 30 normal
npm.cmd run load -- 30 slow
npm.cmd run load -- 30 errors
Откройте http://127.0.0.1:9190/ и Targets. Дождитесь успешного сбора demo-api, затем выполните запрос из нужного примера. Для rate нужны несколько измерений; сразу после старта пустой ответ ожидаем. При завершении остановите свои процессы Ctrl+C. График статьи продолжает показывать исходную запись и не переключается на локальный сервер автоматически.
Linux и VPS: тот же проект без привязки к Windows
На Linux подготовьте поддерживаемый Node.js 24 и распакуйте проект в каталог обычного пользователя. Команды сборки и тестирования совпадают, меняется имя npm и путь к исполняемому файлу. Каталог TSDB должен быть доступен на запись пользователю Prometheus; не запускайте два процесса с одним storage.tsdb.path. Для теста достаточно домашнего каталога, для постоянной службы удобнее отдельный пользователь и каталог данных.
npm ci --ignore-scripts
npm run setup:prometheus
npm run check:config
npm run test:rules
npm test
npm start
./tools/bin/prometheus --config.file=config/prometheus.local.yml --storage.tsdb.path=runtime/manual --web.listen-address=127.0.0.1:9190 --storage.tsdb.retention.time=7d --storage.tsdb.retention.size=512MB
Оставьте API и сборщик в отдельных сеансах либо запускайте через менеджер служб. Для просмотра панели с рабочего компьютера создайте SSH-туннель. Тогда локальный URL браузера приведёт к закрытому порту на VPS без публикации панели в интернете. Это особенно удобно для первого стенда и диагностики.
ssh -L 9190:127.0.0.1:9190 user@your-vps
Для генерации измерений выполните npm run load — 30 normal на сервере; затем slow и errors. Проверка curl -fsS http://127.0.0.1:8291/metrics покажет текст экспорта. Если порт недоступен, сначала проверьте запущенный процесс и конфиг, а не меняйте firewall на разрешение всего входящего трафика.
Docker Compose: воспроизводимый запуск на Windows и Linux
В архиве есть Dockerfile и compose.yml. Node API запускается отдельным контейнером, Prometheus — официальным образом закреплённой версии. Для Windows понадобится работающий Docker Desktop, для Linux — Docker Engine с Compose plugin. Перед запуском просмотрите итоговый конфиг: так видны адреса, volume и ограничения, которые действительно будут применены.
docker compose -p aen-prometheus config
docker compose -p aen-prometheus up -d --build
docker compose -p aen-prometheus ps
docker compose -p aen-prometheus logs --tail=50 api prometheus
Цель внутри контейнерной сети — api:8291. Адрес 127.0.0.1 внутри контейнера Prometheus указывал бы на него самого, а не на соседний Node API. Поэтому в проекте два небольших конфига: local и docker. API8290 и панель9190 публикуются только на loopback хоста, metrics8291 остаётся внутри сети.
Данные хранятся в именованном volume. Обычный docker compose down останавливает стенд, сохраняя историю; ключ -v удаляет volume, поэтому не используйте его как привычную команду остановки. Для эксперимента с недоступностью остановите только api и затем верните его. Остальные пользовательские контейнеры не затрагиваются.
docker compose -p aen-prometheus stop api
docker compose -p aen-prometheus start api
docker compose -p aen-prometheus down
Метрика и labels: что именно хранится во временном ряду
Имя метрики объясняет измеряемую величину, labels разбивают её на ограниченные категории. В примере счётчик завершённых HTTP-запросов имеет route, method и status. Сочетание имени и всех labels образует отдельный временной ряд. Поэтому два кода ответа или два маршрута — это разные серии, которые затем можно суммировать или сравнивать.
demo_http_requests_total{route="/api/orders",method="GET",status="200"} 145
demo_http_requests_total{route="/api/orders",method="GET",status="500"} 8
Наш сервер использует нормализованные имена /api/orders, /api/report и unmatched. Неизвестный URL не превращается в новый label с содержимым пользовательского пути. Так запрос с уникальным ID не создаёт отдельный временной ряд и не раскрывает этот ID в экспорте. Существующие route/status комбинации инициализированы нулями, чтобы отсутствие ошибок не выглядело как случайное отсутствие серии.
Практическое правило простое: измерение должно отвечать на вопрос, по которому вы будете принимать действие. До добавления label подумайте, какие значения он может принимать и насколько их число ограничено. Имя клиента, полный URL, случайный ID и текст исключения обычно относятся к журналам, а не к метрикам.
Опыт 1. Counter и перезапуск приложения
Counter — накопительное число событий. В нашем API счётчик увеличивается после завершения ответа. Пока процесс работает, он растёт; после перезапуска новый процесс начинает с нуля. Это нормальное поведение счётчика в памяти, а не исчезновение всех запросов из истории Prometheus. Перейдите в мастерской к восстановлению и сравните counter до и после остановки.
const requests = new client.Counter({
name: 'demo_http_requests_total',
help: 'Completed demo HTTP requests',
labelNames: ['route', 'method', 'status'],
registers: [registry]
});
requests.inc({ route: '/api/orders', method: 'GET', status: '200' });
sum(demo_http_requests_total{job="demo-api"})
Само накопленное число редко является лучшим показателем текущей нагрузки. Процесс, работающий неделю, почти всегда имеет больше событий, чем только что запущенный экземпляр. Для сравнения текущей активности используйте скорость или прирост за окно. Не вычитайте вручную две соседние точки без учёта сбросов: после рестарта получится отрицательный результат, который не описывает реальные события.
Опыт 2. Rate: скорость запросов и скользящее окно
Rate оценивает среднюю скорость увеличения counter в секунду на выбранном окне. В мастерской можно сравнить одну и пять минут. Короткое окно быстрее показывает изменение, но сильнее реагирует на отдельные колебания. Длинное сглаживает график и дольше помнит прежнюю нагрузку. Ни один из вариантов не является автоматически правильным для всех предупреждений.
sum(rate(demo_http_requests_total{job="demo-api"}[1m]))
Сначала rate вычисляется для каждого исходного ряда, затем результаты складываются. Такой порядок сохраняет возможность учитывать сбросы отдельных процессов. Если сначала сложить счётчики разных экземпляров, рестарт одного может скрыться ростом другого. Для разделения маршрутов используйте sum by (route), сохраняя тот же порядок операций.
sum by (route) (rate(demo_http_requests_total{job="demo-api"}[5m]))
Окно — это история перед каждой точкой, а не длина выбранного на экране участка. В нашей четырёхминутной записи пятиминутный запрос ещё не имеет полного пятиминутного прогрева; он использует доступную историю с запуска. В реальном сервисе дайте сборщику накопить достаточно данных, прежде чем сравнивать устойчивые значения. Формальное поведение функций описано в справочнике PromQL.
Опыт 3. Increase: сколько событий добавилось за интервал
Increase удобно читать как оценку количества новых событий за окно. Для панели это может быть число запросов за пять минут или количество ошибок за час. В отличие от rate результат выражен в событиях за окно, а не в событиях в секунду. Поэтому при смене длительности окна число может увеличиться даже при одинаковой нагрузке.
sum(increase(demo_http_requests_total{job="demo-api"}[5m]))
Вы можете увидеть дробное значение, хотя counter увеличивается целыми числами. Это связано с оценкой между периодическими измерениями и границами окна. Не округляйте его внутри рассуждения до «точно столько было заказов»: Prometheus не сохраняет каждое бизнес-событие. Для удобства отображения можно выбрать число знаков, сохраняя исходный результат в экспорте.
Полезный эксперимент — остановить API между scrape и сравнить накопленный counter, rate и increase. Они отвечают на разные вопросы, поэтому не обязаны выглядеть одинаково. В документации собственной панели подписывайте единицы и окно рядом с числом: надпись «запросы» без указания периода слишком легко вводит человека в заблуждение.
Опыт 4. Доля ошибок: почему одного числа 500 недостаточно
Десять ошибок при десяти запросах и десять ошибок при миллионе запросов описывают разные ситуации. Для оценки качества ответа полезно делить скорость серверных ошибок на скорость всех завершённых запросов. В записи фаза errors возвращает 500 примерно для каждого третьего запроса; на графике видно постепенное заполнение выбранного окна такими событиями.
sum(rate(demo_http_requests_total{job="demo-api",status=~"5.."}[1m]))
/
sum(rate(demo_http_requests_total{job="demo-api"}[1m]))
Результат — доля от нуля до единицы: 0.1 соответствует десяти процентам. При отсутствии трафика знаменатель может быть нулевым, и NaN не следует подменять благополучными 0%. Наша таблица показывает отсутствие точки, а график оставляет разрыв. Это сохраняет различие между «ошибок не было» и «нет данных для оценки».
Правило предупреждения дополнительно требует поток больше одного запроса в секунду. Это защищает учебный пример от реакции на единичный запрос, но для малонагруженного сервиса такой порог может скрыть проблему. Выбирайте минимальный объём трафика по своему процессу, а недоступность проверяйте отдельным правилом. HTTP 4xx и 5xx также стоит анализировать раздельно: их причины и ответственные действия обычно различаются.
Опыт 5. Gauge: сколько запросов ещё находится в работе
Gauge может увеличиваться и уменьшаться. В примере он показывает число одновременно выполняющихся HTTP-запросов. При старте обработки значение увеличивается, при завершении или закрытии соединения — уменьшается. В отличие от counter это состояние на момент измерения, а не накопленная история всех обращений.
const inFlight = new client.Gauge({
name: 'demo_http_in_flight',
help: 'Requests currently executing',
registers: [registry]
});
inFlight.inc();
try { await handleOperation(); } finally { inFlight.dec(); }
В фазе slow длительность работы растёт примерно с двадцати до четырёхсот пятидесяти миллисекунд. Генератор продолжает отправлять запросы, и несколько из них оказываются в обработке одновременно. Так график помогает увидеть накопление работы, даже если число завершённых ошибок ещё равно нулю. Для очереди похожим показателем будет количество ожидающих задач.
Scrape раз в пять секунд — отдельные снимки состояния. Короткий всплеск между ними может полностью исчезнуть из графика. Поэтому отсутствие пика gauge не доказывает, что пиков никогда не было. Для оценки распределения длительностей нужны другие метрики, а для очереди стоит смотреть также возраст самой старой задачи и скорость завершения.
Histogram: как измерить длительности без хранения каждого запроса
Histogram накапливает распределение наблюдений по границам buckets. Мы измеряем серверное время от начала обработки до завершения ответа в секундах. В наборе есть границы 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1 и 2 секунды. Эти интервалы подобраны для учебных быстрых и медленных запросов, а не являются универсальным стандартом для любого API.
const duration = new client.Histogram({
name: 'demo_http_request_duration_seconds',
help: 'Demo HTTP request duration in seconds',
labelNames: ['route', 'method'],
buckets: [.01, .025, .05, .1, .25, .5, 1, 2],
registers: [registry]
});
duration.observe({ route: '/api/orders', method: 'GET' }, 0.12);
В classic histogram экспортируются bucket-счётчики, сумма длительностей и количество наблюдений. Buckets накопительные: наблюдение 0.12 попадает в границы 0.25 и все большие. Это важно при чтении сырого /metrics: значения соседних buckets нельзя интерпретировать как независимые непересекающиеся столбцы обычной диаграммы.
В современном Prometheus есть native histograms. Они имеют другую структуру и возможности, поэтому при внедрении стоит проверить поддержку всей цепочки. Наш стенд сознательно использует classic histogram из закреплённого Node-клиента и явные buckets, чтобы запросы и результат можно было разобрать по строкам. Это не утверждение, что classic — предпочтительный вариант для любого нового проекта.
Опыт 6. Средняя задержка: сумма времени делится на число запросов
Среднее удобно для общей оценки нагрузки, но оно способно скрывать медленный хвост. Если большая часть запросов быстрая, небольшое число тяжёлых операций может почти не изменить итоговое среднее. Поэтому в мастерской есть отдельные сценарии среднего и p95, которые полезно смотреть на одном переходе нагрузки.
sum(rate(demo_http_request_duration_seconds_sum{job="demo-api"}[1m]))
/
sum(rate(demo_http_request_duration_seconds_count{job="demo-api"}[1m]))
Числитель описывает накопление секунд, знаменатель — накопление наблюдений. Их отношение снова измеряется в секундах на запрос. Сначала складываются суммы и количества всех нужных экземпляров, а не готовые средние каждого процесса. Простое среднее нескольких средних даст неверный вес процессу, который обслужил мало запросов.
Наш таймер измеряет обработку внутри Node API. Он не включает путь по сети от посетителя, DNS, отрисовку интерфейса и загрузку других ресурсов. Поэтому метрика может быть хорошей, хотя страница всё ещё открывается медленно. Подписывайте границу измерения в dashboard и добавляйте клиентские измерения отдельно, если задача касается полного пользовательского опыта.
Опыт 7. P95: медленный хвост и точность buckets
P95 — оценка длительности, ниже которой находится примерно девяносто пять процентов наблюдений выбранного распределения. Она помогает увидеть хвост, который теряется в среднем. В нашем стенде значение рассчитывается из classic histogram: Prometheus оценивает положение внутри bucket, поэтому результат зависит от выбранных границ.
histogram_quantile(0.95,
sum by (le) (
rate(demo_http_request_duration_seconds_bucket{job="demo-api"}[1m])
)
)
При задержке 450 миллисекунд p95 не обязан равняться точно 0.45. Большая часть наблюдений оказывается внутри интервала до 0.5 секунды, а алгоритм оценивает положение квантиля по накопленным значениям. Если важен порог 300 миллисекунд, полезно иметь границу bucket около него. Слишком грубые buckets не превращаются в точные измерения от увеличения числа знаков после запятой.
Для нескольких экземпляров складывайте bucket rates с сохранением le, а затем вычисляйте квантиль. Усреднение отдельных p95 не даёт p95 объединённого трафика. Устройство и ограничения разных типов распределений описаны в документации histograms и summaries.
Опыт 8. Up: успешный сбор ещё не означает здоровый бизнес-процесс
Up создаёт сам Prometheus. Значение один означает успешное получение метрик цели, ноль — неудачный scrape. В фазе down наш Node API действительно остановлен, поэтому сбор перестаёт проходить. После запуска новый процесс снова отдаёт метрики, а counter начинается с нуля. Это можно сравнить на одной временной шкале.
up{job="demo-api"}
Up не проверяет оформление заказа или корректность ответа поставщика. Приложение может успешно отдавать /metrics и при этом возвращать 500 на основной маршрут. Возможна и обратная ситуация: API работает, но неверный путь, timeout или сеть мешают сбору метрик. Сначала прочитайте ошибку цели в Targets, затем проверьте соответствующий путь из окружения самого Prometheus.
Отсутствующая серия up и серия со значением ноль также различаются. Удалённая из конфигурации цель может перестать существовать в данных вместо продолжения нулей. Для нужных гарантий мониторинга учитывайте отсутствие серии и саму доступность сборщика. Одно правило up == 0 не охватывает все варианты потери наблюдаемости.
Опыт 9. Pending и firing: условие должно продержаться
Предупреждение создаётся выражением PromQL. Если условие выполняется, правило становится активным; параметр for требует, чтобы состояние сохранялось непрерывно заданное время. До истечения этого периода вы видите pending, после — firing. Если условие исчезло раньше, короткий всплеск не превращается в устойчивое предупреждение.
groups:
- name: demo-api
rules:
- alert: DemoTargetDown
expr: up{job="demo-api"} == 0
for: 10s
labels:
severity: critical
annotations:
summary: Demo API scrape failed
В мастерской состояния взяты из реальных ALERTS и снимков API Prometheus. Можно выбрать остановку либо ошибки, передвинуть курсор и сравнить pending с firing. Короткие 10–15 секунд выбраны для четырёхминутного учебного опыта. В рабочей системе время ожидания определяется допустимым временем реакции и шумом измерений, а не удобством демонстрации.
Scrape interval, evaluation interval и for — разные параметры. Первый отвечает за сбор, второй за вычисление правил, третий за непрерывность условия. Поэтому переход не обязан случаться ровно в момент изменения API. Подробная семантика доступна в описании alerting rules.
Firing и уведомление: где появляется Alertmanager
Само состояние firing не отправляет письмо, сообщение в чат или звонок. Для доставки нужна отдельная система: обычно Alertmanager принимает события Prometheus, группирует их, подавляет повторения и направляет в настроенные каналы. В нашем минимальном стенде Alertmanager отсутствует, а уведомления не отправляются — это прямо обозначено рядом с мастерской.
При внедрении сначала определите получателя и действие. Полезное предупреждение сообщает, какой сервис затронут, какова проблема и где находится инструкция проверки. Если каждое незначительное колебание приходит всем сотрудникам, сообщения быстро перестают читать. Разные уровни серьёзности должны соответствовать разным способам реакции, а не просто разным цветам.
labels:
severity: warning
service: orders
annotations:
summary: Orders API error ratio above threshold
runbook_url: https://internal.example/runbooks/orders-errors
Это пример полей правила, а не готовый публичный адрес инструкции. Создайте собственный runbook: проверить Targets, долю ошибок, текущий релиз и журнал по request ID. Затем отдельно проверьте доставку уведомления тестовым событием. Успешное вычисление PromQL и фактическое получение сообщения — разные этапы приёмки.
Recording rules: дать повторяемому расчёту понятное имя
Одинаковое длинное выражение нередко используется в нескольких панелях и правилах. Recording rule сохраняет результат вычисления как новую временную серию, которую затем можно запросить по имени. Это помогает согласовать формулу и уменьшить повторение сложных выражений. В нашем файле rules.yml есть правило для суммарной скорости запросов за минуту.
groups:
- name: demo-api
interval: 5s
rules:
- record: demo:http_requests:rate1m
expr: sum(rate(demo_http_requests_total{job="demo-api"}[1m]))
Новое имя должно сообщать смысл вычисления и окно. Не называйте минутную скорость просто total: человек может принять её за накопительный счётчик. При изменении формулы подумайте о совместимости существующих dashboards и предупреждений. Иногда правильнее создать новую серию с новым именем, чем незаметно изменить значение старой.
Recording rule не восстанавливает прошлые данные автоматически после добавления. История начинается с вычисления правила, если вы отдельно не выполняете backfill. Для учебного стенда важно хотя бы увидеть, что правило валидно и отдаёт ожидаемую скорость при заданном входе. В рабочем окружении добавьте наблюдение за длительностью вычисления и ошибками группы правил.
Promtool: проверяем синтаксис и поведение до применения
Валидный YAML ещё не означает правильное предупреждение. Можно ошибиться именем метрики, labels, единицами или временем for. Promtool проверяет конфигурацию и позволяет выполнить тесты правил на заданных временных рядах. Это особенно полезно, когда правило должно молчать при норме и срабатывать только после устойчивого превышения.
npm run check:config
npm run test:rules
./tools/bin/promtool check rules config/rules.yml
В Windows используйте npm.cmd и promtool.exe. В архиве тесты проверяют отсутствие раннего firing, появление предупреждения после for, нормальный поток без ошибок и скорость десять запросов в секунду. В отличие от четырёхминутной записи эти тесты быстрые и воспроизводимые: время и входные измерения задаются явно.
- series: 'demo_http_requests_total{job="demo-api",status="200"}'
values: '0+40x36'
- series: 'demo_http_requests_total{job="demo-api",status="500"}'
values: '0+10x36'
Такая запись описывает регулярное увеличение счётчиков, а не реальные сетевые запросы. В проекте есть оба слоя: модульная проверка правил и сохранённый настоящий прогон. Тест синтаксиса не подтверждает, что конкретная цель в production доступна и отдаёт нужные labels. Формат тестовых рядов разобран в официальном руководстве promtool.
Конструктор YAML: перенести пример на свой сервис
В нижней части мастерской задаются имя job, hostname:port цели, интервал сбора, доля ошибок и время for. После проверки значений можно скачать prometheus.yml и rules.yml. Поместите их в один каталог и запустите promtool. Конструктор не подключается к цели, не проверяет доступность DNS и не применяет конфигурацию на каком-либо сервере.
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- rules.yml
scrape_configs:
- job_name: demo-api
scrape_timeout: 3s
static_configs:
- targets: ["api:8291"]
Для нативного стенда на одной машине замените api:8291 на 127.0.0.1:8291; для Compose имя api разрешается внутри его сети. Ввод ограничен понятным форматом hostname:port, IPv6 в этом небольшом редакторе не поддерживается. Если вам нужны TLS, аутентификация или discovery, добавьте соответствующие разделы по официальной конфигурации и проверьте их отдельно.
Шаблон конструктора использует окно 5m и защиту от потока меньше одного запроса в секунду. Файл rules.yml записанного опыта использует 1m и короткие интервалы для наглядности. Это два явно разных назначения. Не копируйте учебный порог в production, пока не сравнили его с реальным трафиком и временем реакции команды.
Сырой /metrics и HTTP API: проверить данные до графика
Если линия выглядит странно, полезно пройти цепочку снизу вверх. Сначала откройте /metrics и убедитесь, что приложение вообще отдаёт нужное имя и labels. Затем проверьте Targets в Prometheus. Только после этого разбирайте выражение и диапазон времени. Иначе можно долго менять PromQL, хотя сборщик обращается к неправильному порту.
curl -fsS http://127.0.0.1:8291/metrics
curl -fsS -G http://127.0.0.1:9190/api/v1/query --data-urlencode 'query=up{job="demo-api"}'
В PowerShell используйте curl.exe, чтобы избежать неоднозначности имени команды в старых окружениях. HTTP API Prometheus возвращает статус и тип результата. Мастерская сохраняет ответы query_range с типом matrix, а не вычисляет выражения своим JavaScript-эмулятором. Поэтому скачанный JSON позволяет увидеть исходные точки, labels и особые значения вроде NaN.
Экспорт CSV добавляет UTC-время и корректно заключает поля в кавычки. Текстовые значения с опасными для табличных редакторов префиксами нейтрализуются, чтобы открытие CSV не превращало содержимое в формулу. Это поведение экспортёра, а не функция Prometheus. При импорте в другую систему сохраняйте единицы и выражение запроса рядом с таблицей.
Как записан опыт и как повторить его самостоятельно
Сценарий capture длится около четырёх минут: нормальная нагрузка, замедление, ошибки, остановка и восстановление. Генератор делает настоящие HTTP-запросы, Node обновляет реальные метрики, Prometheus выполняет запросы для девяти сценариев и двух окон. Сохраняются также снимки алертов, сырой экспорт и хеши конфигурации. Число 2031 относится к включённому прогону; новый запуск может дать немного другое количество.
npm run setup:prometheus
npm run capture
npm run build
npm test
Перед новым capture сохраните копию artifacts, если нужен исходный пример. Команда перезапишет запись и результаты в этом каталоге. Ручной стенд на портах 8290, 8291 и 9190 предварительно остановите; проверка свободных портов завершит capture с понятной ошибкой при конфликте. Скрипт не должен подключаться к чужому случайно запущенному сервису вместо своего.
Каждая запись получает новый каталог TSDB в runtime. Собственные дочерние процессы capture завершаются после работы; существующие пользовательские процессы он не останавливает. После обновления записи соберите public заново. Времена и величины в статье описывают учебные условия, а не прогноз производительности вашего сервера.
Измеряем завершение запроса ровно один раз
Наивный обработчик может увеличить counter при начале запроса и записать успешный код, хотя соединение затем оборвалось. В исходном API учёт привязан к завершению ответа, а закрытие незавершённого соединения обрабатывается отдельно. Флаг completed защищает от двойного уменьшения gauge и повторного наблюдения длительности.
let completed = false;
function finish() {
if (completed) return;
completed = true;
inFlight.dec();
requests.inc({ route, method: 'GET', status: String(res.statusCode) });
duration.observe({ route, method: 'GET' }, (performance.now() - started) / 1000);
}
res.once('finish', finish);
res.once('close', () => {
if (!res.writableFinished) { res.statusCode = 499; finish(); }
});
Код 499 здесь — внутреннее соглашение примера для оборванного запроса, а не ответ, гарантированно полученный клиентом. В рабочем приложении можно выбрать отдельный label outcome с ограниченными значениями. Важно документировать смысл, чтобы оператор не принял внутреннее состояние за обычный HTTP-ответ сервера.
Маршрут /health в этом стенде не включён в бизнес-счётчик, а /metrics обслуживается отдельным listener. Благодаря этому сам мониторинг не создаёт ложный пользовательский трафик. При переносе в Express, Fastify или другой framework расположите middleware так, чтобы измерение покрывало нужную часть обработки и не считало один ответ дважды.
Постоянная работа на Linux: systemd и отдельные каталоги
Для VPS без Docker можно оформить два сервиса systemd: Node API и Prometheus. Подготовьте выделенного пользователя без интерактивного входа, каталог кода /opt/aen-monitor и отдельный каталог данных /var/lib/aen-prometheus. Код и конфигурация читаются сервисом, данные доступны ему на запись. Путь к Node уточните командой command -v node: путь из nvm интерактивного пользователя не стоит переносить в unit без проверки.
[Unit]
Description=AEN Prometheus demo collector
After=network-online.target
Wants=network-online.target
[Service]
User=aen-monitor
Group=aen-monitor
WorkingDirectory=/opt/aen-monitor
ExecStart=/usr/local/bin/prometheus --config.file=/opt/aen-monitor/config/prometheus.local.yml --storage.tsdb.path=/var/lib/aen-prometheus --web.listen-address=127.0.0.1:9190 --storage.tsdb.retention.time=7d --storage.tsdb.retention.size=512MB
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aen-prometheus
[Install]
WantedBy=multi-user.target
Это шаблон, требующий созданных пользователя, каталогов и установленного бинарника по указанному пути. Для Node unit используйте того же выделенного пользователя, WorkingDirectory проекта и ExecStart с абсолютным путём к node и src/server.mjs. Он слушает loopback по умолчанию. Затем проверьте unit, загрузите конфигурацию systemd и изучите журнал запуска.
sudo systemd-analyze verify /etc/systemd/system/aen-prometheus.service
sudo systemctl daemon-reload
sudo systemctl enable --now aen-prometheus
sudo journalctl -u aen-prometheus -n 50 --no-pager
Автоматический restart помогает после сбоя процесса, но не заменяет исправление неверного конфига или заполненного диска. После обновления сначала выполните promtool и проверьте права каталогов. Для отката сохраняйте предыдущую конфигурацию и версию бинарника; не экспериментируйте с форматом TSDB на единственной рабочей копии данных.
Доступ к метрикам: приватная панель и понятные границы
В метриках могут быть имена сервисов, маршруты, детали нагрузки и внутренние labels. Панель умеет выполнять дорогие запросы и читать историю. Поэтому нативный стенд и опубликованные Compose-порты ограничены loopback. Для администратора на VPS используйте SSH-туннель либо отдельно настроенный защищённый доступ с аутентификацией и ограничением сети.
Наш публичный сайт раздаёт только статическую мастерскую с учебной записью. Он не открывает посетителю рабочую TSDB, не выполняет его произвольный PromQL на сервере и не собирает метрики адреса из конструктора. Такая граница позволяет показать реальные данные и код без предоставления публичного доступа к внутреннему мониторингу.
Для нескольких пользователей продумайте доступ к dashboards отдельно от доступа к административным endpoints. Не добавляйте секреты, токены и персональные данные в labels. При reverse proxy сохраняйте ограничения служебных маршрутов, а не проксируйте все пути по умолчанию. Исходные предположения о доверии и HTTP-доступе описаны в модели безопасности Prometheus.
Хранение и cardinality: почему набор labels важнее красоты графика
Количество рядов растёт вместе с комбинациями labels. Десять маршрутов, три метода, несколько статусов и множество экземпляров уже дают заметный набор; уникальный ID заказа делает его практически неограниченным. Classic histogram добавляет отдельные ряды для buckets, count и sum. Поэтому стоимость нового label оценивают до его публикации.
Учебный Compose ограничивает retention семью днями и размером 512MB. Эти настройки предназначены для маленького стенда, а не для вашего production по умолчанию. Помимо хранения блоков нужен запас под WAL, активные данные и служебные операции; retention.size не следует понимать как абсолютно точный предел общего размера каталога на каждом мгновении.
--storage.tsdb.retention.time=7d
--storage.tsdb.retention.size=512MB
Следите за свободным местом, количеством активных рядов и временем запросов. Если история нужна дольше, сначала оцените объём и стратегию хранения, а не просто увеличивайте срок до бесконечности. Prometheus предназначен для мониторинга, а источник точных бизнес-операций остаётся в приложении. Перед удалением volume убедитесь, что именно эту историю вы действительно готовы потерять.
Типовые ошибки: пустой график, странный p95 и молчащий алерт
Пустой график чаще всего требует проверки времени, наличия рядов и labels. После старта rate ещё может не иметь достаточных точек. В Docker адрес localhost указывает на текущий контейнер. Ошибка MIME относится к статической мастерской, а ошибка Targets — к сбору метрик: эти уровни нужно проверять отдельно, иначе действия становятся случайными.
Необычный p95 проверьте по единицам и buckets. Если duration наблюдается в миллисекундах, а имя обещает секунды, запрос даст число в тысячу раз больше ожидаемого. Если алерт pending, посмотрите for и непрерывность условия; если firing не приходит в почту, проверьте отдельный маршрут доставки. Если трафик слишком мал, дополнительное условие минимальной скорости может намеренно удерживать правило.
up{job="demo-api"}
count(demo_http_requests_total{job="demo-api"})
sum by (status) (rate(demo_http_requests_total{job="demo-api"}[1m]))
При изменениях двигайтесь от простого запроса к сложному: имя метрики, затем фильтр labels, затем rate, агрегирование и отношение. Скачанный из мастерской PromQL удобен как исходный пример, но не подтверждает совпадение имён вашего приложения. Сохраните удачные запросы и проверяйте их после смены инструментации.
Проверка интерфейса, исходники и следующие практические шаги
Мастерская использует обычные подписанные поля, кнопки и шкалу времени. На узкой ширине блоки располагаются последовательно, график масштабируется, а таблица сохраняет читаемые числа. Пустые точки остаются пустыми, время явно указано в UTC. По умолчанию выбирается первая конечная точка, а «К началу» возвращает начало участка, где данных ещё может не быть.
В исходниках есть тест реального HTTP API, проверка ограниченных labels, histogram и gauge, тесты PromQL-правил и DOM-регрессия интерфейса. Последняя проверяет смену окна и фазы, неправильный конфиг, ошибку загрузки записи и успешный повтор. Эти проверки дополняют просмотр страницы в браузере; они не заменяют проверку мобильного экрана и скачивания файлов на фактическом хостинге.
Начните внедрение с одного сервиса и четырёх понятных вопросов: сколько работы приходит, сколько завершается ошибкой, сколько занимает времени и доступен ли сбор метрик. Сопоставьте графики с известным изменением нагрузки, затем добавьте правило и инструкцию реакции. После этого расширяйте мониторинг очередей, базы и внешних API по реальным задачам.


