Skip to main content

部署

记录从零一步步将 Nest.js 项目部署到 Linux 服务器的过程。


配置 ssh 免密登录服务器

  1. 如果本机没有生成过 sshkey,那么就新生成一个,执行以下指令,然后一直 Enter
ssh-keygen
  1. 执行指令,将密钥添加到远程服务器的授权配置中:
ssh-copy-id user@host
tip
  1. 测试密钥登录:
ssh user@host

给 github action 配置服务器密钥访问

因为需要服务器上直接从 github 上拉取代码,本地编译并启动服务器,所以需要有服务器有访问代码仓库的权限。

  1. ssh 连接服务器,执行ssh-keygen生成密钥,并将公钥配置到 github 的私人密钥中
tip

本机公钥默认地址:~/.ssh/id_rsa.pub, github 配置密钥入口:个人/Settings/SSH and GPG keys

  1. 将刚才生成的私钥配置到仓库的secrets中。取名为SSH_PRIVATE_KEY,这个变量定义将在 github action 中用到
tip

本机私钥默认地址:~/.ssh/id_rsa, github 项目仓库配置secrets入口:仓库Settings/Secrets/New repository secret

  1. 将远程服务器的地址也配置到仓库的Secrets中,取名为SERVER_HOST,github action 将会用到

服务器代码编译

  1. 安装 git

linux 默认不安装 git,需要手动安装:

yum install git
  1. 复制 github 上项目的 ssh 访问地址,在服务器中拉取代码:
# 创建项目存放的文件夹
mkdir ~/data/server
cd ~/data/server
git clone git@github.com:daichangxin/xxx.git

配置 node 环境

使用n来管理 node 环境。

  1. 安装n
# 创建一个放安装包的文件夹
mkdir ~/data/installs
curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n
bash n lts

这样 node 环境就安装好了,为了方便以后全局使用n,再执行下全局安装n

npm i n -g
  1. 安装pm2来管理服务器进程
npm i pm2 -g

github action 配置

在仓库目录下创建 ci 配置文件:.github/workflows/ci.yml,内容:

name: Deploy Server

on:
push:
branches:
- master

jobs:
deploy:
if: "!contains(github.event.head_commit.message, 'skip ci')"
runs-on: ubuntu-latest
steps:
- run: |
eval $(ssh-agent -s)
echo "${{secrets.SSH_PRIVATE_KEY}}" > ssh.key
mkdir -p ~/.ssh
chmod 0600 ssh.key
ssh-add ssh.key
echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
ssh -p ${{secrets.SERVER_PORT}} root@${{secrets.SERVER_HOST}} "cd ~/data/server/xxx/ && npm run server:update"

从上到下解释:

  1. 仅在 master 分支上 push 代码时触发。
  2. 如果提交信息中包含了skip ci字样,则略过 ci 编译
  3. Secrets中配置的服务器私钥,配置到 ci 当前的机器上,让当前机器拥有密钥访问远程服务器的权限
  4. 在 ci 机器上密钥连接服务器并执行指令:进入仓库目录,并执行服务更新脚本

服务器脚本

仓库 package.json 中涉及到服务器操作的脚本:

"build": "rimraf dist && nest build",
"server:start": "npm ci && npm run build && pm2 start dist/main.js -i max --name server --env production -f",
"server:reload": "pm2 reload server",
"server:update": "git pull && npm ci && npm run build && npm run server:reload",

从上到下解释:

  1. 编译项目
  2. 首次拉取仓库时,创建server服务,仅首次执行一次即可
  3. 重启服务脚本,一般不需要手动执行,都是走后面的server:update
  4. 在 ci 中会调用到server:update,拉取最新代码,安装类库,重新编译项目,执行服务重启

配置开机自动启动服务

  1. 生成 pm2-root 的启动脚本,且自动将 pm2-root 设为服务
pm2 startup
输出:
[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=root
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/root/.pm2
PIDFile=/root/.pm2/pm2.pid
Restart=on-failure

ExecStart=/usr/local/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/local/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/local/lib/node_modules/pm2/bin/pm2 kill

[Install]
WantedBy=multi-user.target

Target path
/etc/systemd/system/pm2-root.service
Command list
[ 'systemctl enable pm2-root' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-root...
Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save

[PM2] Remove init script via:
$ pm2 unstartup systemd
  1. 将当前 pm2 所运行的应用保存在 /root/.pm2/dump.pm2 下,当开机重启时,运行 pm2-root 服务脚本,并且到 /root/.pm2/dump.pm2 下读取应用并启动
pm2 save

输出:

[PM2] Saving current process list...
[PM2] Successfully saved in /root/.pm2/dump.pm2

配置 Nginx

为啥需要 Nginx?因为需要获取用户的真实 IP、设置跨域等。这里只做简单的转发,设置获取 ip 的 header 头。

  1. 安装 略

  2. 在配置目录conf.d下,新建server.conf文件,必须以conf结尾,nginx 默认会加载该目录下的所有配置。内容如下:

server {
listen 80;
location /trade/ {
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Connection "keep-alive";
proxy_set_header X-Real-IP $remote_addr;
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers "x-token, Origin, X-Requested-With, Content-Type, Accept, Authorization" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
if ($request_method = 'OPTIONS') {
return 204;
}
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

表示访问服务器的xxx.com/trade/会自动转发到http://127.0.0.1:3000/

注意:

  1. add_header 配置后要加always,否则部分服务器的错误码会被 nginx 拦截并提示 header 跨域错误。

  2. 如果server.conf配置不生效,查看是不是被default.conf拦截了,可以删除default.conf,或者去掉default.conf中对 location/的处理。

  3. 如果提示跨域 Header 头重复,查看下是不是服务器代码中是不是也设置了跨域,有 Nginx 的话服务器中的跨域不需要设置了。