PM2 是 Node.js 的 app server,就好比 Python 的 Uvicorn 或 Gunicorn,以及 Ruby 的 Passenger,PHP 的 PHP-FPM。
這些 app server 負責調度與監管 app,這裡的 app 就是我們撰寫的專案或程式,app server 通常作為系統服務,當它跑起來之後也會把我們的 app 叫起來,並且視負載的輕重決定要開幾隻 app 程序來消化負載,也負責在 app 出錯時把 app 重啟,app server 在正式環境是影響一款應用能否穩定服務的重要角色,而 PM2 是 Node.js 生態中主流的 app server 之一。
安裝
雖然 PM2 也是 Node.js 套件,但它作為 app server,我們視其與 web server 為類似的角色,通常會全域安裝,而不是安裝在專案內,安裝指令:
$ npm install pm2 -g
安裝完執行看看有沒有成功:
$ pm2
初次執行應該會輸出以下訊息:
__/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
_\/\\\/////////\\\_\/\\\\\\________/\\\\\\__/\\\///////\\\___
_\/\\\_______\/\\\_\/\\\//\\\____/\\\//\\\_\///______\//\\\__
_\/\\\\\\\\\\\\\/__\/\\\\///\\\/\\\/_\/\\\___________/\\\/___
_\/\\\/////////____\/\\\__\///\\\/___\/\\\________/\\\//_____
_\/\\\_____________\/\\\____\///_____\/\\\_____/\\\//________
_\/\\\_____________\/\\\_____________\/\\\___/\\\/___________
_\/\\\_____________\/\\\_____________\/\\\__/\\\\\\\\\\\\\\\_
_\///______________\///______________\///__\///////////////__
Runtime Edition
PM2 is a Production Process Manager for Node.js applications
with a built-in Load Balancer.
Start and Daemonize any application:
$ pm2 start app.js
Load Balance 4 instances of api.js:
$ pm2 start api.js -i 4
Monitor in production:
$ pm2 monitor
Make pm2 auto-boot at server restart:
$ pm2 startup
To go further checkout:
http://pm2.io/
-------------
usage: pm2 [options] <command>
pm2 -h, --help all available commands and options
pm2 examples display pm2 usage examples
pm2 <command> -h help on a specific command
Access pm2 files in ~/.pm2
上面的簡短介紹也差不多把 PM2 的基礎用法講完了,本文完。
事情當然沒有那麼簡單,注意到上面訊息的最末一句「Access pm2 files in ~/.pm2」,就先去看裡面有哪些東西吧。
~/.pm2/ 裡面長這樣:
.pm2/
├── logs/
├── module_conf.json
├── modules/
├── pids/
└── touch
目前裡面大多是空的,比較要留意的只有那 logs/,未來要排查問題時記得要來這裡找紀錄。
上面輸出訊息提示了一些 PM2 CLI 的用法,但在生產環境,我們會希望把配置寫成檔案,畢竟 CLI 參數總是會忘記,還是以配置檔的形式運作比較保險也可以納入版控管理。
在專案根目錄下執行下面指令產生基礎的配置文件:
$ cd ~./Porjects/liveboard/
$ pm2 init
產出的檔名為 ecosystem.config.js,內如如下:
module.exports = {
apps: [
{ script: 'index.js', watch: '.' },
{ script: './service-worker/', watch: ['./service-worker'] },
],
deploy: {
production: {
user: 'SSH_USERNAME',
host: 'SSH_HOSTMACHINE',
ref: 'origin/master',
repo: 'GIT_REPOSITORY',
path: 'DESTINATION_PATH',
'pre-deploy-local': '',
'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production',
'pre-setup': ''
}
}
}
內容相當簡單,大多可望文生義,apps
區塊定義了兩個 Node.js app,deploy
區塊則是一些遠端部署用的參數與指令。
在 apps
區塊方面,所有的配置項目可以參考 PM2 文件〈Configuration File〉,這裡不重複說明,下面是本人常用的配置:
apps: [
{
// General
name: 'liveboard',
script: './build/index.js',
port: 4000,
// Advanced features
instances: 2,
env: { NODE_ENV: "development" },
env_production: { NODE_ENV: "production" },
env_staging: { NODE_ENV: "staging" },
env_development: { NODE_ENV: "development" },
// Log files
time: true,
},
]
裡面的 name
、port
就是字面上的意思,下面的 instance
自然就是這支 liveboard app 要開的程序數量了,透過多個程序來增加服務的容納量,這裡 PM2 會用 Node.js 的 cluster 模式來管理程序,那對外的 port 4000 由所有的 liveboard instance 共享,不會發生搶 port 的問題。
再往下是幾組 env
開頭的配置,這裡的 env_xxx
是可以自由制定的,裡面當然就是放環境變數啦,而不帶任何後綴的 env
則是預設的環境變數。
最後有個 time: true
,就只是把 log 加上時間戳。
配置好就可以跑起來了:
$ pm2 start ecosystem.config.js
查看納管 app 狀態:
$ pm2 list
輸出如下:
⇆ PM2+ activated
┌────┬───────────┬───────────┬─────────┬─────────┬───────┬────────┬───┬────────┬─────┬────────┬──────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼───────────┼───────────┼─────────┼─────────┼───────┼────────┼───┼────────┼─────┼────────┼──────┼──────────┤
│ 0 │ liveboard │ default │ 0.0.1 │ cluster │ 11784 │ 63m │ 0 │ online │ 0% │ 32.9mb │ user │ disabled │
│ 1 │ liveboard │ default │ 0.0.1 │ cluster │ 16076 │ 63m │ 0 │ online │ 0% │ 32.0mb │ user │ disabled │
└────┴───────────┴───────────┴─────────┴─────────┴───────┴────────┴───┴────────┴─────┴────────┴──────┴──────────┘
還有一些常用命令:
$ pm2 restart app_name
:重啟 app。$ pm2 stop app_name
:停止 app。$ pm2 delete app_name
:終止 app。$ pm2 logs app_name
:顯示 app log 訊息。
另外 PM2 也可以產生 systemd 的服務配置檔,以普通用戶身分執行:
$ pm2 startup
它會輸出:
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u mes --hp /home/mes
依照吩咐執行:
$ sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u mes --hp /home/mes
輸出訊息如下:
[PM2] Init System found: systemd
Platform systemd
Template
[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target
[Service]
Type=forking
User=mes
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/home/mes_staging/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/home/mes/.pm2
PIDFile=/home/mes/.pm2/pm2.pid
Restart=on-failure
ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill
[Install]
WantedBy=multi-user.target
Target path
/etc/systemd/system/pm2-mes.service
Command list
[ 'systemctl enable pm2-mes' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-mes.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-mes...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-mes.service → /etc/systemd/system/pm2-mes.service.
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save
[PM2] Remove init script via:
$ pm2 unstartup systemd
如訊息所表示,它幫我們生成一份 systemd 服務配置檔案,也幫我們啟用了,如此以後重開機就會自動把 PM2 服務跑起來。
接著我們要做的是,告訴 PM2 它要把哪些 app 叫起來。延續前面的例子,先確定要跑的 app 有在運行中:
$ pm2 start ecosystem.config.cjs
$ pm2 list
輸出如下:
⇆ PM2+ activated
┌────┬───────────┬───────────┬─────────┬─────────┬───────┬────────┬───┬────────┬─────┬────────┬──────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼───────────┼───────────┼─────────┼─────────┼───────┼────────┼───┼────────┼─────┼────────┼──────┼──────────┤
│ 0 │ liveboard │ default │ 0.0.1 │ cluster │ 11784 │ 63m │ 0 │ online │ 0% │ 32.9mb │ user │ disabled │
│ 1 │ liveboard │ default │ 0.0.1 │ cluster │ 16076 │ 63m │ 0 │ online │ 0% │ 32.0mb │ user │ disabled │
└────┴───────────┴───────────┴─────────┴─────────┴───────┴────────┴───┴────────┴─────┴────────┴──────┴──────────┘
把目前運行中的 app 保存作為 PM2 服務啟動時要帶起來的 app:
$ pm2 save
輸出如下:
[PM2] Saving current process list...
[PM2] Successfully saved in /home/mes/.pm2/dump.pm2
這個 dmp.pm2 是一份 JSON 文件,裡面紀錄一大堆前面兩個 app instance 的參數。
最後,當然就是重開機一波確認服務是否如常運行。
最後,因為 PM2 的角色是 app server,在正式環境通常還會在 app server 之前擺一個負責反向代理的 web server,最典型的就是 NGINX,由 NGINX 負責對外提供服務,再把部分請求轉交給對應的 app server,關於 NGINX 與 PM2 的轉發配置,可以參考 PM2 的〈Production Setup with Nginx〉,而如果是 Caddy,那可以參考以下 caddyfile 配置:
http:// {
handle_path /* {
reverse_proxy :4000
}
}
上面配置會把所有的請求都轉發到 4000 埠,也就是 PM2 納管監聽的埠,至於轉發要帶上的那些 HTTP 標頭,X-Forwarded-For
、X-Real-IP
什麼的,Caddy 原本就會帶上,無須額外配置,GJ。