PM2 отвечает на вопрос, на который хостинг-панель обычно уже ответила: что перезапускает моё приложение, когда оно завершается, и куда уходят его логи. Если запустить оба, получаются два надзирателя, поставленных друг на друга, и внешний - тот, у которого есть график памяти, история перезапусков и оповещения, - теряет из виду процесс, за которым должен следить. Приложение, ушедшее в цикл падений под PM2, снаружи контейнера выглядит как приложение, которое работает уже девять дней.
Это краткий ответ: на контейнерной панели запускайте процесс напрямую и позвольте платформе им управлять. На обычном виртуальном сервере, где своего надзора нет, менеджер процессов - именно то, что нужно, хотя systemd уже установлен и выполняет большую часть той же работы. Остальное - подробности: что даёт каждый слой, где они сталкиваются и как уйти с PM2, не потеряв того, что он действительно для вас делал.
Что на самом деле делает PM2#
PM2 - это демон плюс CLI. Команда pm2 start передаёт ваш скрипт фоновому демону, который запускает его, следит за ним, перезапускает при завершении и пишет вывод в файлы. После этого CLI завершается, и именно это первое, что важно в контейнере.
$ pm2 start dist/server.js --name api -i 2$ pm2 ls$ pm2 logs api --lines 100$ pm2 restart api$ pm2 save && pm2 startupНа голой машине его набор возможностей действительно полезен:
- Перезапуск при завершении с
min_uptime,max_restartsи необязательным экспоненциальным backoff, чтобы процесс, умирающий сразу, не перезапускался в плотном цикле. - Режим cluster, использующий модуль
clusterиз Node, чтобы запустить несколько рабочих процессов с общим слушающим сокетом. - `pm2 reload`, который в режиме cluster перезапускает воркеры по одному, так что всегда есть воркер, принимающий соединения.
- Захват логов в
~/.pm2/logs/<name>-out.logи-error.logс ротацией, если установить модульpm2-logrotate. - Запуск при загрузке через
pm2 startup, который создаёт и устанавливает unit systemd, выполняющийpm2 resurrect, чтобы вернуть то, что записалpm2 save. - `max_memory_restart`, перезапускающий воркер, у которого резидентная память превысила порог.
- Файл ecosystem, чтобы всё перечисленное лежало в репозитории, а не в чьей-то истории командной строки.
module.exports = { apps: [ { name: "api", script: "dist/server.js", instances: 2, exec_mode: "cluster", max_memory_restart: "300M", env: { NODE_ENV: "production" }, }, ],};Ничто из этого не является плохим ПО. Вопрос лишь в том, не делает ли то же самое что-то, что находится ниже.
Что панель уже делает#
Панельный хостинг запускает ваше приложение в его собственном контейнере, и сам контейнер тоже находится под надзором. В RE:NODE это означает:
- Start, stop, restart и kill из консоли, а контейнер перезапускается, когда процесс завершается.
- Лимит памяти, который обеспечивает ядро. При достижении лимита контейнер останавливается и запускается заново, а не уходит в своп; это противоположно поведению upstream Pterodactyl и гораздо проще для понимания: у вас никогда не бывает сервера, который формально жив, а функционально заморожен.
- Жёсткая доля CPU. Цифра тарифа - это процент от одного ядра, так что 250 - это 2.5 vCPU, и это потолок, а не ориентир. Процесс на 100% работает медленно, но не сломан и никогда не приостанавливается из-за этого.
- Обнаружение сбоев. Наблюдатель каждые две минуты проверяет, не ушёл ли сервер в офлайн и не пошло ли время его работы назад. Перезапуски, которые вы запросили сами, не считаются. Три неожиданных перезапуска за час выводят предупреждение на странице сервера и автоматически открывают обращение; шесть - приостанавливают сервер, чтобы цикл падений не мог работать все выходные.
- Живой вывод консоли без фильтрации, с командной строкой, плюс графики памяти, CPU и диска относительно лимитов тарифа.
- Schedules по cron-выражению, выполняющие упорядоченные задачи с задержками: команду консоли, бэкап, действие с питанием.
- Деплой из Git с GitHub с двумя переключателями - обновлять при каждом запуске и передеплоить при push, что перезапускает только тот сервер, который уже работал, - и с одной записью на каждый деплой.
Прочитайте этот список рядом со списком PM2, и станет видно, что пересекается большая часть обоих.
Пересечение, строка за строкой#
| Задача | PM2 | Панель |
|---|---|---|
| Перезапуск после сбоя | Да, внутри контейнера | Да, перезапускает контейнер |
| Запуск после перезагрузки | pm2 startup плюс systemd | Да, по умолчанию |
| Потолок памяти | max_memory_restart, рекомендательный | Лимит ядра на контейнер |
| Потолок CPU | Нет | Жёсткое ограничение до доли тарифа |
| Логи | Файлы в ~/.pm2/logs | Живая консоль, без фильтрации |
| Несколько процессов | Режим cluster | Один сервер на процесс или & |
| Перезагрузка без простоя | pm2 reload в режиме cluster | Нет |
| Плановые перезапуски | cron_restart | Вкладка Schedules |
| Оповещение о цикле падений | Нет | Предупреждение и автоматическое обращение |
| Деплой из git | pm2 deploy, используется редко | Приложение GitHub, pull или push |
Стоит задержаться на двух строках. PM2 - единственный столбец, где есть перезагрузка без простоя, и это реальная возможность, которую он предлагает. А панель - единственный столбец, который сообщает кому-то, что приложение в цикле падений, и именно это важно в четыре часа утра.
Почему надзиратель внутри надзирателя скрывает проблемы#
Проследите падение вверх по этой схеме. Ваше приложение бросает необработанное отклонение промиса и завершается. PM2 замечает и запускает его снова. Контейнер при этом не завершался, и с точки зрения платформы ничего не произошло: время работы не прервано, перезапуск не записан, наблюдатель сбоев насчитал ноль, а обращение, которое сообщило бы вам о проблеме, не открывается. Ваш график памяти - ровная линия, составленная из десятков короткоживущих процессов.
В этом весь довод, а остальное - следствия:
- Код выхода теряется. Процесс, завершившийся с кодом 1 из-за отсутствия обязательной переменной окружения, снаружи неотличим от нормально работающего.
- Учёт памяти запутывается. Лимит контейнера действует на демон и всех воркеров вместе. Если поставить
max_memory_restartв 512M на тарифе 1 GB с двумя воркерами, ядро остановит весь контейнер раньше, чем будет достигнут порог самого PM2: одна политика, которую вы настроили, и одна, которая реально срабатывает, и срабатывает не ваша. - У сигналов появляется лишний переход. Остановка из панели посылает сигнал главному процессу контейнера. Если это оболочка или вызов CLI PM2, который уже вернулся, ваше приложение может вообще не получить сигнал и будет убито по истечении отсрочки вместо чистого завершения. Почему это стоит вам запросов в обработке, объясняет статья корректное завершение и проверки работоспособности.
- Логи уходят в неудобное место. PM2 пишет в файлы в своём домашнем каталоге, а не в стандартный вывод, поэтому в живой консоли вы видите болтовню самого демона, а не вашего приложения. В итоге вы читаете логи через SFTP, а это странный способ наблюдать за деплоем.
- Демон стоит памяти. Десятки мегабайт: на 8 GB это ничто, а на 1 GB заметно.
Если вы всё же запускаете PM2 в контейнере, используйте pm2-runtime#
Иногда PM2 остаётся по причине - файл ecosystem, в котором записана настоящая конфигурация, или команда, знающая его команды. В таком случае правильная точка входа ровно одна:
$ pm2-runtime start ecosystem.config.js --env productionpm2-runtime - это версия CLI, рассчитанная на контейнеры. Она остаётся на переднем плане, выводит логи приложения в стандартный вывод, чтобы их показала консоль, и завершается, когда завершаются приложения, благодаря чему платформа видит сбои. Запуск обычным pm2 start в контейнере - классическая ошибка: CLI отдаёт приложение демону и возвращается, главный процесс завершается, и контейнер останавливается, хотя приложение, казалось бы, работает.
Стоит проверить две настройки. По умолчанию PM2 посылает вашему приложению SIGINT, а не SIGTERM, поэтому приложение, обработчик завершения которого слушает только SIGTERM, никогда его не выполнит: задайте kill_signal или обрабатывайте оба сигнала. А kill_timeout - сколько PM2 ждёт до отправки SIGKILL; он должен быть больше, чем самый долгий запрос в обработке, а не меньше.
Режим cluster: когда больше процессов помогает, а когда нет#
Режим cluster - сильнейшая возможность PM2 и самая часто применяемая неправильно. Node выполняет ваш JavaScript в одном потоке, поэтому второй процесс может задействовать второе ядро. Вопрос в том, покупали ли вы второе ядро.
| Доля CPU тарифа | Полезных процессов Node |
|---|---|
| 0.5 ядра | 1 |
| 1 ядро | 1 |
| 1.5-2 ядра | 2 |
| 3 ядра | 2-3 |
instances: "max" запрашивает по одному воркеру на каждый логический CPU, о котором сообщает машина, а на общем узле это число ядер хоста, а не ваша доля. В контейнере, ограниченном половиной ядра, это может означать десяток воркеров, делящих один и тот же урезанный кусок, и у каждого в памяти своя копия вашего дерева зависимостей. Результат - меньше пропускной способности и намного больше памяти, то есть именно тот сценарий, что описывается словами «мы добавили воркеров, и стало медленнее».
Ещё два последствия режима cluster, которые стоит знать до его включения:
- Sticky sessions. Всё, что хранит состояние в процессе, - соединение WebSocket в Map, хранилище сессий в памяти, апгрейд транспорта по умолчанию в Socket.IO, - ломается, когда последовательные запросы попадают на разных воркеров. О стороне маршрутизации см. websockets за обратным прокси.
- Всё, что должно выполняться один раз. Задача cron, потребитель очереди или миграция внутри приложения теперь выполняется по разу на воркера. Так ночное письмо отправляется четыре раза.
Если вы на самом деле хотите использовать имеющийся CPU, подберите под это тариф, а не умножайте процессы внутри тарифа, который не в силах их прокормить. Вторую половину вопроса о размерах покрывает статья лимиты памяти Node простыми словами.
Без него: команда запуска и логи#
Замена PM2 на панели - это в основном удаление. Команда запуска выполняет ваш процесс на переднем плане, в контейнере, как главный процесс:
$ npm ci --omit=dev && node dist/server.jsИспользуйте npm ci, а не npm install, чтобы то, что будет установлено, определял lock-файл - в чём разница и почему на сервере это важнее, чем на ноутбуке, описано в статье npm ci или npm install. Логи идут в стандартный вывод и стандартный вывод ошибок, откуда читает консоль; ротировать файлов нет, и нечего чистить, когда заполняется диск. Что панель делает с этим потоком, рассказано в статье чтение консоли.
Замены для того, что вы теряете:
- Перезапуск при сбое - это делает контейнер. Убедитесь, что процесс действительно завершается при фатальной ошибке, а не зависает, иначе перезапускать его будет нечему.
- Перезапуск по расписанию - действие с питанием на вкладке Schedules с cron-выражением, что к тому же означает, что перезапуск записан как намеренный и не считается наблюдателем сбоев.
- Лимит памяти - лимит тарифа, обеспечиваемый ядром. Если хотите, чтобы сам Node падал раньше и предсказуемее, задайте
--max-old-space-sizeниже лимита контейнера. - Несколько процессов - второй сервер, что чище, или
&в команде запуска, что дешевле и не даёт никакого надзора за фоновым процессом. Эти варианты сопоставлены в статье фоновые задачи на небольшом сервере. - Перезагрузка без простоя - честно говоря, вы её теряете. У деплоя в одном контейнере есть пауза в несколько секунд, пока процесс перезапускается, и делать вид, что её нет, - способ снова получить двух надзирателей. Что возможно и что невозможно на одной машине, прямо сказано в статье деплой без простоя на небольшом сервере.
Сам деплой идёт с GitHub: pull при запуске или redeploy при push, а каждый деплой записывается, чтобы вы могли отличить выкатившийся от упавшего. Пятиминутную версию этой настройки даёт статья деплой приложения Node с GitHub.
Где менеджер процессов по-прежнему уместен#
На VDS или выделенной машине ничто ничем не управляет, пока вы этого не сделаете. Там что-то нужно, и кандидатов два: PM2 и systemd.
systemd уже установлен, запускается при загрузке без помощников, собирает логи в журнал и перезапускает при сбое с задержкой, которую вы настраиваете:
[Service]User=appWorkingDirectory=/srv/apiExecStart=/usr/bin/node dist/server.jsRestart=alwaysRestartSec=5Environment=NODE_ENV=production[Install]WantedBy=multi-user.targetjournalctl -u api -f заменяет pm2 logs, а systemctl restart api - pm2 restart. Никакого дополнительного демона, никакого pm2 save, о котором можно забыть, и тот же механизм управляет всем остальным на машине. Файлы unit разобраны как следует в статье службы systemd для ваших приложений.
PM2 оправдывает своё место на VDS, когда нужен режим cluster с последовательными перезагрузками, когда вы запускаете несколько небольших приложений Node и предпочитаете одну CLI для всех или когда pm2 monit - действительно способ работы вашей команды. Это настоящие причины. «Так было в руководстве» - не причина.
Уход с PM2 за десять минут#
- Прочитайте
ecosystem.config.jsи запишите всё, что является настоящей конфигурацией: переменные окружения, путь к скрипту, число экземпляров,max_memory_restart. - Перенесите переменные окружения на вкладку Startup в панели или в собственный файл окружения, если вы на VDS.
- Задайте команду запуска как сам скрипт, на переднем плане:
node dist/server.js, а неpm2 start. - Убедитесь, что приложение пишет в стандартный вывод. Если оно было настроено писать в файл потому, что PM2 всё равно перехватывал stdout, верните прежнее.
- Добавьте обработчик
SIGTERM, который закрывает сервер и дожидается запросов в обработке, и проверьте, что остановка из панели чистая. - Если вы использовали
cron_restart, воссоздайте его как запланированное действие с питанием. - Удалите PM2 из
package.jsonи проверьте, что ничто другое его не вызывает. - Задеплойте, затем намеренно уроните приложение и посмотрите, что произойдёт: контейнер должен перезапуститься, и перезапуск должен быть виден. Если это не так, что-то всё ещё проглатывает выход.
Восьмой шаг - тот, который пропускают, и единственный, что доказывает работу остальных. Тот же довод об оповещениях, которые никто никогда не видел сработавшими, приводит статья мониторинг, который что-то сообщает.
FAQ#
Работает ли PM2 на хостинг-панели вообще?
Да, если запускать его через pm2-runtime, чтобы он оставался на переднем плане и выводил логи в стандартный вывод. Он работает; просто дублирует надзор, который у контейнера уже есть, и прячет сбои от обнаружения сбоев на платформе. Обычный pm2 start в качестве команды запуска не работает, потому что CLI возвращается, и контейнер останавливается.
Нужен ли мне режим cluster?
Только если у вас больше одного ядра доли CPU и приложение упирается в CPU на стороне JavaScript. Большинство небольших сервисов Node ждут базу данных или внешний API, и второй процесс добавляет памяти, но не пропускной способности. Перед добавлением воркеров посмотрите график CPU.
Что заменяет pm2 logs?
Консоль, если приложение пишет в стандартный вывод. Это же единственное место, где панель может показать вывод в реальном времени, и это хороший повод перестать писать логи приложения в файлы на хостинге с одним контейнером.
PM2 - плохой инструмент?
Нет. Это хорошо сделанный менеджер процессов, решающий задачу, которая существует на неуправляемых машинах. Ошибка не в PM2, а в том, чтобы запускать двух надзирателей и ждать, что внешний будет знать, что делает внутренний.
Даёт ли панель деплой без простоя?
Нет, и не даёт ничто другое, что запускает одну копию вашего приложения на одном сервере. Перезапуск - это пауза в несколько секунд. Настоящий деплой без простоя требует двух экземпляров и чего-то перед ними, а это более крупная архитектура, чем один тариф для приложения.
А как насчёт forever, nodemon или supervisor?
nodemon - инструмент разработки, перезапускающий процесс при изменении файлов, и в команде запуска production ему не место. forever в основном вытеснен. Приведённые выше рассуждения относятся ко всем ним: если платформа перезапускает ваш контейнер, второй перезапускатель не добавляет ничего, кроме места, где могут прятаться сбои.




Комментарии
Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.