В мире серверного JavaScript’а я — новичок с чистым, практически незамутнённым разумом. Поэтому когда я узнал о существовании менеджеров процессов, а конкретно — о pm2, то сразу же попробовал применить его для запуска какого-нибудь простейшего backend-сервиса на nodejs
в целях самообразования. Мне очень импонирует возможность подключения модулей в JS-коде через import
(ES6 modules), т.к. он позволяет использовать один и тот же код как в браузере, так и на серверной стороне, и я запилил простой сервис с ES6-модулями.
Если вкратце, то запустить ES6-версию приложения под pm2
у меня не получилось, для запуска таких приложений лучше использовать либо forever
, либо systemd
. Под катом — отчёт о результатах для тех, кто любит тексты подлинее.
Введение
В контексте данной публикации под менеджером процессов подразумевается сервис, основной задачей которого является мониторинг запущенного nodejs
-приложения и его перезапуск в случае падения. Также менеджер процессов может (но не обязан) собирать информацию о потребляемых приложением ресурсах (процессор, память).
Тестовый сервис
Для тестирования менеджеров процессов я использовал вот такой код в ES6-сервисе (github repo):
# src/app_es6.mjs import express from "express"; import mod from "./mod/es6.mjs"; const app = express(); const msg = "Hello World! " + mod.getName(); app.get("/", function (req, res) { console.log(msg); res.send(msg); }); app.listen(3000, function () { console.log('ES6 app listening on port 3000!'); });
и в ES6-модуле:
# src/mod/es6.mjs export default { getName: function () { return "ES6 module is here."; } }
Аналогичный сервис, выполненный c CommonJS-модулями выглядит так:
# src/app_cjs.js const express = require("express"); const mod = require("./mod/cjs.js"); const app = express(); const msg = "Hello World! " + mod.getName(); app.get("/", function (req, res) { console.log(msg); res.send(msg); }); app.listen(3000, function () { console.log("CommonJS app listening on port 3000!"); });
CJS-модуль:
# src/mod/cjs.js module.exports = { getName: function () { return "CommonJS module is here."; } };
Запуск сервиса без использования менеджера процессов на nodejs
v12.14.0:
$ node --experimental-modules ./src/app_es6.mjs # ES6-service $ node ./src/app_cjs.js # CJS-service
pm2
pm2
на данный момент является лидером среди менеджеров процессов по предлагаемому функционалу (помимо поддержания процесса в рабочем состоянии также есть кластеризация, мониторинг использования ресурсов, различные стратегии рестарта процессов).
CJS-сервис запускается без проблем (pm2
v4.2.1):
$ pm2 start ./src/app_cjs.js -i 4
также без проблем поддерживается заданное количество экземпляров сервиса в кластере:
root@omen17:~# ps -Af | grep app_cjs alex 29848 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js alex 29855 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js alex 29864 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js alex 29875 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js
После «убийства» одного экземпляра (PID 29864
) менеджер процессов сразу же поднял новый (PID 30703
):
root@omen17:~# kill -s SIGKILL 29864 root@omen17:~# ps -Af | grep app_cjs alex 29848 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js alex 29855 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js alex 29875 29828 0 15:31 ? 00:00:00 node /.../src/app_cjs.js alex 30703 29828 7 15:35 ? 00:00:00 node /.../src/app_cjs.js
Но ES6-версия приложения не отрабатывает корректно в pm2
. При передаче в nodejs аргумента «—experimental-modules»:
$ pm2 start ./src/app_es6.mjs -i 4 --node-args="--experimental-modules"
получается вот такая картина:
В логах видим:
$ pm2 log ... /home/alex/.pm2/logs/app-es6-error-2.log last 15 lines: 2|app_es6 | at /usr/lib/node_modules/pm2/node_modules/async/internal/onlyOnce.js:12:16 2|app_es6 | at WriteStream.<anonymous> (/usr/lib/node_modules/pm2/lib/Utility.js:186:13) 2|app_es6 | at WriteStream.emit (events.js:210:5) 2|app_es6 | at internal/fs/streams.js:299:10 2|app_es6 | Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /home/alex/work/sof_es6_pm/src/app_es6.mjs 2|app_es6 | at Object.Module._extensions..mjs (internal/modules/cjs/loader.js:1029:9) 2|app_es6 | at Module.load (internal/modules/cjs/loader.js:815:32) 2|app_es6 | at Function.Module._load (internal/modules/cjs/loader.js:727:14) 2|app_es6 | at /usr/lib/node_modules/pm2/lib/ProcessContainer.js:297:23 2|app_es6 | at wrapper (/usr/lib/node_modules/pm2/node_modules/async/internal/once.js:12:16) 2|app_es6 | at next (/usr/lib/node_modules/pm2/node_modules/async/waterfall.js:96:20) 2|app_es6 | at /usr/lib/node_modules/pm2/node_modules/async/internal/onlyOnce.js:12:16 2|app_es6 | at WriteStream.<anonymous> (/usr/lib/node_modules/pm2/lib/Utility.js:186:13) 2|app_es6 | at WriteStream.emit (events.js:210:5) 2|app_es6 | at internal/fs/streams.js:299:10
То есть, по факту pm2
не может без транспиляции запускать скрипты, в которых используются ES6-модули. Последний issue на эту тему создан 5 декабря 2019 (примерно месяц назад).
forever
forever
является следующим по популярности менеджером процессов после pm2
(npmtrends). Это более старый проект (начат в 2010 году против 2013 для pm2
), но у него более узкий фокус по функциональности, чем у pm2
. forever
«заточен» на постоянное поддержание работоспособности процесса без всяких дополнительных pm2
-плюшек в виде балансировки нагрузки и мониторинга используемых ресурсов. Судя по частоте коммитов проект находится в стабильном состоянии (фазу активного развития уже прошёл) и каких-то новых функций от него ждать не приходится. Я не нашёл способа передачи аргументов в nodejs
из командной строки при запуске forever
, но такая возможность есть, если использовать конфигурационный файл:
{ "uid": "app_es6", "max": 5, "spinSleepTime": 1000, "minUptime": 1000, "append": true, "watch": false, "script": "src/app_es6.mjs", "command": "node --experimental-modules" }
Запуск приложения в таком варианте выглядит так:
$ forever start forever.es6.json ... $ forever list info: Forever processes running data: uid command script forever pid id logfile uptime data: [0] app_es6 node --experimental-modules src/app_es6.mjs 3972 3979 /home/ubuntu/.forever/app_es6.log 0:0:0:3.354
Вот сами процессы:
$ ps -Af | grep es6 ubuntu 3972 1 0 12:01 ? 00:00:00 /usr/bin/node /usr/lib/node_modules/forever/bin/monitor src/app_es6.mjs ubuntu 3979 3972 0 12:01 ? 00:00:00 node --experimental-modules /home/ubuntu/sof_es6_pm/src/app_es6.mjs
При «убийстве» процесса (PID 3979
) менеджер исправно поднимает новый (PID 4013
):
$ kill -s SIGKILL 3979 ubuntu@vsf:~/sof_es6_pm$ ps -Af | grep es6 ubuntu 3972 1 0 12:01 ? 00:00:00 /usr/bin/node /usr/lib/node_modules/forever/bin/monitor src/app_es6.mjs ubuntu 4013 3972 4 12:10 ? 00:00:00 node --experimental-modules /home/ubuntu/sof_es6_pm/src/app_es6.mjs
forever
прекрасно справляется с запуском приложения, использующего ES6-модули, но возникает вопрос, зачем тянуть на linux-системы forever
, если подобной функциональности можно добиться и через средства самой ОС?
systemd
systemd позволяет создавать сервисы в linux-среде и контролировать их запуск, в том числе и в случае их внезапного падения. Достаточно создать unit-файл с описанием сервиса (./app_es6.service
):
[Unit] Description=Simple web server with ES6 modules. After=network.target [Service] Type=simple Restart=always PIDFile=/run/app_es6.pid WorkingDirectory=/home/ubuntu/sof_es6_pm ExecStart=/usr/bin/nodejs --experimental-modules /home/ubuntu/sof_es6_pm/src/app_es6.mjs [Install] WantedBy=multi-user.target
и залинковать его в каталог /etc/systemd/system
(в unit-файле пути должны быть абсолютными). За рестарт сервиса в случае его внезапного останова отвечает опция:
Restart=always
Запуск сервиса осуществляется так:
# systemctl start app_es6.service # systemctl status app_es6.service ● app_es6.service - Simple web server with ES6 modules. Loaded: loaded (/home/ubuntu/sof_es6_pm/app_es6.service; linked; vendor preset: enabled) Active: active (running) since Thu 2020-01-02 11:09:42 UTC; 9s ago Main PID: 2184 (nodejs) Tasks: 11 (limit: 4662) CGroup: /system.slice/app_es6.service └─2184 /usr/bin/nodejs --experimental-modules /home/ubuntu/sof_es6_pm/src/app_es6.mjs Jan 02 11:09:42 vsf systemd[1]: Started Simple web server with ES6 modules.. Jan 02 11:09:42 vsf nodejs[2184]: (node:2184) ExperimentalWarning: The ESM module loader is experimental. Jan 02 11:09:42 vsf nodejs[2184]: ES6 app listening on port 3000!
При «убийстве» процесса (PID 2184
) systemd
исправно поднимает новый (PID 2233
):
# ps -Af | grep app_es6 root 2184 1 0 11:09 ? 00:00:00 /usr/bin/nodejs --experimental-modules /home/ubuntu/sof_es6_pm/src/app_es6.mjs # kill -s SIGKILL 2184 # ps -Af | grep app_es6 root 2233 1 3 11:10 ? 00:00:00 /usr/bin/nodejs --experimental-modules /home/ubuntu/sof_es6_pm/src/app_es6.mjs
Т.е., systemd
делает то же самое, что и forever
, но на более фундаментальном уровне.
StrongLoop
При обзоре вариантов имплементаций менеджеров процессов часто всплывает StrongLoop. Однако очень сильно похоже, что этот проект перестал развиваться (последняя версия 6.0.3 вышла 3 года назад). Мне не удалось его даже установить на Ubuntu 18.04 через npm
:
# npm install -g strongloop npm WARN deprecated swagger-ui@2.2.10: No longer maintained, please upgrade to swagger-ui@3. ... npm ERR! A complete log of this run can be found in: npm ERR! /root/.npm/_logs/2020-01-02T11_25_15_473Z-debug.log
Через yarn
пакет установился, несмотря на большое количество сообщений о deprecated версиях зависимостей и ошибок установки, тем не менее, от изучения StronLoop’а я отказался.
Инструменты разработчика
Очень часто рядом с pm2
и forever
встречаются такие пакеты, как nodemon, watch, onchange. Эти инструменты не являются менеджерами процессов, но позволяют мониторить изменения в файлах и выполнять команды, привязанные к этим изменениям (в том числе, и перезапускать приложение).
Резюме
Менеджер процессов, подобный pm2
, является очень полезным сервисом в мире серверного JS. Но, к сожалению, сам pm2
не позволяет запускать современные nodejs
-приложения (в частности — с ES6-модулями). Так как я не очень люблю транспиляцию, то наиболее приемлемым на данный момент менеджером процессов в nodejs
для меня является традиционный systemd
(или его альтернативы). Однако я с радостью буду использовать pm2
, как только pm2
сможет поддерживать приложения с ES6-модулями.
ссылка на оригинал статьи https://habr.com/ru/post/482370/
Добавить комментарий