一、购买并启动云服务器【Aliyun】
阿里云的优惠活动还是蛮多的,前段时间出了个 2核2G 99/1年 的云服务器,正好自己最近也在一边学习 Nestjs,一边写点功能当作练习,想着可以试试把自己的项目部署到服务器上。
购买服务器并不复杂,按照官网指引,填写好相关配置并付费即可。
购买完成后前往 工作台 --> 实例 --> 你的服务器实例,实例详情如下,包括公私网IP、实例状态、操作系统等。
至于连接服务器的工具按自己习惯的即可,我们这里直接用阿里云提供的远程连接工具。
点击 实例ID 旁边的远程连接
,选择默认的 Workbench
连接成功后,进入操作终端
点击菜单栏的 文件 --> 打开新文件管理,查看服务器文件
在后续的操作中,我们会不时用到该工具,以完成项目的部署工作
二、部署前端项目【Vue3】
我们会用到 Jenkins 来实现前端项目的自动化部署。
Jenkins 的安装步骤暂时不作过多介绍,可以直接在 Jenkins 官网下载安装包或使用 Docker 拉取镜像,网上的相关教程也很多。
Jenkins download: www.jenkins.io/download/
1. 安装插件【Publish Over SSH、Nodjs】
【Dashboard】-->【Manage Jenkins】-->【Plugins】-->【Available plugins】,搜索 Publish Over SSH
和NodeJS
,安装完成后重启 Jenkins
2. Publish Over SSH 配置
【Dashboard】-->【Manage Jenkins】-->【System】,找到 Publish over SSH,并根据你购买的云服务器实例创建对应的 SSH Server
Name:自定义服务名称
Hostname:服务器IP
Username:服务器登录用户
Remote Directory:登陆后访问的地址
高级配置:输入Passphrase / Password
、Port
,再点击下方的 Test Configuration
按钮测试连接是否正常,显示 success
为成功。
全部确认后,先点击应用
,再保存
3. Nodejs 配置
【Dashboard】-->【Manage Jenkins】-->【Tools】,找到 NodeJS安装
,配置好 别名
和 版本
后,先点击应用
再保存
这里我用的 NodeJS 版本为 v20.10.0
4. 凭证管理
【Dashboard】-->【Manage Jenkins】-->【Credentials】,点击 Add credentials
填写远程项目仓库的用户名、密码、描述等,创建后即可在后续的操作中使用该凭证
5. 创建 Job
经过上述步骤,我们完成了所有的前置准备工作,接下来可以创建一个Job 开始正式部署。
点击【Dashboard】左侧菜单里的【新建Item】,选择创建一个【Frestyle project】
创建完成后,进入项目配置页面,并填写项目需要的信息:
描述
源码管理
构建环境
构建步骤
(1)Execute Nodejs script
(2)Execute shell
bash
#打印 node 和 npm 版本
node -v
npm -v
#安装项目依赖
npm install -g cnpm --registry=http://registry.npmmirror.com
cnpm install
#项目构建
npm run build
#进入到打包目
ls
cd dist
ls
#删除上次打包生成的压缩文件
rm -rf *.tar
#把生成的项目打包成压缩包,方便传输到远程服务器
tar -cvf `date +%Y-%m-%d-%H-%M-%S`.tar *
#回到上层工作目录
cd ../
echo "构建结束"
(3)Send files or execute commands over SSH
bash
#进入远程服务器的目录
cd /usr/share/nginx/html
#找到新的压缩包,解压
tar -xvf *.tar -C ./
#删除压缩包
echo ">>>移除*.tar"
rm -rf *.tar
nginx -s reload
#发布完成
echo "发布完成"
完成后点击 应用 并 保存,回到项目首页,点击【Build Now】开始构建项目
构建结束后,可以点击构建记录查看构建详情,比如在控制台输出中查看构建每个步骤的执行结果,如果构建失败,也可以籍此定位失败原因
6. 阿里云部署
此时,进入 阿里云工作台 》实例,点击远程连接
,这里我们直接使用阿里云提供的 workbench 进行操作
连接成功后打开文件管理,进入我们在填写 Send files or execute commands over SSH
配置时用到的 /usr/share/nginx/html
文件目录,可以看到,打包构建后的文件已经被发送到了我们的服务器上
(1)安装 nginx
yum install nginx -y
(2)启动 nginx
bash
#启动 nginx
systemctl start nginx
如果安装成功,我们可以找到服务器的 nginx.conf 配置文件的所在目录,可以使用 systemctl status nginx
查看 nginx 服务状态,也可以在打印结果中看到 nginx.conf 的所在目录
我的配置文件存放在 /etc/nginx/nginx.conf 内,点击编辑
打开配置文件
nginx 的配置在此不做过多赘述,这里我们使用默认的 nginx.conf,仅将端口、项目地址等基本信息修改为自己项目的信息即可
ini
server {
#项目端口
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
#项目地址
root /usr/share/nginx/html;
index index.html;
# 为默认的 server 加载 nginx 配置文件
include /etc/nginx/default.d/*.conf;
location / {
try_files $uri $uri/ /index.html;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
(3)重启 nginx 服务
nginx -s reload
7. 打开项目页面
在浏览器中输入公网IP及端口,查看项目是否启动
三、安装数据库【MongoDB】
在安装 MongoDB 之前,我们需要先准备好几个文件目录,分别用来存储数据库的数据及日志等
软件安装位置:/usr/local/mongodb 数据存放位置:/usr/local/mongodb/data/db 日志存放位置:/usr/local/mongodb/data/logs
1. 进入安装 mongodb 的目录
bash
cd /usr/local
2. 下载 mongodb 源代码压缩包
arduino
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.4.tgz
3. 解压安装包,并重命名为 mongodb
bash
tar zxvf mongodb-linux-x86_64-4.0.4.tgz
mv mongodb-linux-x86_64-4.0.4 mongodb
4. 创建数据和日志存储目录,也可以远程连接后手动创建
bash
mkdir /usr/local/mongodb/data/db
mkdir /usr/local/mongodb/data/logs
5. 创建 mongodb.conf 配置文件,放在 usr/local/mongodb 目录下
bash
cd /usr/local/mongodb
touch mongodb.conf
vim mongodb.conf
mongodb.conf 配置文件内容
ini
#端口号(默认的端口号是27017,也可以为了安全性等因素修改为其他值,在安全组里开放对应端口即可)
port=27017
#数据目录(指向刚才创建的数据文件目录)
dbpath=/usr/local/mongodb/data/db
#日志目录(指向刚才创建的日志目录,并指定mongodb.log文件名,系统会自动创建)
logpath=/usr/local/mongodb/data/logs/mongodb.log
#设置后台运行
fork=true
#日志输出方式(写日志的模式:设置为true为追加。默认是覆盖。如果未指定此设置,启动时MongoDB的将覆盖现有的日志文件。)
logappend=true
#是否开启认证
auth=false
#对外开放端口(默认是127.0.0.1,修改为 0.0.0.0 表示所有 IP 连接)
bind_ip=0.0.0.0
6. 配置安全组
在入方向
创建一条新规则,端口范围设置为你在 mongodb.conf 中配置的 port 值
7. 启动 mongodb
bash
#进入 mongodb 的 bin 目录
cd /usr/local/mongodb/bin
#启动命令
./mongod --config ../mongodb.conf
如果启动成功,我们可以看到以下信息:
arduino
about to fork child process, waiting until server is ready for connections.
forked process: 18999
child process started successfully, parent exiting
也可以使用 ps aux |grep mongodb
命令查看 mongodb 进程是否开启
arduino
root 31574 0.5 2.6 1000376 48508 ? Sl 10:43 0:01 ./mongod --config ../mongodb.conf
root 31773 0.0 0.0 112812 980 pts/1 S+ 10:47 0:00 grep --color=auto mongodb
8. 中止 mongodb
bash
#使用ps aux |grep mongodb打印出来的第一条记录就是刚才启动的 mongodb 进程,进程ID是 31574
kill -9 31574
9. 为了检测云服务器是否开启 mongodb,可以使用以下方法:
(1)在浏览器地址栏输入http:{云服务器公网IP地址}:{mongodb设置的端口号}
,比如:http://47.99.102.151:27017/
进行查看,启动成功会看到如下信息:
sql
It looks like you are trying to access MongoDB over HTTP on the native driver port.
(2)使用 Database Client JDBC
插件尝试连接,连接成功可以看到:
(3)使用 Mongodb Compass 连接数据库,填写好配置后点击 connect ,成功可以看到:
10. 处理数据库数据丢失的问题
刚开始成功把 MongoDB 服务启动后,运行以来并没有什么问题。
但是过段时间,自己添加的测试数据全部丢失了,重启服务后再次添加的数据,
依然遇到被清空的情况。
难道是有什么定时清除的功能吗?我寻思着也不太可能啊。
当我拿 Database Client JDBC
连接数据库后,突然发现多了一个 READ__ME_TO_RECOVER_YOUR_DATA
库,之前没仔细看,现在注意下就发现不对劲。
阅读此内容助你恢复数据。。。被黑了
原因很简单,之前编辑 mongo.conf 文件时,bind_ip为 0.0.0.0,端口号用的默认的27017,测试期间图方便,也没有设置用户名和密码,这不就是光着身子在互联网上裸奔吗?人家用脚写个脚本也能抓到啊。
为了解决这一问题
首先,修改端口号,连接数据库
bash
cd /usr/local/mongodb/bin
./mongo 127.0.0.1:27018
创建用户和权限
php
db.createUser({ user: "username", pwd: "password", roles: [{ role: "readWrite", db: "meleon" }, { role: "dbOwner", db: "meleon" }] })
设置开启权限
将配置文件中的 auth=false修改为 auth=true
重启服务,此时再次连接就需要提供用户名及密码了
ini
#mongo://myUser:myPassword@127.0.0.1:27018/test
mongo://[username]:[password]@[host]:[port]/[dbName]
四、部署后端项目【Nestjs】
和部署前端项目相比,部署后端项目的主要区别在于项目的打包,或者说构建行为,是放在服务器上进行的。
至于为什么要这么做,我们可以先使用默认的 nest build
在本地构建下 Nestjs 项目,结果如下:
即便包含了 .js,.d.ts 以及 .js.map,整个文件夹的体积也不到 1MB,作为经受过 node_modules 折磨的前端打工仔,一看体积才这么点就知道肯定有问题,如果我们完全按照前端部署步骤执行的话,最终的结果是提示Cannot find module XXX
,程序也根本跑不起来。
回到构建结果目录,我们注意到有一份特殊的文件tsconfig.build.tsbuildinfo
,里面的内容也告诉我们要到 dist 目录外的 node_modules 里找项目所需要的依赖项。
确定好基本思路,接下来就按部就班进行 Nestjs 的部署即可。
1. 搭建环境
(1)安装 Nodejs
bash
# 使用 nvm 来帮助我们更加方便的管理多个版本的 nodejs
yum install nvm
bash
# 查看可用的 nodejs 版本
nvm list-remote
bash
#安装需要的 nodejs 版本
nvm install v17.9.1
选择 v17.9.1 这个版本是因为这是我目前能安装最新且支持
??=
语法的版本了 T-T
csharp
# 设置默认的 node 版本
nvm alias default v17.9.1
在正式部署之前,我们可以先创建一份测试用项目文件 example.js,用以验证如何将项目代码部署到指定端口上
bash
#回到 root 目录
cd
#创建测试文件
touch example.js
#编辑测试文件
vim example.js
ini
const http = require('http');
const hostname = '0.0.0.0';
const port = 3033;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
node example.js
此时,我们可以打开浏览器,在地址栏输入 IP + 端口号【3033】,如果页面上显示 Hello World
,说明项目已经成功部署。
记得在阿里云服务器的安全组里开启相应的 3033 端口
(2)安装 pm2
使用 node 运行项目的弊端在于必须保持窗口始终处于开启状态,一旦窗口关闭,服务就会中止。很明显,这种方案是不太现实的。为了保证服务即便在窗口关闭后也能保持启动状态,我们需要使用另一个工具 ------ pm2。
a. 安装 pm2
css
npm install pm2@latest -g
b. 介绍下 pm2 常用的指令
bash
#启动服务
pm2 start <service>
bash
#列出已启动的进程
pm2 ls
bash
#结束所有或指定进程
pm2 kill
bash
#查看 pm2 日志
pm2 logs
c. 顺便介绍下其他可能会用到的指令
shell
#返回当前 nodejs 所在的目录【指定 pm2 运行时所用的 nodejs 版本】
#/root/.nvm/versions/node/v17.9.1/bin/node
which node
# 查看服务器端口占用情况,确认服务是否启动
netstat -nultp
2. 填写配置
源码管理和构建环境等配置项与前端部署区别不大,根据自己项目的信息自行配置即可,需要介绍的仅是构建步骤上的区别,如下:
(1)Execute shell
bash
node -v
npm -v
#删除上次打包生成的压缩文件
rm -rf *.tar
#把生成的项目打包成压缩包方便传输到远程服务器
#.env 文件通常包含敏感信息,例如 API、密钥、密码等
#默认情况下,tar 命令不会自动包含 .env 文件,如果确实需要,你可以在命令中显式地将 .env 文件打包进去
tar -cvf `date +%Y-%m-%d-%H-%M-%S`.tar .env.production .env.development *
echo "打包结束"
(2)Send files or execute commands over SSH
bash
#Source files
**/*.tar
bash
#Remove prefix
/
bash
#Remote directory
/usr/share/nginx/server
bash
#Exec command
source /etc/profile
#进入远程服务器的目录
cd /usr/share/nginx/server
#找到新的压缩包
tar -xvf *.tar -C ./
echo ">>>移除*.tar"
rm -rf *.tar
#打包构建
npm config set registry https://registry.npmmirror.com
npm install
npm run build
#关闭之前的进程
pm2 delete meleon-profile-backend
rm -rf ~/.pm2
#启动进程
pm2 start npm --name meleon-profile-backend --interpreter=/root/.nvm/versions/node/v17.9.1/bin/node -i 1 -- run start:prod
pm2 save --force
#发布完成
echo "发布完成"
解决
this.options.length ??= bufferOrReadStream.length;
报错问题
本地开发时我用的 nodejs 版本为 v20.10.0,而我购买的阿里云ECS服务器并不能支持这个版本,无法执行任何 node 相关的指令,所以不得不降低node的版本。
但是降低版本后,比如 14.18.0【公司早期的一些项目会用到 node-sass,需要将 nodejs 降到这个版本才能正常使用,所以最开始就习惯性地用了这个版本】,会出现无法识别 ??=
等新语法的报错,经过几次尝试,目前 v17.19.1 能够正常使用且不会出现类似报错。
此外,如果指定 node 版本并重启进程后,依然会有该报错,可以试试清下缓存再重启试试。
bash
pm2 kill
rm -rf ~/.pm2
bash
#手动设置 pm2 使用的 node 版本
pm2 set pm2:node-version 14.5.0
五、记录待解决的问题
(1)项目启动后,虽然没有报错,但是服务监听的端口并没有被占用。
app.listen(port, callback)
中callback
回调会在端口监听成功时触发,可以在代码中加上这个函数验证服务是否真的启动了
javascript
await app.listen(3000, () => {
console.log('成功监听了 3000 端口')
})
异常定位:
vbnet
/**
* @fix 在部署时,保留 { logger: false, cors: true } 参数会导致项目运行不起来,虽然本地运行正常,原因未知,先暂时注释掉吧
*/
// const app = await NestFactory.create(AppModule, {
// logger: false,
// cors: true
// })
const app = await NestFactory.create(AppModule)
(2)Send files or execute commands over SSH 步骤:将文件发送到服务器后并没有执行 Exec command
内编写的指令
blog.csdn.net/weixin_4429...
在Exec command
里添加一行source /etc/profile
(3)项目启动后,会提示端口已被占用,即使强行"杀死"端口后重启项目依然会出现这个问题。
解决方案:
在使用 pm2 启动项目时,添加 -i
指令,让 pm2 从 fork 模式切换为 cluster 模式。二者的区别主要在于:
-
Fork 模式:
- 基本概念 :Fork 模式是一种基本的进程派生方式,它使用
child_process.fork
API 来创建子进程。 - 用途:Fork 模式适用于多语言混编的场景,例如同时运行 PHP、Python 等不同类型的服务器。
- 端口复用:不支持端口复用,需要手动分配端口并自行处理子进程的业务代码。
- 灵活性:非常灵活,可以实现各种自定义需求,例如在预先分配的端口上启动多个服务器,然后由 HAProxy 或 Nginx 进行负载均衡。
- 基本概念 :Fork 模式是一种基本的进程派生方式,它使用
-
Cluster 模式:
- 基本概念 :Cluster 模式仅适用于 Node.js,它利用 Node.js 的 cluster 模块(例如
isMaster
、fork
等方法)来自动创建多个子进程。 - 自动负载均衡:Cluster 模式非常适合零配置的进程管理,因为它会自动将进程分叉为多个实例,实现负载均衡。
- 启动命令示例 :例如,使用
pm2 start -i 4 server.js
将启动 4 个server.js
实例,并由 cluster 模块处理负载均衡。
- 基本概念 :Cluster 模式仅适用于 Node.js,它利用 Node.js 的 cluster 模块(例如
六、总结
根据以上步骤,我们已经完成了前后端及数据库的部署工作
当然,目前来看还是有很多值得进一步优化的地方,比如代码push后的自动更新,又或者结合 Docker 以隔离依赖版本的影响等等
不过暂时先放一边吧,毕竟不算日常工作的话,已经有段时间没正经写代码了,项目里搁置了一堆有一堆的半成品功能,啧,估计半成品都算不上,抽点时间再整整