# 自动化部署
大部份公司应该都使用了 Jenkins 部署项目,这里主要是讲如果没有 Jenkins 的话前端自己使用 node 实现一个自动化部署脚本
# 自动化部署脚本
deploy-cli-service (opens new window) 是目前找到的一个前端自动化部署 Node 脚本。
但是实际使用的时候并不能完全部门的发布要求,所以就自己简单实现了一个
先捋下这个自动化部署脚本应该需要实现的基本功能
压缩需要部署的文件
连接服务器
上传压缩文件
解压文件
压缩需要部署的文件
node可以使用 archiver (opens new window) 模块实现压缩功能
const archiver =require('archiver');
//压缩dist目录为public.zip
function startZip() {
console.log('开始压缩dist目录...');
const archive = archiver('zip', {
zlib: { level: 9 } //递归扫描最多5层
})
.on('error', function(err) {
console.log('压缩失败')
throw err;//压缩过程中如果有错误则抛出
});
const output = fs.createWriteStream(__dirname + '/public.zip')
.on('close', function(err) {
/*压缩结束时会触发close事件,然后才能开始上传,
否则会上传一个内容不全且无法使用的zip包*/
if (err) {
console.log('关闭archiver异常:',err);
return;
}
console.log('已生成zip包');
uploadFile();
});
archive.pipe(output);// 压缩内容输出到 zip
archive.directory(path.resolve(__dirname,'./dist'), false); // 压缩内容直接放在zip包的根目录中
// archive.directory(srcPath, '/public');// 压缩内容放在zip包目录中 '/public' 路径中
archive.finalize(); // 执行打包
}
以上就实现了将当前目录中 dist
文件夹压缩成名叫 public.zip
压缩包
连接服务器
连接服务器可以使用 node-ssh (opens new window) 模块
const { NodeSSH } = require('node-ssh')
const ssh = new NodeSSH();
ssh.connect({
host: config.host,
port: config.port,
username: config.username,
password: config.password,
}).then(function () {
console.log('ssh连接成功...');
}).catch(err=>{
console.log('ssh连接失败:',err);
});
以上代码就实现了 ssh 的连接
上传文件
// localPublishZipPath 本地路径,如 __dirname + '/public.zip'
// remotePublishZipPath 远程路径, 如 config.publishPath + '/public.zip'
ssh.putFile(localPublishZipPath, remotePublishZipPath).then(function(status) {
console.log('上传文件成功');
}).catch(err=>{
console.log('文件传输异常:',err);
});
解压文件
解压文件就是在服务端执行 shell
命令
// remotePublishZipPath 要解压的压缩包路径 如 config.publishPath + '/public.zip'
// remotePublishPath 解压后的文件路径 如 config.publishPath + '/public'
ssh.execCommand(`unzip -o ${remotePublishZipPath} -d ${remotePublishPath}`)
.then(() => {
console.log('解压成功')
})
.catch(err => {
console.log('解压失败')
})
一般解压文件需要文件前需要删除之前项目文件,可以使用以下命令
ssh.execCommand(`/bin/rm -rf ${remotePublishPath}`)
.then(() => {
console.log('删除远程文件成功')
})
.catch(err => {
console.log('删除远程文件失败')
})
有了这些部分基本就实现自动化部署,然后实际工作使用中就可以根据自己需要添加配置扩展
注意点,执行成功或者捕获到失败时应该执行 process.exit
主动退出当前 node 进程
完整例子:
const path = require('path');
const archiver =require('archiver');
const fs = require('fs');
const { NodeSSH } = require('node-ssh')
const ssh = new NodeSSH();
const config = require('./deploy.config');
const srcPath = path.resolve(__dirname, config.distPath); // 要上传的文件
if(!fs.existsSync(srcPath)){
console.log(`不存在${config.distPath}目录`)
return
}
const localZipPath = path.resolve(__dirname, `${config.distPath}.zip`); // 压缩名件名
let remotePublishZipPath = path.resolve(config.publishPath, `${config.distPath}.zip`)
let remotePublishPath = path.resolve(config.publishPath, `/${config.distPath}`)
//执行远端部署脚本
async function doJob() {
try {
await startZip() // 压缩
await connectSSH() // 连接 ssh
await uploadFile() // 上传
await removeRemoteFile() // 移除原来的目录
await unzipRemoteFile() // 解压文件
if(config.isRemoveRemoteZip){
await removeRemoteZip() // 移除上传的压缩包
}
if(config.isRemoveLocalZip){
await removeLocalZip() // 移除本地的压缩包
}
}catch (err){
console.log(err)
}finally {
process.exit(0);
}
}
//压缩文件
function startZip() {
console.log('开始压缩目录...');
return new Promise((resolve, reject) => {
const archive = archiver('zip', {
zlib: { level: 9 } //递归扫描最多5层
})
.on('error', function(err) {
console.log('压缩失败')
reject(err);//压缩过程中如果有错误则抛出
});
const output = fs.createWriteStream(localZipPath)
.on('close', function(err) {
/*压缩结束时会触发close事件,然后才能开始上传,
否则会上传一个内容不全且无法使用的zip包*/
if (err) {
console.log('关闭archiver异常:',err);
reject(err)
return;
}
console.log('已生成zip包');
resolve();
});
archive.pipe(output);// 压缩内容输出到 zip
archive.directory(srcPath, false); // 压缩内容直接放在zip包的根目录中
// archive.directory(srcPath, '/public');// 压缩内容放在zip包目录中 '/public' 路径中
archive.finalize(); // 执行打包
})
}
// 上传文件
function connectSSH(){
console.log('开始ssh连接...');
return ssh.connect({
host: config.host,
port: config.port,
username: config.username,
password: config.password,
}).then(function () {
console.log('ssh连接成功...');
}).catch(err=>{
console.log('ssh连接失败:',err);
throw err
});
}
// 上传文件
function uploadFile(){
console.log('上传本地压缩文件');
//上传网站的发布包至configs中配置的远程服务器的指定地址
return ssh.putFile(localZipPath, remotePublishZipPath).then(function(status) {
console.log('上传文件成功');
}).catch(err=>{
console.log('文件传输异常:',err);
throw err
});
}
// 解压上传的文件
function unzipRemoteFile(){
console.log('开始解压文件:', remotePublishZipPath)
return ssh.execCommand(`unzip -o ${remotePublishZipPath} -d ${remotePublishPath}`)
.then(() => {
console.log('解压成功')
})
.catch(err => {
console.log('解压失败')
throw err
})
}
function removeRemoteFile(){
console.log('删除远程文件:'+remotePublishPath)
// 货拉拉需要使用 /bin/rm 删除文件
return ssh.execCommand(`/bin/rm -rf ${remotePublishPath}`)
.then(() => {
console.log('删除远程文件成功')
})
.catch(err => {
console.log('删除远程文件失败')
throw err
})
}
function removeRemoteZip(){
console.log('删除远程压缩文件:'+remotePublishZipPath)
// 货拉拉需要使用 /bin/rm 删除文件
return ssh.execCommand(`/bin/rm -rf ${remotePublishZipPath}`)
.then(() => {
console.log('删除远程压缩文件成功')
})
.catch(err => {
console.log('删除远程压缩文件失败')
throw err
})
}
function removeLocalZip(){
console.log('删除本地压缩文件:'+localZipPath)
fs.unlinkSync(localZipPath)
console.log('成功删除本地压缩文件')
}
doJob();
# GitHub Actions
通过 GitHub Actions (opens new window) 实现了静态博客的自动化部署
比如本人使用 VuePress 静态网站生成器制作博客。博客源码地址在 Github 仓库 Hello-Word (opens new window)。构建后的文件分别部署在 Github 仓库 blog (opens new window) 和 Gitee 仓库 lanjz (opens new window) 两个地方。如果使用手动的方法,每次更新完博客后的打包步骤为:
build
=> 将打包文件分别复制到 Github/blog和Gitee/lanjz 仓库
=> 两个 Git 都是执行 commit和push
=> 然后再分别进入Git Pages 进行更新
使用 GitHub Actions 只需要 push
VuePress 项目后,后台就会自己执行上面的所有的操作
# 使用
GitHub 监控到执行事件时,会分配一台虚拟机先将你的项目 checkout 过去,然后按照你指定的 step 顺序执行定义好的 action,这些 action 就包括执行 Node 脚本打包项目,push 到你指定的仓库等动作
粟子:
添加一个 yml
文件并在博客编辑项目的根位置 .github
文件夹中
# .github/pages-update.yml
name: Deploy Github Pages # 该Action的名字
# on:何时触发该事件.
on:
# 在仓库执行了push请求事件时触发工作流,但只针对主分支
push:
branches: [ master ]
# 允许从Actions选项卡手动运行此工作流
workflow_dispatch:
# 工作流运行由一个或多个jobs组成,这些job可以按顺序或并行运行
jobs:
# 此工作流包含一个名为“build-deploy”的job。
build-deploy:
runs-on: ubuntu-latest # job运行于什么虚拟机上:最新版 Ubuntu
# steps表示将作为job一部分执行的一系列任务
steps:
- name: Checkout
uses: actions/checkout@master # 切换分支到master
with:
persist-credentials: false
- name: Build
uses: actions/setup-node@v1
with:
node-version: '12.x' #使用nodejs 12.x版本
# 1、生成静态文件
- name: Build
run: cd blog&&npm install && npm run build #安装依赖并打包,执行的是项目我们定义的命令
# 2、更新 Github 博客仓库
- name: Deploy
uses: JamesIves/github-pages-deploy-action@releases/v3
with:
ACCESS_TOKEN: ${{ secrets.DEPLOY_KEY }} # Github ACCESS_TOKEN
REPOSITORY_NAME: lanjz/blog # [Github 账号名/仓库]
BRANCH: master
FOLDER: blog/docs/.vuepress/dist # 上传到 lanjz/blog 的文件目录,这里就是 vuepress 打包出来的目录
# 3、同步到 gitee 的仓库
- name: Sync to Gitee
uses: wearerequired/git-mirror-action@master
env:
# 注意在 Settings->Secrets 配置 GIT id_rsa
SSH_PRIVATE_KEY: ${{ secrets.GITEE_R_P_KEY }}
with:
# 注意替换为你的 GitHub 源仓库地址
source-repo: git@github.com:lanjz/blog.git
# 注意替换为你的 Gitee 目标仓库地址
destination-repo: git@gitee.com:codebeat/lanjz.git # git@gitee.com:[Gitee 账号名]/[仓库].git
# 4、更新 Gitee Pages
- name: Build Gitee Pages
uses: yanglbme/gitee-pages-action@master
with:
# 注意替换为你的 Gitee 用户名
gitee-username: ${{ secrets.GITEE_USERNAME }}
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
gitee-password: ${{ secrets.GITEE_PASSWORD }}
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错
gitee-repo: codebeat/lanjz
# 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在)
branch: master
上面的各种 Git 操作难免涉及到权限问题。所以需要一些相关的 key
、账号密码等信息
ACCESS_TOKEN:这是博客源项目所在 Github 的
ACCESS_TOKEN
配置位置:
Github 头像
->Settings
->Developer settings
=>Personal access tokens
SSH_PRIVATE_KEY:安装 Github 的时候都会生成私钥和公钥,这里的
SSH_PRIVATE_KEY
就是私钥内容配置位置:
Github 头像
->Settings
->Developer settings
=>Personal access tokens
gitee-username:Gitee 账号
gitee-password:Gitee 密码
当然这些信息不能直接编写到内容里面。Github 提供了 Actions secrets,类似环境变量的配置,这些环境变量将会被 Action
执行时访问。
配置位置:yml 所在仓库
-> Settings
-> secrets