使用 pm2 管理应用

pm2 是使用 node.js 开发的进程管理器,实现统一方式管理进程,如:崩溃后拉起、启动/停止、监控、日志管理等。

安装

npm install pm2@latest -g

为什么要全局(global)方式安装 pm2?

pm2 被设计成管理用户的全部应用,pm2 的数据保存在 ~/.pm2 目录下,同一用户只能启动一个 pm2 后台进程(PM2 daemon),不同用户的 pm2 互不影响。不安装为全局的情况下,如果安装多个版本的 pm2,不同版本的 pm2 前端工具程序与 pm2 后台进程(PM2 daemon)交互是有风险的。

应用管理

  • 启动应用

    pm2 start -n app1 app1.js
    pm2 start -n app2 app2.js
    
  • 列出应用

    pm2 list
    
  • 应用详情

    pm2 describe app1
    
  • 停止应用

    pm2 stop app1
    pm2 stop app2
    
  • 删除应用

    pm2 delete app1
    pm2 delete app2
    

开机启动

应用启动后需要保存,应用才会在开机后由 pm2 服务启动。

pm2 save

创建 pm2 系统服务,开机启动 pm2

sudo pm2 startup systemd -u app

不重启试运行一下,看是否正常

# 清空进程并退出 pm2,回到干净的系统状态
sudo systemctl stop pm2
ps aux | grep node

# 启动 pm2 服务,验证一下应用是否正常启动
sudo systemctl start pm2
pm2 list

日志管理

pm2的日志管理 》有详细描述。

多 node.js 版本共存

pm2 本身就是 node.js 开发的程序,依赖 node.js,pm2 应用可以使用不同版本的 node.js。

pm2 命令行工具会通过“#!/usr/bin/env node”方式引用 node,如果应用也以同样的方式引用 node.js,就要随时注意切换 node.js 版本,一不小心 pm2 命令行工具和 pm2 应用使用的 node.js 版本会错乱,有一定风险性。一个 node.js 版本安装的模块不能保证与另一个 node.js 版本兼容,特别是一些 c++ 扩展模块。

我以前的实践中,应用会提供一个环境脚本 .bashrc ,在操作某个应用时,总是会通过 source .bashrc 先设置应用的 shell 环境变量,通过 $PATH 环境变量指定 node 命令为应用所需的 node.js 版本不是一个好主意,当操作 pm2 时,pm2 也会使用这个应用的 node.js 版本。

从这一点上看,不应该使用 node.js 、php、python、ruby 之类的脚本语言来开发进程管理器,它本身的依赖管理就是个大麻烦,使用 go、c 或 c++ 来开发会好得多。

理想情况下,pm2 和 应用(app)总是使用正确的 node.js 版本,可以归为以下三种情形。新应用应该总是假设布署环境为情形 1,不要过多考虑系统运行的 node.js 版本,这也就要求应用能够兼容各种 node.js 版本,但是现实情况是,node.js 以及 javascript 发展得太快了,应用依赖的各种 node.js 模块也往往做不到兼容各种 node.js 版本,很多模块基于实现的简洁性考虑,提供多个版本分别对应不同的 node.js 版本,导致应用也必须从一开始就选择特定的 node.js 版本,不同团队、人员及项目跟进新技术步调不一致时,情形 2 及情形 3 是现实的选择。

情形 1:系统中只有一个 node.js 版本,并且是全局安装

在专机专用的生产环境下,这种情形会很常见,特别是 docker 容器环境下。

这是最简单的一种情况,不需要为 node.js 版本操心,整个开发组织在 node.js 版本选择上共进退,保持一致。

情形 2:系统中有一个全局 node.js 版本,应用有自已的 node.js 版本

开发环境下,或者同一机器部署大量微服务的情况下,一般就是这种情形。

这是最复杂的一种情况,在运行应用代码的时候,要确保切到应用所需的 node.js 版本,在执行 pm2 操作的时候,要确保切到 pm2 所需的 node.js 版本,有如履薄冰的感觉。

node.js 版本需要在以下方面正确匹配:

  • pm2 的 node.js 版本

    pm2 本身就是一套用户全局的进程管理工具,使用全局的 node.js 版本是自然而然的选择。

    否则就一定要记得使用正确的 node.js 版本运行 pm2:/usr/local/node-v5.0.0/bin/node pm2 list,很是不便。

  • 应用的 node.js 版本

    建议使用 --interpreter 选项指定 node.js 版本,参见讨论:Using different versions of node via nvm for each app · Issue #1034 · Unitech/pm2

    警告:pm2 在 cluster 模式下, --interpreter 选项被忽略,详见:Module version mismatch 错误排查 | 看看俺 – KanKanAn.com

    这是最关键的一点,应用的 node.js 版本不对,可能导致应用启动失败,中断服务。

  • 应用的辅助脚本的 node.js 版本

    使用 node.js 开发的应用附带命令行工具运行时如果 node.js 版本不对,通常不会对运行中的服务造成影响。

    可以简单地写一些 shell 脚本封装,在 shell 脚本中指定正确的 node.js 版本,如:

    dump.sh

    #!/bin/bash
    
    /usr/local/node-v5.0.0/bin/node ./dump.js
    

    也可以直接在 node.js 脚本中引用正确的 node.js 版本,如:

    dump.js

    #!/usr/local/node-v5.0.0/bin/node
    
    var fs = require('fs');
    ...
    
    chmod a+x dump.js
    ./dump.js
    

情形 3:系统没有全局 node.js 版本,应用各自维护 node.js 版本

这是上面情况的简化版,考验开发、运维团队的纪律性。

由于 $PATH 中没有 node.js,不会由于没有指定 node.js 绝对路径无意间引用错误的 node.js 版本。

可以把 node.js 安装在应用根目录下,如下目录结构所示:

Applications
|
|
|--- Application 1
|         |
|         |--------- node
|         |
|         |--------- package.json
|         |
|         |--------- ...
|
|    
|--- Application 2
|         |
|         |--------- node
|         |
|         |--------- package.json
|         |
|         |--------- ...
|
|
|--- Application 3
|         |
|         |--------- node
|         |
|         |--------- package.json
|         |
|         |--------- ...
|

甚至 pm2 也通过以上方式安装自已的 node.js 版本。

通过 ./node/bin/node 引用 node.js 可执行程序,不要试图通过将 ./node/bin 目录加到 $PATH 中以简化使用,否则操作不同应用或 pm2 时,又会一不小心引用到错误的 node.js 版本。