Що таке PM2 і як управляти процесами Node.js у виробництві?
PM2 - це менеджер процесів для Node.js у виробничому середовищі, який автоматично перезапускає додатки після збоїв, розподіляє навантаження між ядрами CPU через кластеризацію та зберігає логи у файлах.
Теорія
TL;DR
- PM2 схожий на менеджера ресторану: якщо один офіціант пішов, він одразу замінює його новим, у пік трафіку відкриває більше станцій і записує все в журнал без зупинки кухні.
- Головна різниця:
node server.jsвмирає при падінні й використовує одне ядро CPU. PM2 перезапускається автоматично, кластеризує на всі ядра й відстежує метрики. - Використовуй PM2 при деплої на VPS або сервер. Для локальної розробки - nodemon. Для serverless (Lambda, Vercel) - платформа сама керує процесами.
pm2 reloadіpm2 restart- це різні речі. Перша замінює воркери поступово, друга вбиває всіх одразу.
Швидкий приклад
# Без PM2 - одне падіння вбиває все
node server.js # Необроблена помилка → процес мертвий → потрібен ручний перезапуск
# З PM2 - автоматичне відновлення
npm install -g pm2
pm2 start server.js --name api -i max # кластерний режим, всі ядра CPU
pm2 list # online | uptime | restarts: 0
# Симулюй падіння: вбий процес вручну
# PM2 виявляє вихід, перезапускає за 1 секунду
# pm2 list показує: restarts: 1
pm2 stop api
pm2 delete apiОдна команда замінює весь скрипт запуску плюс ручний моніторинг.
Ключова різниця
node server.js прив'язує життя додатку до одного OS-процесу. Будь-який необроблений виняток вбиває його назавжди, весь трафік іде через одне ядро CPU, а логи зникають після перезапуску. PM2 огортає процес супервізором: перехоплює сигнал виходу, запускає замінника за мілісекунди і розподіляє трафік між кількома воркерами через вбудований модуль cluster. Додаток стає сервісом, а не скриптом.
Коли використовувати
- Один сервер, API на Express або Fastify:
pm2 start server.js -i maxодразу дає кластеризацію. - Self-hosted Next.js:
pm2 start npm --name "next" -- startз кастомним сервером. - NestJS або скомпільований TypeScript: ecosystem-файл, що вказує на
dist/server.js. - Навантажений додаток за Nginx: PM2 керує процесами, Nginx - маршрутизацією.
- Локальна розробка: nodemon краще справляється з гарячим перезавантаженням.
- Serverless (Lambda, Vercel): платформа сама керує процесами, PM2 нічого не додає.
Таблиця порівняння
| Можливість | node app.js | PM2 | nodemon | forever |
|---|---|---|---|---|
| Перезапуск при падінні | Ні | Так | Так (тільки dev) | Так |
| Кластеризація CPU | Ручний модуль cluster | Вбудована (-i max) | Ні | Ні |
| Збереження логів | stdout, зникають при рестарті | Файли в ~/.pm2/logs/ | Консоль | Файли |
| Перезавантаження без простоїв | Вручну | pm2 reload | Ні | Ні |
| Моніторинг | Немає | pm2 monit + хмарна панель | Немає | Базовий |
| Коли використовувати | Скрипти, локальна розробка | Продакшн Node.js сервери | Гаряче перезавантаження | Простий рестарт (застарілий) |
Як PM2 працює всередині
PM2 запускається як Node.js master-процес і форкає дочірні процеси через OS-рівневі fork() виклики, керуючи ними через модуль child_process. Він слідкує за кодами виходу кожного дочірнього процесу і сигналами (SIGINT, необроблені винятки) та запускає перезапуск за мілісекунди, якщо код виходу ненульовий. Кластеризація делегується вбудованому модулю cluster з одним воркером на ядро CPU (через os.cpus().length).
Команда pm2 reload запускає нові воркери, чекає поки кожен з них сигналізує готовність (подія "listening"), потім надсилає SIGTERM старим воркерам і чекає на завершення відкритих з'єднань. Саме ця послідовність забезпечує деплой без простоїв.
Ecosystem-файл
Для серйозного деплою використовуй ecosystem-файл:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'api',
script: 'dist/server.js', // скомпільований TypeScript
instances: 'max', // по одному екземпляру на кожне ядро
exec_mode: 'cluster', // обов'язково - без цього instances ігнорується
max_memory_restart: '1G', // перезапустити воркер при перевищенні 1GB RAM
max_restarts: 5, // зупинити після 5 падінь за 60 секунд
kill_timeout: 5000, // 5 секунд на завершення з'єднань перед SIGKILL
env_production: {
NODE_ENV: 'production',
PORT: 3000
}
}]
};pm2 start ecosystem.config.js --env production
pm2 reload api # нові воркери стартують, старі завершують запити і виходять
pm2 save # зберегти список процесів між перезавантаженнями сервера
pm2 startup # згенерувати systemd unit для автозапускуТипові помилки
Запуск без назви
pm2 start app.js без --name створює запис з назвою "app" або "server". Коли в pm2 list з'являється п'ять однаково названих записів, неможливо зупинити чи перезавантажити конкретний. Завжди додавай --name myapp.
Відсутність exec_mode: 'cluster' в ecosystem-файлі
Якщо встановити instances: 'max' без exec_mode: 'cluster', PM2 запускає один екземпляр у fork mode. Вся конфігурація з кількома екземплярами мовчки ігнорується. 8-ядерний сервер працює на одному потоці Node.js. Це причина приблизно половини скарг на продуктивність PM2 на Stack Overflow.
pm2 restart замість pm2 reload в продакшні
pm2 restart вбиває всіх воркерів одразу. Активні з'єднання обриваються і повертають 5xx помилки. pm2 reload замінює воркерів по одному, чекаючи на завершення кожного. Для CI/CD пайплайнів завжди використовуй pm2 reload.
Запуск PM2 від root
Дочірні процеси успадковують права root. Якщо додаток виконує shell-команди, це реальна дірка в безпеці. Використовуй непривілейованого системного користувача, а pm2 startup згенерує systemd-конфігурацію для коректного автозапуску.
Ігнорування ротації логів
Я бачив, як через це падає продакшн сервер о третій ночі - логи займають 100GB і диск переповнюється. Встановлюй pm2-logrotate в перший же день: pm2 install pm2-logrotate. За замовчуванням ротація при 10MB.
Де використовується в реальних проєктах
- Ghost blog, Strapi CMS:
pm2 start ecosystem.config.jsдля кластеризованих API маршрутів. - Self-hosted Next.js:
pm2 start npm --name "next" -- start. - NestJS бекенди: ecosystem-файл з
max_memory_restart: '1G'і скомпільованим dist. - Feathers.js real-time додатки:
-i maxдля масштабування Socket.io воркерів. - PM2 у Docker: використовуй
pm2-runtimeяк entrypoint для коректної обробки PID 1 і уникнення zombie-процесів.
Питання на співбесіді
Q: Як PM2 реалізує деплой без простоїв?
A: Запускає нові cluster-воркери, чекає поки кожен з них видасть подію "listening" (HTTP сервер готовий приймати з'єднання), потім надсилає SIGTERM старим воркерам і чекає на закриття відкритих з'єднань.
Q: Яка різниця між pm2 start -i max і ручним написанням cluster module?
A: PM2 додає автоматичний перезапуск окремих воркерів, збереження логів і моніторинг поверх Node's cluster. Якщо один воркер падає, PM2 перезапускає саме його, не зачіпаючи інших.
Q: Що відбувається коли воркер перевищує ліміт пам'яті?
A: PM2 опитує розмір V8 heap і порівнює з max_memory_restart. При перевищенні ліміту перезапускає конкретний воркер, інші продовжують обробляти запити.
Q: Best practice для PM2 у Docker?
A: Використовуй pm2-runtime замість звичайного pm2 start. Він коректно обробляє сигнали PID 1 і запобігає накопиченню zombie-процесів, які plain PM2 пропускає у Docker-контексті.
Q: Рівень senior: як PM2 розрізняє падіння і штатну зупинку?
A: Слухає child.on('exit') і перевіряє код виходу разом з тим, чи PM2 сам надсилав SIGTERM (від pm2 stop). Ненульовий код виходу без попереднього SIGTERM від PM2 означає падіння і тригерить перезапуск. Після max_restarts спроб додаток переходить у стан "errored" і PM2 припиняє спроби.
Приклади
Базовий: Express API з автоперезапуском
// server.js
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello from PM2'));
app.listen(3000, () => console.log('Сервер запущено на порту 3000'));pm2 start server.js --name basic-api -i 2
pm2 list
# basic-api | cluster | 2 instances | online | restarts: 0Вбий один з воркерів вручну. PM2 виявляє вихід і запускає замінника. Другий воркер продовжує обробляти запити поки перший відновлюється.
Середній рівень: Ecosystem-файл для продакшну (NestJS / TypeScript)
// ecosystem.config.js - використовується в деплоях NestJS і Strapi
module.exports = {
apps: [{
name: 'api',
script: 'dist/main.js',
instances: 'max',
exec_mode: 'cluster',
max_memory_restart: '1G',
max_restarts: 5,
kill_timeout: 5000,
env_production: {
NODE_ENV: 'production',
PORT: 3000
}
}]
};pm2 start ecosystem.config.js --env production
pm2 reload api
# Під час перезавантаження: жодних 5xx помилок - нові воркери приймають до завершення старих
pm2 logs api
pm2 save && pm2 startupКомбінація pm2 save і pm2 startup зберігає список процесів після перезавантаження сервера, нічого не потрібно запускати вручну.
Просунутий рівень: Захист від нескінченних перезапусків
Без обмежень баг, який кладе додаток одразу після старту, змушує PM2 перезапускати його в нескінченному циклі, навантажуючи CPU і переповнюючи логи.
// Додай до apps[] в ecosystem.config.js
{
max_restarts: 5, // зупинитись після 5 падінь
min_uptime: '10s', // додаток має протримати 10с щоб вважатись запущеним
kill_timeout: 5000 // 5 секунд перед примусовим завершенням SIGKILL
}pm2 start ecosystem.config.js
# Додаток падає 5 разів поспіль за менш ніж 10 секунд кожен
pm2 list
# Статус: errored - PM2 припинив спроби
pm2 logs api --lines 50 # подивись причину падінняПісля 5 перезапусків PM2 позначає додаток як "errored" і зупиняється. Фіксуєш баг, запускаєш pm2 restart api, лічильник скидається. Ніяких CPU спайків від нескінченних перезапусків о третій ночі.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.