Собираем метрики Node.js приложений в PM2 с экспортом в Prometheus

от автора

Ни для кого не секрет что для устойчивой и надежной работы node.js приложений необходимо проводить мониторинг их работы и делать полезные выводы глядя на их метрики. Это означает, что вы способны получать информацию о состоянии до возникновения проблем, таким образом, предотвращая сбои.

В этой статье я хотел бы рассказать о способе сбора статистики из node.js приложений, которые запущены в PM2, и экспорт этих данных в Prometheus.

Когда вы просто запускаете node.js приложение через команду node app.js или же через 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="app1",serviceName="my-app"} 10 nodejs_app_request_counter{app="app2",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.

Пример дашбоарда с pm2-prom-module

Пример дашбоарда с pm2-prom-module

Спасибо, что дочитали до конца.


ссылка на оригинал статьи https://habr.com/ru/articles/794046/