为什么要写这篇文章

最近利用 ant-design-pro 结合 koa2 搭建了一套后台管理系统,用于管理个人项目。由于需要将 koa2 代码部署到服务器上,很自然地就想到了用 pm2 来发布和管理。其实自己之前是有在工作中使用 pm2 的,但是我都是在别人搭建好的环境下进行使用,没有很好地领会精髓。在这次部署个人项目的实践中,自己从头到尾把控整个部署流程,所以收获是有很多的,因此才会准备写这篇文章,将自己的所学所知整理成文,既方便自己以后进行回顾,也可以帮助想要了解这块知识的朋友。

初识 pm2

首先,我们要知道 pm2 是什么。pm2 是一个 node 应用的监控管理工具,具有如下的功能:

  1. 以守护进程的方式将我们的 node 应用运行在后台
  2. 提供简便的方式启动或停止 node 应用
  3. 监控 node 应用的运行状态,并提供当 node 应用崩溃时自动重启的功能(不用手动node app.js啦)
  4. 开启 cluster 模式后,会采用负载均衡的策略将 node 应用跑在多个 cpu 上,从而充分发挥多核 cpu 的性能
  5. 配合 ecosystem.config.js,可以方便地进行一键部署

pm2 常用的命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Fork mode
pm2 start app.js --name my-app #启动一个node应用,并将其命名为my-app

# Cluster mode
pm2 start app.js -i 0 # 启动一个node应用,并开启cluster模式,使用的cpu数量取决于主机cpu的可用核数
pm2 start app.js -i max # 跟上述命令效果一致,不推荐使用

# Listing
pm2 list # 以表格的形式列出当前所有node应用的信息
pm2 jlist # 以原生JSON的形式列出当前所有node应用的信息
pm2 prettylist # 以美化过的JSON形式列出当前所有node应用的信息
pm2 describe 0 # 列举出id为0的应用的所有信息

# Logs
pm2 logs [--raw] # 以流的形式列举出所有node应用的日志
pm2 flush # 清空所有日志文件
pm2 reloadLogs # 重载所有日志文件

# Actions
pm2 stop all # 停止所有node应用
pm2 restart all # 重启所有node应用
pm2 reload all # 重载所有node应用,与restart不同的是这种方式可以实现不停机更新的效果
pm2 stop 0 # 停止id为0的node应用
pm2 restart 0 # 重启id为0的node应用
pm2 delete 0 # 删除id为0的node应用,pm2 list上就不会再出现了
pm2 delete all # 删除所有node应用

使用 pm2 部署 node 应用

除了上述直接在命令行中操作 pm2,更常见的使用方式是使用约定的配置文件(ecosystem.config.js)来管理。通过这个文件,我们可以简单高效的管理和发布多个 node 应用,下面将列出常见的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
module.exports = {
/*
apps是个数组,里面可以存放多个node应用实例的配置
*/
apps: [
{
/*
指定node应用的名称
*/
name: "my-app1",
/*
指定node应用的入口文件
*/
script: "./src/index1.js",
/*
开启cluster集群模式
*/
exec_mode: "cluster",
/*
根据cpu可用核数运行最大数量的node应用实例
*/
instances: "0",
/*
开启监听模式,任意文件内容发生改变,都会重启node应用
*/
watch: true,
/*
监听模式下,忽略的文件列表
*/
ignore_watch: [],
env: {
/*
默认的环境变量
*/
NODE_ENV: "production",
MODE: "default",
},
env_production: {
/*
生产环境变量,需要在命令行中添加参数 --env production
*/
NODE_ENV: "production",
MODE: "pro",
},
env_pre_production: {
/*
预生产环境变量,需要在命令行中添加参数 --env pre_production
*/
NODE_ENV: "pre_production",
MODE: "pre",
}
},
{
name: "my-app2",
script: "./src/index2.js",
/*
默认的模式,只会在单核上运行node应用,无法充分利用多核cpu的性能,一般不推荐使用
*/
exec_mode: "fork",
env: {
NODE_ENV: "production",
MODE: "default",
},
env_production: {
NODE_ENV: "production",
MODE: "pro",
},
env_pre_production: {
NODE_ENV: "pre_production",
MODE: "pre",
}
}
],
/*
部署相关的配置
*/
deploy: {
/*
生产环境的部署,部署的方式以deploy的key作为区分
*/
production: {
/*
服务器用户名
*/
user: "root",
/*
服务器ip地址
*/
host: "8.8.8.8",
/*
需要拉取的仓库分支
*/
ref: "origin/main",
/*
仓库地址
*/
repo: "git@github.com:test/test.git",
/*
需要发布到服务器的路径
*/
path: "/var/test",
/*
在安装进程启动之前需要执行的命令
*/
"pre-setup": "",
/*
拉取代码之后需要执行的指令
*/
"post-setup":
"npm install && pm2 start ecosystem.config.js --env production",
/*
部署之前的钩子,暂不清楚与'post-deploy'的区别,共同点是都可以进行reload的操作
*/
"pre-deploy": "",
"post-deploy": "pm2 reload ecosystem.config.js --env production"
},
/*
预生产环境的部署,部署的方式以deploy的key作为区分
*/
pre_production: {
user: "root",
host: "8.8.8.8",
ref: "origin/main",
repo: "git@github.com:test/test.git",
path: "/var/test",
"pre-setup": "",
"post-setup":
"npm install && pm2 start ecosystem.config.js --env pre_production",
"pre-deploy": "",
"post-deploy": "pm2 reload ecosystem.config.js --env pre_production"
}
}
}

使用 pm2 部署的完整指令是pm2 deploy <configuration_file> <environment> <command>,默认的就是 ecosystem.config.js,所以可以省略该参数。就是 deploy 里面的 key,可以区分不同的发布环境,指定需要执行的指令。
例如要发布到预生产环境上,执行的指令是pm2 deploy pre_production。这里需要注意的是,如果是第一次发布,需要执行pm2 deploy pre_production setup,从而走pre-setuppost-setup钩子,执行我们设置的初始化安装命令。还有一点需要注意的是由于存在拉仓库代码的行为,所以我们需要为服务器配置 ssh 免密登录,这样才能保证成功地拉下代码。
在发布完成后,我们可以登录到服务器上,输入pm2 list,查看 node 应用的运行状态,如果是online,那么恭喜你,已经部署成功啦!

结语

ci/cd 这一套流程其实会涉及很多知识,虽然在实践的过程中,会遇到很多问题,但是只有通过不断的实践,才能真正领悟其中的精髓。未来我还会尝试更多的 ci/cd 工具,同时也会把自己学到的经验整理成文分享出来,帮助需要的人。