Автоскейлинг node.js приложений с помощью PM2
Многие node.js приложения которые работают в продакшен запускают через процесс менеджер PM2. PM2 — это менеджер процессов с открытым исходным кодом позволяющий кластеризировать приложение и гибко распределять нагрузку между ядрами процессора. Не буду расписывать все удобства этого менеджера, напишу сразу о его небольшом недостатке и как решить эту проблему.
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И PM2 поднимает вам 3 новых инстанса. Но когда нагрузка спадет, вам снова придется зайти на сервер и уменьшить количество инстансов. Достаточно муторная схема, да и еще с ручным мониторингом. А теперь представьте у вас на одном сервере 10 приложений. Если у вас сервер с 48 ядрами, то можно запустить по 48 инстансов каждого приложения, но тогда у вас будет просто занято ~4800МБ оперативной памяти под каждое приложение (а если 10 приложений, то это почти 50ГБ) и вы не будете знать какое из них в ближайшее время будет под нагрузкой.
Что бы решить эту проблему я написал плагин pm2-autoscale (https://www.npmjs.com/package/pm2-autoscale) для PM2 который позволяет запускать каждое приложение с минимальным количеством инстансов и которые потом будут автоматически увеличиваться по мере нагрузки на приложение. Установить плагин можно командой.
pm2 install pm2-autoscaleОн считывает текущую нагрузку на приложение, смотрит есть ли свободная память и увеличивает количество инстансов до количество CPU - 1, если это возможно. Когда нагрузка спадает, то он уменьшает количество инстансов и освобождает память. Например, пример ниже:
0|pm2-autoscale | App "app" has 1 worker(s). CPU: 0.
0|pm2-autoscale | App "app" has 1 worker(s). CPU: 17.
0|pm2-autoscale | App "app" has 1 worker(s). CPU: 29.
0|pm2-autoscale | INFO: Increase workers
0|pm2-autoscale | App "app" scaled with +1 worker
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 26,34.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 29,43.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 28,38.
0|pm2-autoscale | INFO: Increase workers
0|pm2-autoscale | INFO: App "app" is busy
0|pm2-autoscale | App "app" scaled with +1 worker
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 27,35,33.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 25,31,22.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 24,29,23.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 23,25,20.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 15,18,17.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 11,13,13.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 8,9,10.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 6,7,7.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 5,5,5.
0|pm2-autoscale | INFO: Decrease workers
0|pm2-autoscale | INFO: App "app" is busy
0|pm2-autoscale | App "app" decresed one worker
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 4,4.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 3,3.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 2,2.
0|pm2-autoscale | INFO: Decrease workers
0|pm2-autoscale | INFO: App "app" is busy
0|pm2-autoscale | App "app" decresed one workerУ плагина есть несколько свойств конфигурации:
scale_cpu_thresholdМаксимальное значение загрузки CPU для приложения после которого плагин попытается увеличить количество инстансов. Если у вас 4 инстанса, то когда хотя бы один из них превысит это значение. (по умолчанию30)release_cpu_thresholdСреднее значение суммы нагрузки всех инстансов приложения после которого количество инстансов будет уменьшаться. (по умолчанию to5)debugВключение подробного лога для плагина (по умолчаниюfalse)
Для изменения значений конфигурации плагина можно воспользоваться командами ниже:
pm2 set pm2-autoscale:debug true
pm2 set pm2-autoscale:scale_cpu_threshold 50Благодаря этому плагину нам удалось в разы снизить ресурсы для сервера и правильно их распределить между приложениями.
Если у вас есть вопросы и предложения - пишите мне на GitHub https://github.com/VeXell