我们前面简略介绍了基于 GitLab CI/CD 的安装与配置操作,详细大家对 GitLab Runner 的使用已经不再陌生,但话说回来,看着配置来讲的话,也不好摸索出一整套的部署方案,所以小花便给出我们团队所通用的实战方案,方便大家参考
方案分析
在此篇文章中,小花会给出四种项目的部署实现方案,分别是 Vue 项目,Node 项目,Taro 微信小程序项目,Uniapp 项目
在此之前,让我们先来熟悉一下本团队内部使用的 Git 分支管理方案,管理方案大同小异,适合而已,大家借鉴即可
基于分支
Git 管理方案大多是基于分支管理,我们也并不例外,所有创建的项目中,我们默认 master 为生产分支,develop 为开发分支,以及版本分支
develop 分支下存在多个功能分支,以 develop 做为基础切出,并会合并回 develop
版本分支 下存在多个环境的版本历史,以版本控制的严格程度分化为多个子分支
master 孑然一身,只有存在紧急 Bug 时,才会有 hotfix分支切入并合并回 master 与 develop
具体细节参考下图
那么对于我们而言,只需要特别关注 beta/*、release/*、master,这三种分支,关乎于我们的测试,灰测,生产环境的部署,也是我们 CI Jobs 切入的关键点
项目分析
如上所述,我们在本篇文章中会简介四种项目的 CI/CD 部署方式,在我们完全了解 Git 分支的管理方式之后,接下来就是对四种项目的区别分析了
Vue 项目
Vue 项目相对来说较为简单,可以预想到我们会执行到的任务是
- 服务器下载项目 (
git clone这个步骤由 GitLab Runner 自动完成,不需要过多的操作) - 项目依赖安装 (
npm install) - 项目编译 (
npm run build) - 静态文件部署
Node 项目
Node 项目则比Vue项目稍微复杂一点点,我们这里以 Typescript + Egg.js 的项目为示例,Koa 和 Express 项目则大同小异,任务指令如下
- 服务器下载项目 (同上)
- 项目依赖安装 (同上)
- Typescript 编译 (Egg.js 提供了很便捷的方式
npm run ci) - 运行项目 (
npm run start) - 项目部署
Taro 微信小程序项目
微信小程序项目我们只能借助于微信自己的 CI 脚手架来执行编译和上传,那么对于其他应用框架,如 uni-app 的微信小程序,mpvue, kbone 都是同样的道理
- 服务器下载项目 (同上)
- 项目依赖安装 (同上)
- 项目编译 (
npm run build) - 借助微信 CI 工具上传项目 (自己编写 cli 脚本)
uni-app 项目
相对上面三种类型来讲,uni-app 的项目显然困难了一点,因为大部分项目都是基于 DCloud 的 HBuilder 工具建立,这对我们编译发布来讲就复杂了很多,不过万幸的是,uni-app 同样提供了 vue-cli 的安装方式
这里分为两种情况,其一是如果你的项目是基于 uni-app 的 vue-cli 模式创建,那么恭喜你,你可以直接使用如上面 Vue 项目的形式加上微信 CLI 来直接进行发布操作
其二就是你的项目完全是基于 HBuilderX 进行开发,那么就需要用到以下任务
- 创建编译框架项目 (
vue-cli模式创建,方便我们后自动转换) - 服务器部署编译框架
- 下载 HBuilderX 创建的项目到服务器 (
GitLab Runner完成) - 执行迁移脚本 (迁移 HBuilderX 项目至
vue-cli项目) - 项目依赖安装 (
npm install) - 项目编译 (
npm run build:mp-weixin) - 借助微信 CI 工具上传项目
准备工作
在正式开始项目部署之前,我们还需要对自动化所依赖的一系列环境进行配置
服务器环境
上面分析了我们项目所需要的相关任务,那么相应环节的环境依赖是必不可少的,目前已经明朗的需要安装的环境为
Node.jsGithub RunnerTaro CLI(也可以在项目中单独安装)Gitminiprogram-ci(微信 CI 工具,也可在项目中单独安装)uni-app 编译框架(这个我们在后面会详解)Nginx(或其他部署类框架)- 如果你的项目多个环境部署在不同的机器或服务器中,那么你可能需要安装
rsync以备不时之需 Docker如果你的 GitLab Runner 基于 Docker 运行,你则需要安装 Docker 环境
Docker 或 Shell 是 GitLab Runner 在注册时选择的一种运行机制,你可以在运行 github-runner register 命令时一探究竟,本教程基于 Shell 版本运行
GitLab Runner 环境
你需要确认是,GitLab Runner 是否成功绑定在你的 预编译项目之上 或者 项目 Group 之上,绑定在 Group 之上的好处是,只要在这个 Group 之下的所有项目,我们都能使用到该 Runner,而不需要单独绑定
比如小花前端团队的名字叫 G-FE,那么我创建了一群组为 @g-fe,下面又划分了多个子群组来区分各个端的项目,这样我的 Runner 程序就应该挂载到 @g-fe 这个总群组下面,其他子群组及子项目都能无碍的使用 Runner 配置
GitLab 提供了多种挂载方案,甚至可以在整个网站上挂载同一个 Runner
需要确认你的 Runner 是否挂载正确,请参照 深入浅出,前端团队的自动化部署方案 - 环境篇
运行脚本
GitLab 以及 GitLab Runner 基于 .gitlab-ci.yml 文件运行,预要开始,则必须现在你的项目中新建该文件,你可以使用 GitLab 提供的自动创建工具,也可以手动新建,然后跟着小花一步步进行配置
首先要确认我们 CI 任务的执行顺序与任务目的,我们可以先设定构建场景 stages,如下所示,在你新鲜的文件之中加上下面的代码吧
stages:
- test
- build
- deploy
如果你忘记了关键词的含义,那让我们一起来温故而知新 深入浅出,前端团队的自动化部署方案 - 配置篇
Vue 项目
为了完成本次的 CI/CD 操作,我使用 vue-cli 创建了一个初始化项目,你也可以使用一个新的项目跟着小花一起操作,也可以在你的预备项目中原地作业
做 CI/CD 的含义在于更便捷的发布与部署,但是代码质量同样需要控制,然而 CI 就能帮我们做到这一点,在 Vue 项目中,我们一般会使用到 eslint 或者 prettier 来为项目做代码优化,那么对于 CI 来讲,只需运行代码检测的 npm run lint 即可,所以第一步
代码质量检测
stages:
- test
- build
- deploy
test:
stage: test
tags:
- shell-g-fe-runner
script:
- npm install --no-optional --registry=https://registry.npm.taobao.org/
- npm run lint
tags 用来声明可用于该任务执行的 Runners,而在执行
gitlab-runner register时你就拥有了为自己 Runner 设置 Tag 的时刻
如上,我们声明了一个 test 任务,任务命令会执行两条,一条执行安装依赖,一条执行代码检测
项目编译
stages:
- test
- build
- deploy
test:
stage: test
tags:
- shell-g-fe-runner
script:
- npm install --no-optional --registry=https://registry.npm.taobao.org/
- npm run lint
build:
stage: build
tags:
- shell-g-fe-runner
script:
- sudo npm rebuild node-sass
- sudo npm run build
only:
- master
- /^beta\/.*$/
- /^release\/.*$/
如上,我们声明了 build 任务,它首先会帮我们重新构建 node-sass,然后执行标准的编译语句
only 参数你很感兴趣,它规定只在特定的分支执行该任务,想起我们之前所说的三种分支了吗,我们只在他们之中执行编译操作,当然,beta/\* 和 release/\* 下面可能有多个分支版本,万幸的是,only 参数支持正则表达式配置
GitLab 的 CI 程序同时包含缓存机制,如果你想把你的编译产物缓存下来,那么在编译期后执行则是一个很好的时机,如下所示
build:
stage: build
tags:
- shell-g-fe-runner
script:
- sudo npm rebuild node-sass
- sudo npm run build
artifacts:
paths:
- dist/
expire_in: 60 mins
artifacts 关键字可以帮我们把产物缓存下来,在任务执行完毕之后,即可在 GitLab 可视化界面进行下载,你也可以设置产物的缓存过期时间,如上面的 expire_in 所示,它的默认值是 4 weeks,去 GitLab CI/CD 文档 了解更多
项目部署
上面的步骤,我们完成了代码检查和项目编译操作,这个时候我们已经得到了 dist 文件夹,聪明的人已经知道接下来的步骤了,我们只要把 dist 文件夹放在 nginx 设置的位置即可完成部署,接着,我们再生成一个任务,不过考虑到我们是多环境配置,所以我们为每一个对应的环境建立一个独立的任务,收好了哦~
deploy_test:
stage: deploy
tags:
- shell-g-fe-runner
only:
- /^beta\/.*$/
environment:
name: Test
url: http://test.vue.com/
script:
- cp -R dist/* /data/html/vue-com/test/
deploy_uat:
stage: deploy
tags:
- shell-g-fe-runner
only:
- /^release\/.*$/
environment:
name: Uat
url: https://uat.vue.com/
script:
- cp -R dist/* /data/html/vue-com/uat/
deploy_prod:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://vue.com/
script:
- cp -R dist/* /data/html/vue-com/prod/
清晰明了的操作,我们只是在对应的分支编译后,发布到不同的服务中去,在配置中,我们看到一个新的关键字 environment,这是我们的环境标识,不仅能有效区分对应环境,还对我们之后要做的 环境管理与回滚 提供了便捷
以上的示例我们仅仅是在同一台机器上进行操作,如果你的环境由不同的服务器进行区分,那么请关注 多服务器部署
你会发现每次进入任务执行期后,都会进入一段时间的等待,也可能会报 node_modules 文件找不到的情况,那是因为我们的任务机制都是相互隔离的,之间享有的文件也不同享,这时候,我们就需要补上全局的缓存策略了
cache:
paths:
- node_modules/
加入到你的配置文件中试一试吧
Node 项目
通过上面的 Vue 项目的发布策略,我们应该知道一些有关于 GitLab CI/CD 的常规操作了,Node 项目当然是信手拈来
需要注意的是,Node 项目会比正常的 Vue 项目少了编译操作,那么对于我们来讲,就可以少写一个任务了,废话不多说,直接上全盘,相信对于大家来说,理解该配置都是轻而易举
stages:
- test
- build
- deploy
cache:
paths:
- node_modules/
test:
stage: test
tags:
- shell-g-fe-runner
script:
- npm install --no-optional --registry=https://registry.npm.taobao.org/
- npm run lint
deploy_test:
stage: deploy
tags:
- shell-g-fe-runner
only:
- /^beta\/.*$/
environment:
name: Test
url: http://test.node-api.com
script:
- npm run ci
- npm run restart:test
deploy_uat:
stage: deploy
tags:
- shell-g-fe-runner
only:
- /^release\/.*$/
environment:
name: Uat
url: https://uat.node-api.com
script:
- npm run ci
- npm run restart:uat
deploy_prod:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://node-api.com
script:
- npm run ci
- npm run restart
Taro 微信小程序项目
Taro 项目的上传其实并不复杂,不过我们首先需要撰写一个提供给 CI 程序使用小程序上传文件,如下,是一个简单的上传脚本文件
小程序上传脚本
// build/ci-upload.js
const ci = require('miniprogram-ci')
const path = require('path')
const project = new ci.Project({
appid: 'wxappid',
type: 'miniProgram',
projectPath: path.resolve(__dirname, '../dist'),
// 微信上传的 Key
privateKeyPath: path.resolve(__dirname, 'upload-key.key'),
})
ci.upload({
project,
version: '1.0.0',
desc: 'Upload by CI',
onProgressUpdate: (p) => {
console.log(p._msg, p._status)
},
}).then(() => {
console.log('upload success')
})
微信上传的 Key 来自于 微信公众平台,开发模块下的开发设置中
项目部署
和其他项目不同的是,我们小程序不需要多环境配置,当然,如果你感兴趣的话,完全可以将预览和上传 Source Map 集成在策略中,这也是一次很好的实践,对大家来说,无非多耗点时间罢了,接下来看我们的配置文件
stages:
- test
- build
- deploy
cache:
paths:
- node_modules/
test:
stage: test
tags:
- shell-g-fe-runner
script:
- npm install --no-optional --registry=https://registry.npm.taobao.org/
- npm run lint
build:
stage: build
tags:
- shell-g-fe-runner
script:
- npm run build:weapp
artifacts:
paths:
- dist/
expire_in: 60 mins
only:
- master
deploy:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://weixin.qq.com
script:
- node build/ci-upload.js
服务端存储 Private Key
一般情况下,我们会将涉及敏感内容的文件存放在服务器上面,那么这时候,也可以选择不使用上传文件的形式,直接使用命令行执行操作
deploy:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://weixin.qq.com
script:
- miniprogram-ci upload --pp dist/ --pkp /data/private-key/mini-key.key --appid weixinappid --uv 1.0.0 --ud "Upload By CI"
具体细节请参考 微信 CI 工具
Uniapp 项目
uni-app 的项目则相对麻烦一点,我们会针对 HBuilderX 创建的项目进行部署操作,首先要做的就是搭建一个通用的编译框架
生成编译框架
我们可以通过 uni-app 提供的 vue-cli 模板来默认生成一个项目
vue create -p dcloudio/uni-preset-vue ci-uploader
在生成的项目中,我们同样需要新建一个 .gitlab-ci.yml 文件,因为编译框架也会出现更新以及部署的情况,其次,我们需要删除 src 文件夹以及其所有的文件,这样的话,我们便拥有了一个简易的编译框架
鉴于 uni-app 中可能常用到的 npm 库,我们需要在编译框架中安装这些库,并保证版本统一
npm i vue vuex -S
npm i node-sass sass-loader -D
如果你的项目中也存在常用的库,也都需要安装到编译框架之中,接下来是编译框架的 CI 文件配置
stages:
- deploy
cache:
paths:
- node_modules/
deploy:
stage: deploy
tags:
- shell-g-fe-runner
script:
- sudo npm install --no-optional --registry=https://registry.npm.taobao.org/
- sudo npm rebuild node-sass
- rm -rf /data/mp/ci-uploader
- mkdir -p /data/mp/ci-uploader
- cp -R ./* /data/mp/ci-uploader
only:
- master
如配置所示,我们将编译框架放在了 /data/mp/ci-uploader 这个位置,而我们即将使用到这个位置执行发布
项目部署
将 HBuilderX 的项目转化为 vue-cli 项目相当简单,只要将所有的文件从原项目中移动到 vue-cli 项目中的 src 文件夹中,即可进行编译操作,这也是我们刚刚删除掉 src 文件夹的目的,接下来看我们的配置文件,当前配置文件是处于要发布的小程序当中,非编译框架之中
stages:
- deploy
deploy:
stage: deploy
tags:
- shell-g-fe-runner
script:
- MPNAME=my-mp-app
- rm -rf /data/mp/$MPNAME
- mkdir -p /data/mp/$MPNAME
- cp -R /data/mp/ci-uploader/* /data/mp/$MPNAME/
- mkdir -p /data/mp/$MPNAME/src
- cp -R ./* /data/mp/$MPNAME/src
- npm run build:mp-weixin
- miniprogram-ci upload --pp dist/ --pkp /data/private-key/mini-key.key --appid weixinappid --uv 1.0.0 --ud "Upload By CI"
only:
- master
相信这段代码对大家来说不在话下,但大家可能疑惑为什么不直接把项目复制到编译框架中去,而要单独复制出来一份,因为小程序不止一个,如果只依赖编译框架的一个文件夹,任务就无法同步执行了,而我们显然不想看见这种情况,这也是小花团队的最佳实践
环境管理与回滚
在上面诸多的任务声明中,我们都看到了一个参数 environment,对于单个任务来讲,它可以说是一个环境的辨识,但最重要的是,设置这个参数可以让我们在 GitLab 中很轻松的完成环境管理和回滚操作,相信对于大家,这个也是一个极其重要的步骤,也没人能保证,我们的每一步发布策略都不会出错
在 GitLab 项目面板,点击运维 -> 环境面板,就可以看到我们的环境管理啦
你同样也可以手动在这里新建环境,但是这里的环境不会同步到 .gitlab-ci.yml 文件中,还需要你手动同步,所以小花不建议在这里直接新建环境,点击任意一个环境进去,我们就可以看到回滚、重新部署、暂停服务等诸多环境操作
点击回滚操作,GitLab 就会以你当前选中的 Commit 记录为目标,重新部署环境,十分方便的操作
多服务器部署
上面我们分析了四种项目的部署方式,Vue 项目和 Node 项目,无疑都是需要多环境部署的,甚至在大多数公司之下,多环境是由多机器或多服务器来区分的,不同的环境下部署不同的服务用CI怎么完成呢
记得项目环境分析的时候,小花说过,如果你是多服务器部署的话,那么你就需要安装 rsync 这个工具,相信运维的同事都对此不陌生,是的,远程同步,只要我们安装了这个工具的话,远程部署就相对来说较为简单了,rsync 的使用网上五花八门,小花就不赘述了,我们直接来看部署策略
Vue 项目
Vue 项目很简单,我们只需要把编译好的产物远程发送到服务器所在的机器上就能完成操作
deploy_prod:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://vue.com/
script:
- rsync -ravtz --delete --password-file=/data/auth/rsync.pwd dist/* 192.168.1.1::vue-com-prod/
rsync 的授权文件需要存储在服务器,指定远程机器的地址及映射之后,我们就能完成自动部署了
Node 项目
Node 项目的远程部署相对麻烦一点,因为我们不仅要把文件远程过去,还要重新启动 NodeJs 服务,小花团队的解决方案是这样的,先编译好 Node 服务,然后打包成 tar,远程同步到服务器上,再通过远程 ssh 完成重启操作,是不是很复杂,其实还好,我们看脚本
#!/bin/sh
#
ssh root@192.168.1.1 "
cd /data/api/node
tar -xf release.tar.gz
npm run restart
"
这个是一个很简单的远程命令,你可以将它放在任意位置,我们暂定是 /data/bin/up_node_api, 然后执行授权操作 chmod 755 /data/bin/up_node_api,接着我们的发布文件就可以这样编写
deploy_prod:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://node-api.com/
script:
- npm run ci
- rm -f /data/api/temp-files/release.tar.gz
- tar -zcf /data/api/temp-files/release.tar.gz .
- rsync -ravtz --delete --password-file=/data/auth/rsync.pwd /data/api/temp-files/release.tar.gz 192.168.1.1::/data/api/node/
- /data/bin/up_node_api
如果你的 Node 服务也要做缓存操作,可以这样写入,必须将产物 Copy 到原项目中才能执行缓存,因为 GitLab Runner 不支持工作目录之外的缓存操作
deploy_prod:
stage: deploy
tags:
- shell-g-fe-runner
only:
- master
environment:
name: Production
url: https://node-api.com/
artifacts:
paths:
- release.tar.gz
script:
- npm run ci
- rm -f /data/api/temp-files/release.tar.gz
- tar -zcf /data/api/temp-files/release.tar.gz .
- rsync -ravtz --delete --password-file=/data/auth/rsync.pwd /data/api/temp-files/release.tar.gz 192.168.1.1::/data/api/node/
- /data/bin/up_node_api
- cp /data/api/temp-files/release.tar.gz .
结语
好了,上面的方案便是小花目前团队使用的自动化部署方案了,没有太多花里胡哨,都是干货,当然我们的方案并非万能,也不甚完美,如果你有更好的建议,欢迎给小花留言,小花也会积极回复,如果文中有不理解的地方,小花也期待大家的咨询
期待大家的关注,小花会与大家一起成长,加油!!!