购买阿里云Linux服务器
如果已有服务器请直接跳转到【传统部署方式】
登录阿里云,访问 云服务器 ECS 购买地址:https://ecs-buy.aliyun.com/
也可从首页导航菜单进入。
购买方式
- 一键购买 - 整合了一些常规配置选项,选择后直接确认订单。
- 一键购买提供的实例规格(服务器配置)都是【突发性能实例 t5】
- 自定义购买 - 按步骤完全自定义选择配置(推荐)
- 有更多的实例规格选择
当前购买的配置,在后期都可以通过升级调整,优惠按照当时的活动。
比如今天选好了配置并购买,第二天升级,优惠保持不变(客服说的,大致意思是这个,最好再和客服确认清楚)
本人的购买原则是【便宜】,下面是具体介绍。
下面是自定义购买的一些介绍,没讲到的就是默认选择了。
付费模式
客服和我说,如果不经常使用,可以选择【按量付费】。
我的考虑是:
- 本人懒得管理,所以不会主动去操作停机
- 依然是因为懒得管理,万一里面不小心放了个消耗流量应用,或存在被外部持续访问的各种可能性,本人又不是很懂,担心这种风险导致流量超额
- 包年包月有优惠
所以我这样的懒人就选择了【包年包月】
地域及可用区
刚进来默认选择的是【南京】,还有其他选择。
客服的说法是:访问服务器的IP地域 距离 服务器地域 越近,访问速度越快。
推荐购买离你所属地区近的地域。
另一个考虑:【张家口】和【呼和浩特】本人购买时有优惠(该优惠主要是 t5 相对便宜些,其他的没变化)。
所以本人买的最便宜的【张家口】(t5 17.1元/月)
实例
由于本人只是个人学习用,彼时只是发布一个站点,没有任何数据交互,所以选择了1核1GB。并且选择了最便宜的 t5 实例。
本着不够用的话后期再升级的目的,选择了最便宜的实例。
实例规格 - 突发性能实例 t5:
在【自定义购买】页面可以查看明细对比。
【突发性能实例 t5(后面简称 t5)】相对便宜很多。
它的 “平均基准CPU计算能力” 特意标识了 20%。
大致意思就是当服务器CPU占用超过20%后,会很卡。
其他的实例就是当CPU占用达到100%后才会很卡。
这是阿里云发现很多用户的ECS并没有经常被使用,而提供出的一种优惠选择。
更详细的介绍可以百度,有很多文章讲的很细。
本人之前有一次购买经验,仅供参考:
当时搭建了一个自用的后台系统。
购买的配置是1核1GB(还是标准型的),mysql数据库,20GB数据盘,1M带宽。
使用时访问很慢,于是升级为1核2GB,并升级了带宽(1M 升级为 4M),之后访问正常。
原因没搞懂,因为懒。。。
镜像
因为学习Linux系统,所以镜像选择了 Ubuntu 最新版本。
主要原因:社区庞大。
远程连接 Ubuntu 后的界面:
存储
使用了默认的配置,这次没有购买数据盘。后期需要再升级。
网络和安全组
下一步进入【网络和安全组】,根据个人需要配置,本人看不懂所以基本上都是默认选择。
- 带宽计费模式:同收费模式一样,本人选择了【按固定带宽】收费
- 带宽值:网友都是1M就够了,这次就选择了1M,不够再升级
参考文章:
系统配置
下一步进入【系统配置】。
登录凭证:
- 密钥对,需要创建密钥
- 官方文档
- 注意:如果使用SSH密钥对登录Linux实例,将会禁用密码登录
- 自定义密码
- 可以用ssh连接服务器时通过输入密码登录
- 创建后设置 - 不设置 密钥对/密码 默认选择了这项
- 创建实例(购买后),如需登录,需要重置密码(设置密码),或创建密钥对。
购买时长
购买时长是一直保持在底部的,可以即时计算价格。
根据本人计算,时间越久,优惠越大,建议购买长点的,因为续费很贵!!!!!!
(官方有续费优惠活动,但是力度不大。也可选择到期重新买,只是需要手动转移数据等事情)。
连接测试
购买成功后,通过网站右上角进入控制台:
查看云服务器ECS -> 查看实例 -> 实例详情 ->重置密码:
重置密码后需要重启实例。
使用win10系统的内置应用 openSSH 连接远程服务器,如果没有安装,需要手动安装一下:
安装后打开命令行工具或 Powershell:
# Linux 默认登录名 root
# ip 输入实例的公网IP(在实例详情中可以找到)
ssh [登录名]@[ip]
# 执行命令后会要求输入密码(密码不会显示在命令行中,输完直接回车即可)
连接成功:
端口添加访问权限
本案例发布的项目使用Nuxt默认3000端口,阿里云服务器默认没有为这个端口添加访问权限,需要手动添加:
进入控制台-安全组,点击进入安全组规则:
手动添加规则:
Linux常用命令
cd
切换目录ls
查看当前目录下文件ls -a
查看所有文件(包含.
开头的隐藏目录)df
查看磁盘占用apt install <appname>
安装软件apt remove <appname>
写在软件rm <filename> -i
彻底删除文件(会询问)clear
清空命令行exit
退出连接whereis <appname>
查看软件安装路径env
显示环境变量echo $PATH
打印path路径变量cat <filepath>
查看文件全部内容rm rf *
强制删除当前目录下所有文件
软件安装
新安装的ubuntu系统的 /etc/apt/source.list 中的源比较旧了,需要更新一下,更新方法:apt -y update
否则安装软件会失败,例如安装 unzip
Reading package lists... Done
Building dependency tree
Reading state information... Done
Package unzip is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
E: Package 'unzip' has no installation candidate
unzip安装
如果服务器未安装unzip,使用unzip命令失败会提示安装unzip:
apt install unzip
nvm - node版本管理工具
node多版本管理工具主流有:nvm 和 n。
具体选择可以百度,搜索“管理 node 版本,选择 nvm 还是 n?”。
这里使用 nvm官方安装教程
- 下载 nvm 安装脚本 并 运行
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
# or
# wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
- linux下安装完后执行
nvm -v
如果报找不到该命令
官方:关闭终端,重新打开,再次执行。
- 接着就可以安装node了
nvm install node
# or
# nvm install 12.17.0
# 查看node版本列表
nvm list
# 指定node版本
nvm use 12.17.0
传统部署方式
传统部署流程
-
配置 Host + Port
-
压缩发布包
-
把发布包传到服务端
- Linux 上的 scp 命令
-
解压
-
安装依赖
-
生产环境启动服务
配置 Host + Port
nuxt.config.js中的server属性
- host
- 默认
localhost
只能本地访问 - 对外访问需设置为
0.0.0.0
- 会监听所有网卡地址
- 默认
- port - Nuxt 默认 3000
压缩发布包
需要传到服务器上的文件包括:
- .nuxt
- static
- nuxt.config.js
- package.json
- package-lock.json
上传到 Linux 服务器
vscode 终端连接服务器
# 连接服务器
ssh [username]@[ip]
# 展示目录
ls
# 创建目录
mkdir realworld-nuxtjs
# 进入目录
cd realworld-nuxtjs/
# 展示路径
pwd
# 登出
exit
# 上传本地资源到服务器(此时已经退出ssh链接,在本地执行命令)
scp .\realworld-nuxtjs.zip [username]@[ip]:[pwd地址]
# 再次连接服务器
ssh username@ip
# 进入目录
cd realworld-nuxtjs/
# 展示目录
ls
# 解压压缩包(需要服务器已安装unzip)
unzip realworld-nuxtjs.zip
# 查看目录 添加 -a 可以显示隐藏目录 .nuxt
ls -a
# 安装依赖
npm i
# 启动web服务
npm run start
现在就可以使用公网IP+端口号访问(如果访问失败可能是端口未添加对外访问权限)。
使用 PM2 启动 Node 服务
上面在vscode的命令行通过npm run start
启动的web服务。
它现在占用着命令行,如果命令行退出,这个服务就关闭了。
pm2 是专门用来管理 Nodejs 进程的应用。
通过它,就可以将 Nodejs 相关的应用,运行在后台,保持运行状态。
- Github 仓库地址:https://github.com/Unitech/pm2
- 官方文档:https://pm2.io/
- 全局安装:
npm i -g pm2
- 启动:
pm2 start 脚本路径
- 停止:
pm2 stop <app_name|namespace|id|'all'|json_conf>
当前应用的启动脚本是通过npm启动的,不是执行一个脚本文件,可以 pm2 执行 npm 然后传参:
pm2 start npm -- start
# 此时任务名为npm,自定义名称(--name参数一定要在前面):
# pm2 --name <task_name> start npm -- start
windows 问题
pm2 start npm -- start
在window环境下启动的服务状态是 stopped。
报错日志:
0|RealWorl | :: Created by npm, please don't edit manually.
0|RealWorl | ^
0|RealWorl |
0|RealWorl | SyntaxError: Unexpected token :
0|RealWorl | at Module._compile (internal/modules/cjs/loader.js:872:18)
0|RealWorl | at Object.Module._extensions..js (internal/modules/cjs/loader.j
s:947:10)
0|RealWorl | at Module.load (internal/modules/cjs/loader.js:790:32)
0|RealWorl | at Function.Module._load (internal/modules/cjs/loader.js:703:12
)
0|RealWorl | at Object.<anonymous> (D:\software\nvm\v12.10.0\node_modules\pm
2\lib\ProcessContainerFork.js:32:23)
0|RealWorl | at Module._compile (internal/modules/cjs/loader.js:936:30)
0|RealWorl | at Object.Module._extensions..js (internal/modules/cjs/loader.j
s:947:10)
0|RealWorl | at Module.load (internal/modules/cjs/loader.js:790:32)
0|RealWorl | at Function.Module._load (internal/modules/cjs/loader.js:703:12
)
0|RealWorl | at Function.Module.runMain (internal/modules/cjs/loader.js:999:
10)
GitHub有人提出了这个问题,里面的变通方案是:https://github.com/Unitech/pm2/issues/2808
也就是使用 node-cmd 运行命令(网上其他的方案也是把npm run start 写在一个脚本文件中,然后用pm2执行):
- 安装
npm install node-cmd
- 新建脚本 xxx.js
const cmd = require('node-cmd')
cmd.run('npm run start')
- pm2 启动脚本
pm2 start xxx.js
执行 pm2 配置文件启动Node服务
项目根目录创建 pm2的配置文件:pm2.config.json
{
"apps": [
{
"name": "RealWorld", // 自定义pm2启动的项目名称
"script": "npm", // pm2执行的命令
"args": "start" // 命令参数
}
]
}
执行pm2命令时指定配置文件:
pm2 start pm2.config.json
# 等同于
pm2 start npm -- start
注意:window 环境下同样不生效
PM2 常用命令
命令 | 说明 |
---|---|
pm2 list | 查看应用列表 |
pm2 start | 启动应用 |
pm2 stop | 停止应用 |
pm2 reload | 重载应用(在启动新实例之前,原有实例的进程会一个一个消灭) |
pm2 restart | 重启应用(先消灭原有实例的所有进程,然后启动新实例) |
pm2 delete | 删除应用(从pm2管理中移除掉) |
pm2 -h | 查看所有命令 |
自动化部署方式
传统部署方式
- 优点:部署流程比较清晰。
- 缺点:每次更新都要重复发布流程(本地构建 -> 发布)
现代化的部署方式(CI/CD):
结合 CI/CD 服务工具实现自动部署。
- CI:持续集成(Continuous integration)
- CD:持续部署(Continuous deployment)
CI/CD 服务:
- GitHub Actions
- Gitee Jenkins
- GitLab CI
- Travis CI
- …
使用GitHub Actions 实现自动化部署
准备工作
- Linux 服务器 - 发布应用
- GitHub 仓库 - 托管代码
1. 将项目代码提交到GitHub仓库
2. 在 GitHub 的 Settings 面板创建 token
点击GitHub右上角头像 - Settings - Developer settings - Personal access tokens
生成的个人访问令牌(Token)用于访问GitHub API
输入密码,创建token,添加对仓库完全访问和操作的权限:
生成的token只会出现这一次,如果没有记录,忘记了,只能重新创建一个新的。
3. 在项目根目录创建 GitHub Actions 工作流配置文件
创建目录./github/workflows
,在里面创建.yml
后缀的文件,文件名随意,将作为工作流的默认名称,GitHub会自动识别到这个文件并执行。
name: Publish And Deploy Demo
on:
push:
# 提交tag名以v开头的tag时执行部署任务
tags:
- 'v*'
jobs:
build-and-deploy:
# 运行环境
runs-on: ubuntu-latest
# 执行的步骤
steps:
# 下载源码
- name: Checkout
uses: actions/checkout@master
# 打包构建
- name: Build
uses: actions/setup-node@master
- run: npm install
- run: npm run build
# 生成压缩包(打包 .nuxt static nuxt.config.js package.json package-lock.json pm2.config.json)
- run: tar -zcvf release.tgz .nuxt static nuxt.config.js package.json package-lock.json pm2.config.json
# 发布 Release(创建Release分支)
- name: Create Release
id: create_release
uses: actions/create-release@master
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
# 本次提交的tag的名称
tag_name: ${{ github.ref }}
# Release版本的名称
release_name: Release ${{ github.ref }}
# 是否是草稿
draft: false
# 是否是预发布
prerelease: false
# 上传构建结果(release.tgz)到 Release
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@master
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
# 上传地址(创建的Release分支地址)
upload_url: ${{ steps.create_release.outputs.upload_url }}
# 上传的文件
asset_path: ./release.tgz
# 上传后的文件名
asset_name: release.tgz
# 上传的文件类型
asset_content_type: application/x-tgz
# 部署到服务器
- name: Deploy
uses: appleboy/ssh-action@master
with:
# 远程服务器地址
host: ${{ secrets.HOST }}
# 远程服务器用户名
username: ${{ secrets.USERNAME }}
# 远程服务器密码
password: ${{ secrets.PASSWORD }}
# 远程服务器端口号
port: ${{ secrets.PORT }}
# 命令超时配置 默认10m
command_timeout: 20m
# 运行在远程服务器的命令
# 1. 进入项目目录
# 2. 下载发布包
# 3. 解压缩发布包
# 4. 安装生产环境依赖
# 5. pm2运行配置文件
script: |
cd /root/realworld-nuxtjs
wget [Github仓库地址]/releases/latest/download/release.tgz -O release.tgz
tar zxvf release.tgz
npm install --production
pm2 reload pm2.config.json
工作流中使用到了 secrets
信息,即GitHub项目仓库 Settings 中配置的 Secrets。
需要配置的有(注意名称需要与文件中的保持一致):
- TOKEN - 之前创建并记录的token
- HOST - 服务器IP地址
- USERNAME - 服务器用户名,Linux默认root
- PASSWORD - 服务器 ssh 连接密码
- PORT - ssh连接服务器的端口号,默认22
4. push代码触发自动部署任务
触发条件:push名称以v
开头的 tag。
TortoiseGit方式:
- 创建标签
- 推送 - 勾选包括标签
Git 命令方式:
# 创建tag
git tag v0.1.0
# 查看tag
git tag
# 推送tag
push tag origin v0.1.0
然后在GitHub仓库的Actions面板可以查看构建过程。
构建成功后,使用服务器公网IP+端口号访问。
之后修改内容后,只需重新打 tag 推送到 GitHub 就会自动部署。
遇到的问题
请确认服务器已安装 git、nodejs、pm2 等工具。
本人服务器构建过程中 Deploy 失败,状态码 127。
文件已经解压缩,于是手动连接服务器,执行后面的步骤 npm install --production
最终失败报错:Maximum call stack size exceeded
于是切换npm源为淘宝镜像:npm set registry https://registry.npm.taobao.org/
重新安装,成功。
重新创建tag推送,自动部署又报错:
-bash: npm: command not found
-bash: pm2: command not found
可是手动连接服务器执行都OK(本人是nvm安装的nodejs)。
打印环境变量 echo $PATH
也添加了路径的。
于是在actions任务中执行命令打印 echo $PATH
,果然没有 node 路径。
尝试在/root/.bashrc
和 /etc/profile
中都添加了 环境变量配置,都没有生效。
又在actions任务中查看文件目录,node、npm、pm2都有。
这个问题的原因是:
手动执行任务时,是在当前 shell 环境下进行的,程序可以找到环境变量。
而系统自动执行任务调度时,是不会加载任何环境变量的,因为它不是通过 shell 环境执行的。
解决办法:
- 临时办法:在actions任务中手动添加环境变量:
这个方式就是在每次任务执行时,npm命令执行前手动配置环境变量。
#...
script: |
export PATH=/root/.nvm/versions/node/v12.17.0/bin:$PATH
cd /root/realworld-nuxtjs
#...
- 长久办法:创建软链接
ln -s [node路径] /usr/local/bin
ln -s [npm路径] /usr/local/bin