Пошук уроків, статей та іншого контенту
Порівняйте способи запуску, перезапуску та ізоляції Node.js-процесів у production-середовищі.
У production Node.js-застосунок запускається як окремий операційний процес. Керування цим процесом охоплює:
запуск після перезавантаження сервера;
перезапуск після помилки або зависання;
коректне завершення під час деплою;
зберігання логів і передавання змінних середовища;
ізоляцію від інших застосунків;
контроль ресурсів: пам’яті, CPU та кількості процесів.
Сам Node.js запускає JavaScript-код, але не є повноцінним менеджером production-процесів. Команда node app.js підходить для локальної розробки, проте в production процес повинен контролюватися зовнішнім інструментом.
Під час перезапуску менеджер процесів зазвичай надсилає застосунку сигнал SIGTERM. Застосунок має:
припинити приймати нові запити;
завершити поточні операції;
закрити з’єднання з базою даних та іншими сервісами;
завершити процес із відповідним кодом.
Приклад HTTP-сервера з graceful shutdown:
const http = require('node:http');
const port = Number(process.env.PORT) || 3000;
const server = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Сервер працює\n');
});
server.listen(port, () => {
console.log(`Сервер слухає порт ${port}`);
});
let isShuttingDown = false;
function shutdown(signal) {
if (isShuttingDown) {
return;
}
isShuttingDown = true;
console.log(`Отримано сигнал ${signal}. Завершення роботи...`);
// Припиняємо приймати нові з'єднання.
server.close((error) => {
if (error) {
console.error('Помилка під час закриття сервера:', error);
process.exitCode = 1;
}
console.log('Сервер коректно завершив роботу');
process.exit();
});
// Захист від нескінченного очікування активних запитів.
setTimeout(() => {
console.error('Завершення за тайм-аутом');
process.exit(1);
}, 10_000).unref();
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));Такий підхід важливий, коли менеджер процесів перезапускає застосунок або контейнер зупиняється. Якщо процес просто примусово завершити через SIGKILL, він не може виконати код обробника сигналу.
Код завершення допомагає менеджеру зрозуміти результат роботи:
0 — успішне завершення;
ненульове значення — помилка.
Зазвичай менеджери процесів перезапускають процес, який завершився з помилкою, але не перезапускають процес після нормального завершення. Конкретна поведінка залежить від конфігурації.
Найпростіший варіант:
node app.jsТакий запуск має недоліки:
процес не запускається автоматично після перезавантаження сервера;
після падіння застосунок не відновлюється;
немає стандартної конфігурації логів;
запуск часто прив’язаний до відкритої SSH-сесії;
складніше задати користувача, робочу директорію та обмеження ресурсів.
Команду node app.js можна використовувати для перевірки production-збірки, але для постійної роботи краще застосовувати менеджер процесів.
systemd — стандартний менеджер служб у більшості сучасних Linux-дистрибутивів. Він добре підходить, коли Node.js-застосунок запускається безпосередньо на віртуальній або фізичній машині.
Наприклад, створимо файл /etc/systemd/system/my-app.service:
[Unit]
Description=Node.js production application
After=network.target
[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/var/www/my-app
EnvironmentFile=/etc/my-app/my-app.env
ExecStart=/usr/bin/node /var/www/my-app/app.js
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=15
[Install]
WantedBy=multi-user.targetШлях до Node.js потрібно перевірити на конкретному сервері командою command -v node. Якщо Node.js встановлено через менеджер версій, шлях може відрізнятися від /usr/bin/node.
Файл змінних середовища /etc/my-app/my-app.env може мати такий вигляд:
NODE_ENV=production
PORT=3000Файл не повинен бути доступним для сторонніх користувачів, особливо якщо містить паролі або ключі.
sudo systemctl daemon-reload
sudo systemctl enable my-app
sudo systemctl start my-app
sudo systemctl status my-app
sudo systemctl restart my-app
sudo systemctl stop my-app
sudo journalctl -u my-app -fОсновні параметри:
enable додає запуск служби під час завантаження системи;
start запускає службу зараз;
restart перезапускає службу;
Restart=on-failure перезапускає процес після аварійного завершення;
RestartSec=5 додає затримку перед перезапуском;
User запускає процес не від імені root;
WorkingDirectory визначає поточну робочу директорію;
EnvironmentFile завантажує змінні середовища;
KillSignal=SIGTERM дозволяє застосунку завершитися коректно.
systemd зручний, якщо:
застосунок працює безпосередньо на Linux-сервері;
потрібна інтеграція з журналами операційної системи;
потрібен один або кілька чітко налаштованих процесів;
не хочеться додавати окремий Node.js-інструмент;
ізоляція через окремого Unix-користувача та права доступу достатня.
PM2 — менеджер процесів для Node.js. Він надає команди для запуску, перезапуску, перегляду логів і збереження конфігурації процесів.
Встановлення та запуск:
npm install --global pm2
pm2 start app.js --name my-app
pm2 status
pm2 logs my-app
pm2 restart my-app
pm2 stop my-appPM2 може запускати застосунок у кластерному режимі:
pm2 start app.js --name my-app -i maxПараметр -i max створює кілька процесів відповідно до доступних CPU. Це може збільшити пропускну здатність HTTP-застосунку, але кожен процес має власну пам’ять і власний стан. Стан сесій, кеш або WebSocket-з’єднання не варто безпосередньо зберігати лише в пам’яті одного процесу.
Для збереження списку процесів:
pm2 save
pm2 startupКоманда pm2 startup виводить команду, яку потрібно виконати з правами адміністратора. Після цього PM2 зможе відновити збережені процеси після перезавантаження сервера.
PM2 корисний, якщо:
команді потрібні прості Node.js-команди для керування;
потрібно швидко запускати кілька застосунків;
потрібен кластерний режим;
потрібно зручно переглядати логи та статус процесів;
інфраструктура вже стандартизована на PM2.
PM2 все одно працює поверх операційної системи. Для надійного запуску після перезавантаження потрібна його інтеграція із системним автозапуском.
Ізоляція зменшує вплив одного застосунку на інші. Вона може бути реалізована на кількох рівнях.
Не слід запускати Node.js-застосунок від імені root, якщо це не потрібно для конкретної операції.
Окремий користувач обмежує доступ процесу до:
файлів інших застосунків;
системних налаштувань;
приватних ключів;
каталогів користувачів;
адміністративних операцій.
У systemd це задається параметрами:
[Service]
User=nodeapp
Group=nodeapp
WorkingDirectory=/var/www/my-appПроцес також повинен мати доступ лише до необхідних файлів. Наприклад, каталог застосунку можна зробити власністю nodeapp, а конфігурацію з секретами — доступною тільки цьому користувачу.
Без обмежень несправний процес може спожити всю доступну пам’ять або CPU. Для Node.js можна встановити ліміт розміру heap:
[Service]
ExecStart=/usr/bin/node --max-old-space-size=512 /var/www/my-app/app.jsЗначення 512 означає приблизний ліміт heap у мегабайтах. Це не обмежує всю пам’ять процесу, оскільки процес також використовує пам’ять для native-модулів, буферів і самого середовища виконання.
Обмеження через systemd можуть виглядати так:
[Service]
MemoryMax=700M
CPUQuota=100%Після досягнення ліміту пам’яті система може завершити процес. Тому менеджер процесів має бути налаштований на його автоматичний перезапуск.
Контейнер пакує застосунок разом із середовищем його запуску та ізолює процеси на рівні операційної системи. Приклад мінімального Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "app.js"]Запуск контейнера:
docker build -t my-app .
docker run -d \
--name my-app \
--restart unless-stopped \
-p 3000:3000 \
my-appПараметр --restart unless-stopped просить Docker запускати контейнер знову після помилки або перезавантаження Docker. Контейнер не повинен містити окремий менеджер процесів для запуску кількох копій Node.js. Зазвичай один контейнер запускає один основний процес, а кількість контейнерів контролює оркестратор або сам Docker.
У контейнерному середовищі застосунок повинен коректно обробляти SIGTERM. Інакше під час оновлення контейнера активні запити можуть бути перервані.
Переваги:
мінімальна кількість інструментів;
просте налагодження;
підходить для локальної перевірки.
Недоліки:
немає автоматичного відновлення;
немає запуску після перезавантаження;
слабкий контроль доступів і ресурсів.
Переваги:
доступний у системі без додаткового Node.js-пакета;
інтегрований із журналами та автозапуском Linux;
добре контролює користувача, сигнали та ресурси;
передбачувана поведінка для серверних служб.
Недоліки:
конфігурація прив’язана до конкретної операційної системи;
кластерний запуск потрібно організовувати окремо;
оновлення кількох застосунків може вимагати більше ручної конфігурації.
Переваги:
зручні команди для Node.js;
просте керування кількома процесами;
вбудовані логи та кластерний режим;
зручний варіант для традиційного VPS.
Недоліки:
додає окремий інструмент і власний рівень конфігурації;
потребує налаштування автозапуску;
кластерний режим не вирішує проблему спільного стану між процесами.
Переваги:
однакове середовище запуску;
ізоляція файлової системи та ресурсів;
просте розгортання нової версії;
зручно масштабувати кількість екземплярів.
Недоліки:
потрібен Docker або інша контейнерна платформа;
потрібно окремо налаштувати мережу, сховище та логи;
контейнер сам по собі не замінює моніторинг і коректну обробку сигналів.
Перезапуск не повинен означати миттєве знищення старого процесу, якщо він ще обробляє запити.
Базова послідовність:
підготувати нову версію;
встановити production-залежності;
перевірити конфігурацію;
надіслати старому процесу SIGTERM;
дочекатися завершення активних запитів;
запустити нову версію;
перевірити її стан;
у разі помилки дозволити менеджеру виконати відновлення.
Для одного процесу короткочасна недоступність під час перезапуску можлива. Якщо потрібне оновлення без простою, використовують кілька екземплярів за балансувальником або механізм поступової заміни контейнерів. Важливо, щоб кожен екземпляр умів коректно завершувати роботу.
Застосунок зазвичай пише логи в стандартні потоки:
console.log('Інформаційне повідомлення');
console.error('Повідомлення про помилку');Менеджер процесів або контейнерна платформа перехоплює ці потоки.
Для systemd:
journalctl -u my-app --since "10 minutes ago"
journalctl -u my-app -fДля Docker:
docker logs --tail 100 my-app
docker logs -f my-appНе варто покладатися лише на повідомлення «процес запущено». Перевіряйте також:
чи відкритий потрібний порт;
чи відповідає health endpoint;
чи не перезапускається процес у циклі;
чи не закінчується пам’ять;
чи доступні необхідні змінні середовища.
Для одного Node.js-застосунку на Linux-сервері часто достатньо:
systemd;
окремого користувача;
Restart=on-failure;
обробки SIGTERM;
журналів через journalctl.
PM2 доречний, якщо потрібне зручне керування кількома Node.js-процесами або кластерний режим.
Контейнери доречні, якщо застосунок уже розгортається контейнерно або потрібно ізолювати середовище та стандартизувати деплой.
rootЯкщо застосунок буде скомпрометовано, процес із правами root може отримати надмірний доступ до системи.
Як уникнути: запускайте його від окремого користувача з мінімальними правами.
SIGTERMПроцес може бути завершений під час деплою разом із незавершеними запитами та операціями.
Як уникнути: закривайте HTTP-сервер і зовнішні з’єднання в обробнику SIGTERM.
Помилка конфігурації або відсутня змінна середовища може спричинити постійні падіння процесу.
Як уникнути:
додавайте затримку між перезапусками;
перевіряйте логи;
перевіряйте змінні середовища до запуску;
використовуйте health checks.
Запуск того самого застосунку одночасно через systemd, PM2 і Docker ускладнює діагностику та може спричинити конфлікти портів.
Як уникнути: оберіть один основний спосіб керування процесом для конкретного рівня інфраструктури.
У production шлях до node може відрізнятися від шляху в інтерактивній оболонці. Через це служба може не запускатися, хоча команда працює вручну.
Як уникнути: використовуйте абсолютні шляхи в ExecStart і перевіряйте їх окремо.
Після перезапуску пам’ять процесу очищується. У кластерному режимі різні запити можуть потрапити до різних екземплярів.
Як уникнути: зберігайте критичний стан у зовнішньому сховищі, а процес розглядайте як такий, що можна безпечно перезапустити.
Прямий запуск через node app.js не забезпечує production-надійність.
systemd добре підходить для Node.js на Linux-сервері та дає автозапуск, перезапуск, права доступу й контроль ресурсів.
PM2 спрощує керування Node.js-процесами та підтримує кластерний режим.
Контейнери ізолюють застосунок і його середовище, а політика перезапуску визначається Docker або оркестратором.
Node.js-застосунок має обробляти SIGTERM, закривати сервер і завершувати активні операції.
Запускайте процес від окремого користувача, контролюйте пам’ять і не зберігайте критичний стан лише в пам’яті процесу.