Как собрать метрики Node.js приложений в PM2 с экспортом в Prometheus
Ни для кого не секрет что для устойчивой и надежной работы node.js приложений необходимо проводить мониторинг их работы и делать полезные выводы глядя на их метрики. Это означает, что вы способны получать информацию о состоянии до возникновения проблем, таким образом, предотвращая сбои.
В этой статье я хотел бы рассказать о способе сбора статистики из node.js приложений, которые запущены в PM2, и экспорт этих данных в Prometheus.
Когда вы просто запускаете node.js приложение через команду node
или же через PM2 командой pm2 start app.js
, то новое приложение поднимается в единственном экземпляре. Сбор и экспорт каких либо метрик в этом случае будет не затруднительным. Для этого устанавливаем пакет prom-client и добавляем нужные нам метрики, например, количество обращений к нашему приложению.
import client from 'prom-client';
import { createServer } from 'http';
const registry = new client.Registry();
const PREFIX = `nodejs_app_`;
export const metricRequestsTotal = new client.Counter({
name: `${PREFIX}request_counter`,
help: 'Show total request count',
registers: [registry],
});
// Старт вашего приложения
// ...
nodejsapp.get('/*', async (req, res) => {
metricRequestsTotal.inc();
});
// Запуск сервера для отдачи метрик
const promServer = createServer(async (req, res) => {
res.setHeader('Content-Type', registry.contentType);
res.end(await registry.metrics());
return;
});
promServer.listen(9100, () =>
console.log('Prom server started')
);
В данном примере запускается ваше приложение и вместе с ним стартует экспорт метрик на дополнительном порту 9100
. Можно использовать какой-то отдельный URL и на основном порту приложения, но он не должен быть публичным для пользователей.
Такой способ будет работать до тех пор, пока вам не нужно будет запустить несколько инстансов приложения в режиме кластера. Для сбора метрик в таком режиме у prom-client есть хороший пример, где происходит агрегация метрик. Но как быть, если вы запускаете приложение через менеджер процессов PM2 в котором у вас нет доступа к запущенному кластеру?
В одной из моих предыдущих статей я рассказал о модуле pm2-prom-module который позволял собирать общую статистику по приложениям из PM2, но не мог собирать внутреннюю статистику из каждого приложения. Теперь же, начиная с версии 2.0, такого ограничения нет и вся статистика из PM2 и внутри приложения отдается через один модуль.
Для обмена данными между приложением nodejs и pm2-prom-module необходимо установить npm пакет pm2-prom-module-client в ваше приложение. В итоге пример выше будет выглядеть вот таким образом:
import client from 'prom-client';
import { initMetrics } from 'pm2-prom-module-client';
const registry = new client.Registry();
const PREFIX = `nodejs_app_`;
const metricRequestCounter = new client.Counter({
name: `${PREFIX}request_counter`,
help: 'Show total request count',
registers: [registry],
});
// Регистрируем registry для отправки данных в модуль
initMetrics(registry);
// ...
nodejsapp.get('/*', async (req, res) => {
// ...
metricRequestCounter?.inc();
// ...
});
Теперь pm2-prom-module будет отдавать не только PM2 статистику, но и внутреннюю статистику из ваших приложений.
/// Общая статистика PM2
# HELP pm2_free_memory Show available host free memory
# TYPE pm2_free_memory gauge
pm2_free_memory{serviceName="my-app"} 377147392
# HELP pm2_cpu_count Show available CPUs count
# TYPE pm2_cpu_count gauge
pm2_cpu_count{serviceName="my-app"} 4
# HELP pm2_available_apps Show available apps to monitor
# TYPE pm2_available_apps gauge
pm2_available_apps{serviceName="my-app"} 2
/// Статистика из приложений
# HELP nodejs_app_request_counter Show total request count
# TYPE nodejs_app_request_counter counter
nodejs_app_request_counter{app="app",instance="13",serviceName="my-app"} 10
nodejs_app_request_counter{app="app",instance="14",serviceName="my-app"} 17
По-умолчанию вся статистика агрегируется по всем запущенным инстансам, но если вы хотите детальную статистику по каждому инстансу, то это можно включить через параметр конфигурации модуля:
pm2 set pm2-prom-module:aggregate_app_metrics false
Этот модуль, в отличие от некоторых других, позволяет собирать метрики по каждому приложению отдельно и они не пересекаются.
В целом, статистика, которая отдается PM2 достаточна для базового мониторинга приложений, но в нашем случае, например, нужно было больше детальных данных по времени рендера страниц или загрузки некоторых блоков данных. Для таких целей мы использовали гистограмму:
// ...
export const metricRequestTime = new client.Histogram({
name: 'nodejs_app_page_execute',
help: 'Time to processing request',
registers: [registry],
buckets: [0.1, 0.2, 0.3, 0.5, 0.7, 1, 2, 3, 5],
labelNames: ['code', 'page'],
});
// ...
const endMetricRequestTime = metricRequestTime.startTimer();
// ...
endMetricRequestTime({ code: 200, page: 'Homepage });
В данном примере мы используем 2 дополнительных параметра code
(код ответа) и page
(идентификатор страницы) благодаря которым предоставляется детальная статистика, например, можно построить такие графики в Grafana:
- Какие страницы чаще всего запрашивают
sum(rate(nodejs_app_page_execute_count{serviceName="$serviceName",code="200"}[5m])) by (page)
- Количество ошибок в ответах сервиса (коды ответов)
sum(rate(nodejs_app_page_execute_count{serviceName="$serviceName"}[5m])) by (code)
- Время загрузки каждой страницы указывая, например, 99 перцентиль
histogram_quantile(0.99, sum(rate(nodejs_app_page_execute_bucket{serviceName="$serviceName", code="200"}[1m])) by (page, le))
- И множество других графиков по каждой отдельной странице
Кстати, я не советую использовать URL страницы как значение page
- это увеличит во много раз использование памяти и размер ответа в Prometheus и как следствие - неразбериху в графиках. Лучше определять какая страница загрузилась и использовать ее идентификатор.
В итоге мы получаем единственную точку сбора и отдачи статистики по всем приложениям запущенным в PM2 и систему мониторинга построенную на базе Prometheus и функциональные дашбоарды в Grafana.