Pull to refresh

PM2: подходим к вопросу процесс-менеджмента с умом

Reading time5 min
Views65K
Буквально пару часов назад у меня завязался спор на тему того, что Node.JS слишком медленная для крупных проектов и ей стоит предпочесть Golang, Rust, PHP, etc. Основным аргументом противоположной стороны в этом споре был факт однопоточности JavaScript. Якобы при разработке приложения производительность просто упрётся в эту однопоточность и ничего сделать уже нельзя — только переписать на каком-то другом языке. Однако дела с этим в NodeJS обстоят немного лучше, чем кажется на первый взгляд. Перед тем, как мы углубимся в эту тему хочу заявить, что уважаю право каждого разработчика использовать тот язык программирования, который пришёлся ему по душе и который он считает предпочтительным в той или иной задаче.

Сделав поиск по ключевому слову «PM2» на Хабре я не нашёл ни одной статьи, посвящённой этому process-менеджеру. Лишь одиночные упоминания в статьях других пользователей. Я загорелся (сильно сказано) идеей наверстать упущенное и пролить свет на этот тёмный уголок разработки backend на Node.JS (о котором многие знают, да, я в курсе). Всех заинтересовавшихся прошу под кат.



Пару слов о самом PM2



PM2 — это менеджер процессов с открытым исходным кодом, распространяющийся под лицензией AGPL-3.0. В момент написания статьи имеет ~350k загрузок в неделю, согласно данным NPM. В основном применяется в средах, где необходимо запустить приложение на NodeJS и забыть о нём (с остальными языками тоже можно использовать, но об этом позднее), позволяющий кластеризировать приложение и гибко распределять нагрузку между ядрами процессора. Небольшая вырезка из репозитория PM2 на GitHub:

PM2 is a production process manager for Node.js applications with a built-in load balancer. It allows you to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks.


Многие новички при разработке сталкиваются с проблемой, когда после «выкатки» приложения на production сервер, не знают как запустить его «навечно». Пишут в SSH-консоли set NODE_ENV=production && node app.js, всё отлично, приложение работает. Закрывают консоль и приложение больше не работает. Вопрос на StackOverflow — How to run node.js application permanently? набрал более 237 тыс. просмотров за всё время.

PM2 решает эту проблему одной командой:

pm2 start app.js


Эта команда «демонизирует» (от англ. «daemonize») процесс NodeJS, следит за потреблением им памяти и считает нагрузку на процессор.

Вернёмся к нашим баранам



С ростом нагрузки на backend возникает необходимость его масштабирования — как вертикального, так и горизонтального — кому что удобнее в сложившихся обстоятельствах. Как мы знаем, один процесс может использовать несколько ядер процессора, но только в том случае, если внутри процесса имеется несколько потоков. В NodeJS приложениях поток — один. PM2 способен выручить в этой ситуации и распределить нагрузку между несколькими ядрами процессора. По-прежнему всего с одной командой:

pm2 start app.js -i max


В данном случае параметр max соответствует количеству ядер процессора. Т.е. для 8-ядерного процессора будет создано 8 отдельных процессов. Можно также вместо max задать значение -1 и тогда количество процессов будет соответствовать количество_ядер минус 1. Вся прелесть заключается в том, что и HTTP(S)/Websocket/TCP/UDP соединения будут равномерно распределены между этими процессами. Ну чем не горизонтальное масштабирование? Почитать подробнее о кластеризации в PM2 можно по ссылке — PM2 Cluster Mode.

cluster_mode

Вы можете запустить сколько угодно процессов, но всё же рекомендуется придерживаться рекомендации «один процесс на одно ядро».

Бережное отношение к памяти


При разработке на PHP я однажды столкнулся с проблемой. По неопытности неосознанно заложил в движок системы баг, из-за которого при определённых условиях процессы начинали поедать слишком много оперативной памяти. Вдобавок к этому нагружался процессор, из-за чего виртуальная машина просто зависла и у меня не было к ней доступа совсем.
Как знают PHP-разработчики, в PHP-FPM можно задать тип распределения процессов (если вы вдруг не знали, то в PHP-FPM для каждого нового запроса создаётся новый процесс) — статический, когда задаётся минимальный и максимальный порог, и динамический — выделение сколь угодно большого количества процессов, по необходимости. Что будет в PM2, если запустить 8 процессов и все они начнут потреблять много памяти? И эту проблему PM2 в состоянии решить — лишь одним параметром в командной строке:

# Set memory threshold for app reload
pm2 start app.js -i max --max-memory-restart <200MB>


Каждый раз при достижении лимита по памяти PM2 автоматически перезапустит процесс. Распределять память проще чем процессы, не так ли? 8 процессов * 200 мегабайт = 1,6 гигабайт. Математика уровня второго класса.

Помимо перезапуска процесса можно также настроить и перезапуск через N интервал времени. Я пока не придумал в каких случаях это может пригодиться, но не стесняйтесь указать мне на пару примеров в комментариях :)

А если я перезагружу виртуальную машину?


Сюрприз-сюрприз! Эту проблему PM2 тоже решает за вас. Всё ещё не более чем одной единственной командой в консоли:

pm2 startup


PM2 сгенерирует скрипт, который будет поднимать все необходимые процессы при запуске операционной системы. Однако здесь стоит быть бдительным — при обновлении версии Node.JS всё может сломаться. Во избежание этого, после успешного обновления до новой версии Node.JS выполните pm2 unstartup и pm2 startup. Подробнее об этом можно ознакомиться по ссылке — PM2 Startup Script Generator.

А надо ли перезапускать кластеры вручную при внесении изменений?


Конечно же нет! Ну, точнее, вы, конечно, можете перезапускать приложение вручную, но зачем? Автоматизируйте всё что можете и да прибудет с вами сила!

pm2 start env.js --watch --ignore-watch="node_modules"


Вы можете пользоваться этим при слиянии master-ветки в локальном репозитории с master-веткой из удалённого репозитория. В моём сайд-проекте это делается просто — git pull origin master && npm run build. При изменении файлов в папках server/build и client/build процессы будут автоматически перезапущены. Я понимаю, это очень простенькая фича и она не заслуживает даже быть упомянутой в этом тексте. Разбавлю его кое-чем серьёзным и напишу о том, что если вы пользуетесь кластеризацией, то все процессы будут перезагружены поочерёдно. Да так, что как минимум один из них будет всегда доступен. Это же zero-downtime deployment!

А можно и не перезапускать процессы. Для этого есть reload (нечто похожее на nginx reload):

pm2 reload all


Слишком много команд! И вообще я предпочитаю конфиги


Мне уже наскучило придумывать весёлые фразы, поэтому просто и банально: файл экосистемы — есть. Поддерживаются форматы JSON, YAML и JS. Например, когда необходимо следить за файлами в папках server и client:

module.exports = {
  apps: [{
    script: "app.js",
    watch: ["server", "client"],
    env_production : {
      "NODE_ENV": "production"
    }
  }]
}


Подробнее ознакомиться можно по ссылке — PM2 Application Declaration.

И даже мониторинг есть!


И не один. Выбирайте тот который нравится больше. Можно мониторить в консоли командой:

pm2 monit




Или же воспользоваться полноценной веб-версией мониторинга:



Вы мне, конечно же, не поверите, но она устанавливается и запускается одной командой:

pm2 plus


И многое-многое другое...


Заявлена поддержка Heroku и Docker, автоматическое инкрементирование портов с возможностью передачи в process.env (когда нужно каждый процесс запускать на отдельном порту), запуск нескольких инстансов PM2 в пределах одной ОС, наличие программного API и возможность запускать демонизированные Bash и Python скрипты!

Вероятно, я упустил ещё что-то важное или интересное, о чём всегда можно напомнить мне в комментариях. Надеюсь, что вы смогли почерпнуть для себя что-то новое из этой статьи.
Tags:
Hubs:
Total votes 17: ↑16 and ↓1+15
Comments12

Articles