Всем привет. Сегодня я бы хотел рассказать о том, с какими трудностями пришлось столкнуться при работе с менеджером node.js процессов PM2, и как нам пришлось расширить его возможности для того, чтобы его было можно использовать эффективнее.

Как известно PM2 — это менеджер процессов с открытым исходным кодом позволяющий кластеризировать приложение и гибко распределять нагрузку между ядрами процессора. Многие используют его в своих продакшен решениях благодаря его возможностям, но в последнее время развитие продукта происходит крайне медленно из‑за чего его возможности приходится расширять с помощью своих дополнительных модулей.

Первое, чего в нашем случае нам не хватало — это автоматического масштабирования наших node.js приложений при увеличении нагрузки на какой‑то из проектов. Да, конечно, PM2 позволяет запускать нужное количество инстансов приложения которое вам необходимо. Например, у вас есть сервер на котором есть 4 ядра, то вы можете кластеризировать ваше приложение и запустить его 4 копии которые позволят вам обрабатывать запросы от пользователей в параллель.

Благодаря конфигурационному файлу, который поддерживает PM2 можно создавать конфиг для каждого вашего приложения. Например, вот такой небольшой конфиг для запуска приложения:

module.exports = {
    apps: [
        {
            name: 'app',
            script: 'build/app.js',
            instances: '4',
            autorestart: true,
            watch: false,
            max_memory_restart: '512M',
            vizion: false,
            exec_mode: 'cluster',
        },
    ],
};

Тут видно, что приложение запускается в режиме кластера и поднимает 4 инстанса (копии приложения). Приложение поднимается на одном порту\сокете и PM2 сам распределяет запросы между инстансами. Но потребление памяти будет 4Х в данном случае, потому что каждый инстанс приложения будет активен. Если один инстанс у вас потребляет 100 Мбайт оперативной памяти, то всего PM2 займет 400 Мбайт только под ваше приложение.

В большинстве случаев у вас есть 1 сервер и на нем запущено одно приложение. Но что, если у вас несколько приложений которые вы хотите запустить, а сервер, который обслуживает ваши приложения, всего один. Вам нужно как‑то распределить нагрузку.

Предположим вы делаете сервер с 8-ми ядрами и 4 Гигабайта ОЗУ. Вы запускаете 4 процесса одного приложения и 4 процесса другого. И вроде бы все хорошо, нагрузка распределяется между 2 приложениями. Но, внезапно, одно ваше приложение начинает испытывать нагрузку больше чем другое, однако оно не выйдет за 4 доступных процесса. Вам необходимо зайти на сервер и увеличить количество инстансов с помощью CLI команды в PM2.

pm2 scale app +3

Но когда нагрузка спадет, вам снова придется зайти на сервер и уменьшить количество инстансов. Достаточно непростая схема в которой необходимо еще производить мониторинг нагрузки на каждое приложение. А теперь представьте у вас на одном сервере 10 приложений. Если у вас сервер с 48 ядрами, то можно запустить по 48 инстансов каждого приложения, но тогда у вас будет просто занято ~4800 Мбайт оперативной памяти под каждое приложение (а если 10 приложений, то это почти 50 Гигабайт) и вы не будете знать какое из них в ближайшее время будет под нагрузкой.

Чтобы решить эту проблему я написал плагин pm2-autoscale для PM2 который позволяет запускать каждое приложение с минимальным количеством инстансов и которые потом будут автоматически увеличиваться по мере нагрузки на приложение. Установить плагин можно командой.

pm2 install pm2-autoscale

Он считывает текущую нагрузку на приложение, смотрит есть ли свободная память и увеличивает количество инстансов до количества CPU — 1, если это возможно. Когда нагрузка спадает, то он уменьшает количество инстансов и освобождает память.

У плагина есть несколько свойств конфигурации:

  • scale_cpu_threshold Максимальное значение загрузки CPU для приложения после которого плагин попытается увеличить количество инстансов. Например, если у вас 4 инстанса, то когда хотя бы один из них превысит это значение. (по умолчанию 30)

  • release_cpu_threshold Среднее значение суммы нагрузки всех инстансов приложения после которого количество инстансов будет уменьшаться. (по умолчанию to 5)

  • debug Включение подробного лога для плагина (по умолчанию false)

В нашем случае этот модуль — это первая ступень быстрого масштабирования приложений, если мы видим, что на сервере нагрузка доходит до критической, то мы понимаем еще один сервер из варм пула.

Второй возможности, которой нам не хватало в PM2 — это мониторинг наших node.js приложений, так как он является одним из важных этапов анализа качества и корректной работы любого приложения.

Да, PM2 предоставляет средства для мониторинга ваших приложений, но в бесплатной версии они доступны только через консольную команду pm2 monit или же внешний веб‑интерфейс через платную подписку PM2 Plus с дополнительным функционалом.

Результат исполнения команды pm2 monit
Результат исполнения команды pm2 monit

К сожалению, ни тот, ни другой способ не всегда подходит для мониторинга, потому что в первом случае вам необходимо видеть не только текущие показатели работы, но и изменение значений с учетом времени. Например, рост использования памяти внутри приложения или количество его перезагрузок. А второй вариант — во‑первых платный, а во‑вторых он не интегрируется в вашу общую систему мониторинга и находится на стороннем сервисе.

Во многих случаях проекты используют связку Prometheus + Grafana которые позволяют не только собирать данные и отображать их в виде графиков, но и настроить предупреждения при изменении каких‑либо метрик.

На данный момент, PM2 не предоставляет удобного и простого способа экспортировать данные в Prometheus, поэтому пришлось реализовать свой собственный модуль, который позволял бы это делать. Все существующие модули, которые удалось найти были либо для 4 версии, либо не отдавали полный список необходимых нам метрик, например, количество активных инстансов в приложении.

Модуль pm2-prom‑module был реализован и сейчас доступен для всех желающих. Установить его можно с помощью команды pm2 install

pm2 install pm2-prom-module

После установки поднимается сервер мониторинга на порту 9988 и к нему можно обращаться по адресу http://localhost:9988/

Модуль можно конфигурировать и указывать другой порт или название вашего сервиса. Например,

pm2 set pm2-prom-module:port 10801
pm2 set pm2-prom-module:service_name MyApp

Указание сервиса очень удобно, когда вы устанавливаете модуль на разные проекты, а в Grafana используете один dashboard. Так можно сделать выпадающий список со всеми сервисами и быстро между ними переключаться.

Данный модуль собирает все доступные метрики которые предоставляет PM2 и еще несколько дополнительных (полный список можно прочитать на странице модуля).

В итоге в Grafana получилось настроить вот такой дашбоард. Настройки для него можно скачать по этой ссылке.

Dashboard для мониторинга PM2 приложений с pm2-prom-module
Dashboard для мониторинга PM2 приложений с pm2-prom-module

В нашем случае каждый node.js проект мы пакуем в Docker контейнер и внутри устанавливаем PM2 и дополнительные модули. Все параметры, такие как название сервиса или порт, передаются через аргументы. В итоге, Docker контейнер выглядит примерно таким образом:

FROM node:18-bullseye-slim 
RUN apt -y update && apt -y install curl bash git python3 build-essential procps

#### Установка PM2
RUN npm i -g pm2@5.3.0

# ...
# Сборка проекта
#...

ARG PROJECT_NAME
ARG ENV_METRICS_PORT

#### Установка модулей для PM2
RUN pm2 install pm2-autoscale && pm2 install pm2-prom-module && pm2 set pm2-prom-module:port $ENV_METRICS_PORT && pm2 set pm2-prom-module:service_name $PROJECT_NAME

CMD ["pm2-runtime", "--json", ".ecosystem.config.js"]

На данный момент модуль не умеет экспортировать метрики из самих приложений, но надеюсь, что руки дойдут это сделать.

Спасибо, что прочитали статью, надеюсь было интересно и полезно. Если у вас будут пожелания или предложения — всегда рад вашим пул‑реквестам.

Комментарии (2)


  1. ashkraba
    04.12.2023 20:14
    +1

    Ух, спасибо Вам огромное за проделанную работу. Прям оооочень полезные вещи. Держите в курсе)


  1. Neoldian
    04.12.2023 20:14
    +1

    Тоже делали свой костылек для автоскалирования и рестарта по лимиту памяти (от стандартного был сайд эффект пакета для драйвера оракла, который принимая sigint от pm2 рвал всё коннекты без graceful отключения, хотя pm2 был настроен на messages, а не сигналы), но как подсистема в приложении, ваш сторонний вотчер выглядит правильнее, спасибо поковыряем.