前言
这个是基于 jenkins搭建的多分支自动部署服务
建议先看准备篇
1 Jenkins安装
现在我们要使用docker 来安装jenkins了
- 我们先下载 jenkinsci/blueocean 镜像吧
#运行这个命令会查看仓库的jenkins镜像资源
docker search jenkins
#运行后你会看到类似这样的
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
jenkins Official Jenkins Docker image 4863 [OK]
jenkins/jenkins The leading open source automation server 2154
jenkinsci/blueocean https://jenkins.io/projects/blueocean 544
jenkinsci/jenkins Jenkins Continuous Integration and Delivery … 382
jenkins/jnlp-slave a Jenkins agent which can connect to Jenkins… 129 [OK]
jenkinsci/jnlp-slave A Jenkins slave using JNLP to establish conn… 126 [OK]
jenkinsci/slave Base Jenkins slave docker image 65 [OK]
jenkins/slave base image for a Jenkins Agent, which includ… 43 [OK]
jenkinsci/ssh-slave A Jenkins SSH Slave docker image 42 [OK]
···
#我们选择下载 jenkinsci/blueocean(这个是jenkins集成blueocean的插件,可视化流水线)
docker pull jenkinsci/blueocean
#下载后我们可以运行docker images 命令查看本地镜像
docker images
- 生成jenkins应用的容器
docker run \
--name jenkins \
-d \
-it \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc/localtime:/etc/localtime:ro \
jenkinsci/blueocean
我们先来看看命令行都是啥意思
- **docker run: **创建一个新容器
- **--name jenkins: **将这个容器命名为jenkins(这个后面方便操作)
- **-d: **这个参数能让容器到后台运行
- -it: -i 以交互模式运行容器,-t 为容器重新分配一个伪输入终端
- -p: 端口映射,格式为:主机(宿主)端口:容器端口
- -v: 指定一个volume,将容器的/var/jenkins_home的数据持久化保存到名为 jenkins的volume上,这个归主机docker统一管理
- -v /var/run/docker.sock:/var/run/docker.sock: jenkins 会用docker容器来做agent,有必要的话把他们缓存下来
- -v /etc/localtime:/etc/localtime:ro : 同步容器和主机的时间,ps:这玩意在mac上不好使
- jenkinsci/blueocean: 这个是image镜像文件,docker将以此镜像文件创建一个容器,如果本地不存在会自动拉去线上最新的版本
这样我们就成功创建运行一个jenkins应用的容器
运行成功后,如果你是阿里云,你要去添加实例的安全组规则
因为上面我的jenkins是代理到主机的8080,所以设置阿里云主机
这样用你的主机的 ip:8080就能看到jenkins的启动页面
这个密码 运行查看jenkins容器的日志,拉到最后可以看到
docker logs jenkins
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
cb619dcc57574a6592d96asdasdasjasd
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
*************************************************************
*************************************************************
耐心的等待安装完成
创建第一个管理用户
老板,看到jenkins了!
jenkins的入门教程必须要花时间先看一下,主要了解一下任务啊,声明式和脚本式的两种流水线写法啊,流水线步骤啊
2 Jenkins连接Gitee
- 安装插件
我们要先让jenkins跟git托管仓库连接,gitee、gitlab、github都行,这个我们要安装几个插件
- Gitee
- Multibranch Scan Webhook Trigger
- Generic Webhook Trigger
安装好后现在先全局配置一下Gitee配置,看**官方的教程**
- 配置SSH连接gitee
ssh env #先连接env服务器
docker ps #查看正在运行的docker 容器找到jenkins的容器name
docker exec -it jenkins /bin/bash #在运行中的jenkins容器,生成ssh密钥对(看图吧)
还没结束、因为是自定义名字,需要再 ~/.ssh目录下新建一个config配置文件
cd ~/.ssh/
vi config
#复制下面的内容
-----------------------------------------------
Host gitee.com
Preferredauthentications publickey
IdentityFile ~/.ssh/jenkins-gitee
-----------------------------------------------
这样才能成功执行ssh连接
生成后的公钥复制在gitee上,个人配置那
创建jenkins链接gitee的凭证,当然也可以在下面新建流水线的时候在添加
3 multi branch pipeline
创建流水线任务的时候最好把全局的执行器设置会3个以上,不然我们这个仓库触发多有分支构建的时候会不够资源导致失败
现在我们开始来新建任务吧
按图上设置分支源、设置webhook的token等
设置好存,第一次会会自动拉去仓库的代码去执行构建任务
点击状态,可以看到分支任务拉去的情况,我们项目的feature分支和hoxfix分支是不作为构建任务的
这个就是dev分支对应的pipeline任务步骤啦
因为我们安装的是jenkinsci / blueocean (就是jenkins安装了blueocean插件的集合),这个方便我们对任务的可视化
点击左侧导航的Blue Ocean
分别点击 dev、release、master分支,看到
可以注意到我们的分支,在dev和release启动的时候会自动持续交付,到master的时候多了一个选择按钮,因为master是生产环境,我们一般是需要手动部署上线好
这个几个流水线是基于每个分支的Jenkinsfile来生成运行的
4 webhook 设置
说道这个刚刚只是新建了仓库后自动拉去分支触发的任务,现在我们要来设置webhook触发。
webhookURL:http:你的jenkinsurl/multibranch-webhook-trigger/invoke?token=vueci
现在我们来试一下触发webhook,为了方便我们先不新建功能分支了,直接在dev分支行做push
我们来给jenkinsfile做点修改吧
- 添加新的代码,add,commit,push
echo “test wehook”
- 看我们的jenkins blue-ocean界面,dev分支的任务触发了
等成功后,点击步骤 pre-build 可以看到刚刚添加的echo信息
当然也可以新建分支 feature-*,推送到远仓库,请求合并到release分支,合并成功后也会触发release的任务
5 pipeline的stage 简介
上面大概流程完成了,我们现在来看一下,pipeline上的 pre-build、build-parellel:[ build-dev | build-master | build-release ]、artifacts-manage、deliver、deloy
这些是流水线pipeline的步骤stage,我们在Jenkinsfile里面自定义的,咦,好像少了test步骤,因为我这里不做自动化测试哈哈哈,需要的慢点自己加上就行
在设置pipeline的stage前,我们先来明白我们要做什么
pre-build:打包前的工作
- 设置缓存目录 和 文件
- 判断是不是最近构建过,构建过直接调用缓存的包,重新部署就行,不用重新build什么的
- 判断需不需要重新安装node_modules包依赖
build-env:打包
- 按分支运行打包的脚本
artifacts-manage:制品管理
- 构建后的资源 /dist 打包压缩,到缓存目录或者仓库,归档
deliver:交付
- 这个步骤一步给开发环境、测试环境,预发布环境用,打包后的代码自动交付到这几个环境
deploy:部署
- 这个给生产环境用,打包后到这里会设置一个按钮,手动点击再运行部署上线脚本
6 Jenkinsfile详解
jenkins通过读取我们项目根目录这个Jenkinsfile,来生成相应的流水线任务,这个内容比较多,做成使用目录方便阅读吧
现在我们要看看这个pipeline的配置文件Jenkinsfile,还是要啰嗦一下,对着官方教程扫几遍教程+做一次,对着官方教程扫几遍教程+做一次,对着官方教程扫几遍教程+做一次,重要的事情我说三遍了,希望老板你能做一遍
1 Jenkinsfile 预览
我们先来看看Jenkinsfile
pipeline {
agent any // agent节点,any告诉jenkins 任何可用的agent都能执行
environment {
Name = 'Eric' // 这个是自定义的顶级pipeline全局变量
}
options {
disableConcurrentBuilds() // 在多分支的流水线中限制并发
}
stages {
stage('pre-build') { // 自定义步骤 pre-build
when {
anyOf { // 多分支才有的判断,一个真就通过过
branch 'dev'
branch 'release'
branch 'master'
}
}
agent { // 代表阶段运行在一个代理的docker nodejs容器中
docker {
image 'node:10.21.0'
// 想在 docker 容器中运行代码,但是也想使用流水线所定义的相同节点或和工作空间,必须设置这个
reuseNode true
}
}
steps {
sh "printenv"
echo "pre-build"
echo "test webhook"
}
}
stage('build-env') {
when {
anyOf {
branch 'dev'
branch 'release'
branch 'master'
}
}
// 如果设置了true 那么我们有任何一个并发步骤失败那就全都都失败,
// 因为我们是用来做不同分支任务触发时候的构建选择,所以这个不需要
failFast false
parallel {
stage('build-dev') {
when {
// beforeAgent 是指在进入agent ,如果when的条件对,才进入,错则不进入
// 就是可以加快流水线的运行啦
beoreAgent true
branch 'dev'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps {
echo "build-dev"
}
}
stage('build-release') {
when {
beforeAgent true
branch 'release'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps{
echo "build-release"
}
}
stage('build-master') {
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
when {
beforeAgent true
branch 'master'
}
steps {
echo "build-master"
}
}
}
}
stage("artifacts-manage"){
steps {
echo "artifacts"
}
}
stage('deliver') {
when {
beforeAgent true
anyOf {
branch 'dev'
branch 'release'
}
}
steps {
echo "start deliver"
}
}
stage('deploy') {
when {
beforeAgent true
branch 'master'
}
steps {
// 这个就是生成一个按钮,我们用来手动发布的
input message: "Should you deploy?"
echo "start deloy"
}
}
}
//post 就是流水线的运行结果状态啦,我们慢点会在这里设置邮件通知
post {
changed{
echo 'I changed!'
}
failure{
echo 'I failed!'
}
success{
echo 'I success'
}
always{
echo 'I always'
}
unstable{
echo "unstable"
}
aborted{
echo "aborted"
}
}
}
我们这个脚本是一个声明式流水线,相比脚本式的流程更加清楚明白,也是能更适合生成Blue-Ocean这种可视化的流水线
读懂上面代码必需要知道的pipeline流水线语法知识点:
- pipeline:代表整条流水线,包含整条流水线的逻辑
- environment: 指令制定一个 键-值对序列,该序列将被定义为所有步骤的环境变量,或者是特定于阶段的步骤, 这取决于
environment
指令在流水线内的位置。 - **stages:**stage的容器,stages部分至少有一个stage
- **stage:**流水线的阶段、必须指定名字,stage(“名字”)
- **steps:**代表阶段中的一个或者多个步骤。一个stage有且只有一个steps
- agent : 指定流水线的执行位置。流水线中的每个stage都必须在某个地方(物理机、虚拟机、docker容器)执行。agent部分就是负责指定执行环境,pipeline下的agent指定的是所有stage的执行环境
- when: 指令允许流水线根据给定的条件决定是否应该执行阶段。
- beforeAgent 是指在进入agent ,如果when的条件对才进入,错则不进入,提高构建速度
- branch 这只适用于多分支流水线。
- expression当指定的Groovy表达式评估为true时,执行这个阶段, 例如:
when { expression { return true } }
- anyof 当至少有一个嵌套条件为真时,执行这个阶段,必须包含至少一个条件,例如:
when { anyOf { branch 'master'; branch 'staging' } }
- post: 就是流水线的运行结果状态啦,我们慢点会在这里设置邮件通知
- parallel: 声明式流水线的阶段可以在他们内部声明多隔嵌套阶段, 它们将并行执行。 注意,一个阶段必须只有一个
steps
或parallel
的阶段。
更加完整的信息请在官方文档上查阅吧
2 pre-build 阶段
接下来我们要开始写pre-build步骤的脚本啦啦啦
pre-build 要实现的功能
- 判断和新建&更新缓存 目录 & 文件
- 判断是不是最近构建过,构建过设置回滚标志文件,供后面阶段构建跳过执行、直接调用缓存的包重新部署就行
- 通过package的缓存和内容的查询对比,判断需不需要删除旧node_modules,运行
npm install
重新安装包依赖
- 开始之前我们设置几个pipeline的全局变量
pieline{
environment {
cacheDir = "stage" //定义缓存的目录名字
cachePackage = "${cacheDir}/package.json" //定义缓存的package.json
cacheCommitIDFile = "${cacheDir}/.commitIDCache.txt" //把成功打包的commitID缓存到这里
artifactsDir = "${cacheDir}/artifacts" //制品缓存的目录,构建成功的制品我们放这里
resetFlagFile = "${cacheDir}/.resetFile" //回滚标志的文件
cacheCommitIDMax = 5 //缓存版本的最大值
}
...
}
- 步骤
stage('pre-build') { // 自定义步骤 pre-build
stage('pre-build') { // 自定义步骤 pre-build
when {
anyOf { // 多分支才有的判断,一个真就通过过
branch 'dev'
branch 'release'
branch 'master'
}
}
agent { // 代表阶段运行在一个代理的docker nodejs容器中
docker {
image 'node:10.21.0'
// 想在 docker 容器中运行代码,但是也想使用流水线所定义的相同节点或和工作空间,必须设置这个
reuseNode true
}
}
steps {
sh "printenv" //打印jenkins全局环境变量
sh './jenkins/script/pre-build.sh' //我们把主要的功能写在pre-build.sh脚本里面
}
}
然后新建pre-build.sh脚本 jenkins/script/pre-build.sh
新建好需要执行,给脚本执行权限
chmod +x jenkins/script/pre-build.sh
#!/bin/bash:此脚本使用**/bin/sh**来解释执行
#!/bin/bash
isResetID=false #回滚标志判断
cmpFlag=1 #package.json文件的对比标志
packageJsonChange=false #package.json文件变化标志
# set -x
#先设置淘宝源加快安装速度,这里我们慢点要替换成我们自己搭建的更快
npm config set registry https://registry.npm.taobao.org
#判断缓存目录存在与否
if [ ! -d $cacheDir ]
then
echo "no cache dir"
mkdir $cacheDir
cp package.json "$cacheDir/"
touch $cacheCommitIDFile
# echo $GIT_COMMIT > $cacheCommitIDFile #先不用写入
echo "npm 安装"
npm i || exit 1
#判断缓存文件存在与否
elif [ ! -f $cachePackage ] || [ ! -f $cacheCommitIDFile ]
then
if [ ! -f $cachePackage ]
then
echo "cache file package.json does no exist"
echo "cp package.json to cache dir"
cp package.json "$cacheDir/"
fi
if [ ! -f $cacheCommitIDFile ]
then
echo "cache file commitIdCache.txt does no exist"
echo "create commitIdCache.txt to cache dir"
echo "write the ID to commitIdCache.txt"
touch $cacheCommitIDFile
echo $GIT_COMMIT > $cacheCommitIDFile
fi
rm -rf node_modules
sleep 1
echo "npm 安装"
npm i || exit 1
else
echo "cache file exists"
#这里我们判断一下新提交的commitID是不是有存在于缓存
for commitId in `cat "$cacheCommitIDFile"`
do
if [ $commitId = $GIT_COMMIT ]
then
isResetID=true
fi
done
#对比新旧的package.json,看是否有变化
cmp -s package.json $cachePackage
cmpFlag=$?
#compare package.json
if [ $cmpFlag != 0 ]
then
packageJsonChange=true
fi
#如果是重复的ID
if [ $isResetID = true ]
then
echo "reset啦啦啦啦"
#我们还是需要重置一下新的package
cp -f package.json "$cacheDir/"
#这里我们要一个创建代表回滚标志的文件,提供给pipeline判断以供跳过不必要的步骤执行,提高执行的速度
touch ${resetFlagFile}
# ID不是重复的,而且package.json有更新
elif [ $packageJsonChange = true ]
then
echo "package 更新"
cp -f package.json "$cacheDir/"
rm -rf node_modules
sleep 1
npm i || exit 1
else
ehco "啥都不用干哈哈"
fi
fi
# set +x
更新push到远程仓库,去jenkins查看构建状态
- 构建成功后,我们进去工作空间看看,通过web界面或者docker 容器命令行
- 通过web 界面很简单,就是点点点
- 命令行
#工作空间一般命名是项目名字_分支名
ssh env
docker exec -it jenkins /bin/bash
cd var/jenkins_home/workspace
cd vue-ci-start_dev
ls
我们可以看到我们在pre-build 步骤里面,创建的 目录stage和文件stage/package.json、stage/.commitIDCache.txt
当然也有拉取的项目文件和安装的node_modules等
PS:记住这个文件.commitIDCache.txt,我们用来判断有没有重复ID的,当我们打包压缩成功后,我们才会去把本次的commitID写到文件里面去
3 build-env 阶段
- 按分支运行打包的脚本
这里主要是构建我们的应用啦,因为我们有dev、release、master环境的代码需要构建,所以用了stage的parallel,
当webhook触发SCM触发任务时,我们通过when判断是那个分支就运行哪个分支的构建步骤
stage('build-env') {
when {
anyOf {
branch 'dev'
branch 'release'
branch 'master'
}
}
// 如果设置了true 那么我们有任何一个并发步骤失败那就全都都失败,
// 因为我们是用来做不同分支任务触发时候的构建选择,所以这个不需要
failFast false
parallel {
stage('build-dev') {
when {
// beforeAgent 是指在进入agent ,如果when的条件对,才进入,错则不进入
// 就是可以加快流水线的运行啦
beoreAgent true
branch 'dev'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps {
echo "build-dev"
sh "./jenkins/script/build-dev.sh"
}
}
stage('build-release') {
when {
beforeAgent true
branch 'release'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps{
echo "build-release"
sh "./jenkins/script/build-release.sh"
}
}
stage('build-master') {
when {
beforeAgent true
branch 'master'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps {
echo "build-master"
sh "./jenkins/script/build-master.sh"
}
}
}
}
在jenkins/script目录下新建三个构建脚本:build-dev.sh 、build-release.sh、build-master.sh
分别对应dev开发环境的构建、release测试环境的构建、master线上生产环境的构建
#build-dev.sh
npm run build-dev || exit 1
#build-release.sh
npm run build-release || exit 1
#build-master.sh
npm run build || exit 1
PS:记得给脚本执行 chmod +x
给执行权限
运行的具体脚本是在package.json里面定义srcipt的
"scripts": {
"serve": "vue-cli-service serve",
"build-dev": "vue-cli-service build --mode dev",
"build-release": "vue-cli-service build --mode prod",
"build": "vue-cli-service build ",
"lint": "vue-cli-service lint"
},
--mode [mode]就是指加载 根目录下的.env.[mode]文件:例如 --mode dev
就是加载根目录下的 .env.dev文件
具体可以看vue-cli的官方文档
现在测试一下在各个分支的运行打包情况吧
4 artifacts-manage 阶段
- 构建后的资源 dist 打包压缩存到缓存目录或者仓库,除了可以下载查阅,也方便分支回滚后快速部署
- 我们还设定了制品储存的数量,免得任务多的时候,构建次数一多硬盘空间不够
我们小团队用的是下面这中最朴素的归档打包方法,当然要逼格你可以自己搭Nexus等平台
到了制品管理阶段啦,我们成功打包后的文件都在根目录的dist目录下,这个是webpack控制的,可以改
Jenkinsfile添加执行脚本代码
stage("artifacts-manage"){
steps {
echo "artifacts"
sh './jenkins/script/artifacts-manage.sh'
}
}
在jenkins/script目录下新建脚本:artifacts-manage.sh,chmod +x
给执行权限
#!/bin/bash
#set -x
#创建缓存目录
if [ ! -d $artifactsDir ]
then
mkdir $artifactsDir
fi
#打包压缩 dist 文件
tar -zcvf $artifactsDir/${GIT_COMMIT}_dist.tar.gz dist
FileNum=$(ls -l $artifactsDir | grep ^- | wc -l)
#更新缓存库的内容
while [ $FileNum -gt $cacheCommitIDMax ]
do
OldFile=$(ls -rt $artifactsDir/* | head -1)
echo "Delete File:$OldFile"
rm -f $OldFile
let "FileNum--"
done
#这里可以把commitID存到之前的缓存文件
CommitIDNum=`cat $cacheCommitIDFile | wc -l`
if [ $CommitIDNum -ge $cacheCommitIDMax ]
then
sed -i '1d' $cacheCommitIDFile
fi
echo
sleep 1
echo $GIT_COMMIT >> $cacheCommitIDFile
# set +x
我们再做一次push触发jenkins的流水线,构建好后我们去jenkins任务的工作空间
我们看到项目跟目录下有打包后生成的 dist 文件夹、stage 里面有我们的缓存文件
查看 .commitIDCache.txt 文件发现了我们把这次的git commit ID :16c0451ba1368ada4e29ad6ac3ffb44f0ddb52a0 写入在里面了
再看一下 stage/artifacts目录下的文件,发现有我们打包压缩后的文件啦 16c0451ba1368ada4e29ad6ac3ffb44f0ddb52a0_dist.tar.gz
因为我们设置了缓存五个版本,我们来测试一下功能:在做5次提交push,看看会不会除去一开始这个缓存的ID和文件,看上图可以看到
缓存的commit ID 和 文件都更新到了最近的版本,之前第六个版本已删掉啦啦啦😝
但是这样我们先简单的下载本次成功构建后归档的东西好像还有点麻烦啊,别慌,jenkins还提供了archiveArtifacts 步骤
stage("artifacts-manage"){
steps {
echo "artifacts"
sh './jenkins/script/artifacts-manage.sh'
archiveArtifacts artifacts:"${artifactsDir}/${GIT_COMMIT}_dist.tar.gz" // 把我们本次构建的压缩文件归档
}
}
这样我们就能很方便的jenkins的web页面下载啦
# 用archiveArtifacts归档的文件会等放在这里哈
${JENKINS_HOME}/jobs/${JOB_NAME}/branch/${BRANCH_NAME}/builds/${BUILD_NUMBER}/stage/archive
5 reset 阶段
-
这个是一个检测回滚操作的时候执行的步骤
-
在这里我们需要为 build-env、artifacts-manage等步骤增加点代码,回滚的时候不需要执行他们
还记得我们前面在pipeline的环境设置的变量 resetFlagFile 吗,还有在 [pre-build 阶段](#2 pre-build 阶段) 如果出现相同的commit ID的操作吗
if [ $isResetID = true ]
then
#我们还是需要重置一下新的package
cp -f package.json "$cacheDir/"
#这里我们要一个创建代表回滚标志的文件,提供给pipeline判断以供跳过不必要的步骤执行,提高执行的速度
touch ${resetFlagFile}
# ID不是重复的,而且package.json有更新
...
检测到相同的commit ID 了,创建这个文件 ${resetFlagFile}
,用来做什么?当然是做回滚判断标志呀
在 build-env、artifacts-manage步骤的when添加
when {
beforeAgent true
+ expression{
+ return !(fileExists("${resetFlagFile}"))
+ }
...
}
fileExists()
是pipelinen内置的方法,判断文件是否存在,返回布尔类型
现在我们先做一个普通的push再做一次回滚的push来看看效果
#先做两次正常的commit push
git log
commit 7f31a184256b326cb353c506125d58b0efe28481 (HEAD -> dev, origin/dev)
Author: longming <451904906@qq.com>
Date: Thu Aug 6 13:35:32 2020 +0800
正常的提交2
commit acbc3ea45dca16c82a39f2ec6e772eada57229f7
Author: longming <451904906@qq.com>
Date: Thu Aug 6 13:33:20 2020 +0800
正常的提交
在做执行回滚到版本 acbc3ea45dca16c82a39f2ec6e772eada57229f7
git reset -hard acbc3ea45dca16c82a39f2ec6e772eada57229f7
git push -f
进入工作空间,查看stage的.commitIDCache.txt、和artifacts目录下的压缩包,可以看到写入和缓存都成功了
第二次回滚后文件没改变
我们来看一Blue Ocean的流水线图
我们来看一下,回滚操作中跳过了 build-env 和 artifacts-manage 这两个阶段,因为不需要再重新去build和缓存了,这样就能做到秒级的回滚啦啦啦
reset 阶段的代码在这里
stage("reset"){
when {
beforeAgent true
expression{
return fileExists("${resetFlagFile}")
}
anyOf {
branch 'dev'
branch 'release'
branch 'master'
}
}
steps {
echo "是回滚啊啊啊啊"
//当然我们这里为了方便下载
archiveArtifacts artifacts:"${artifactsDir}/${GIT_COMMIT}_dist.tar.gz"
}
}
6 deliver阶段
这个步骤一步给开发环境、测试环境,预发布环境用,打包后的代码自动交付到这几个环境
-
安装资源服务器
我们这里安装的是Nginx,详细看 Nginx
-
配置SSH
这个阶段要先配置好我们的jenkins容器 到 需要资源服务器的 ssh,具体看 SSH配置
问题来了,部署机怎么来?我是在阿里云🌥梭哈了一台几个月的香港服务器哈哈哈🍖(我们大陆的话,网站要先备案好才能通过)
我们配置好了jenkins ssh 连接 资源服务器,host的名字设置为server
在jenkins应用容器执行
ssh server
就能连接到资源服务器啦ps:如果配置连接的用户名,不是要上传目录 /data的拥有者,请最好更改一下权限
#切换到root,把/data目录的拥有者给eric,eric是我们这里ssh连接到资源服务器的用户名 chmod -R eric /data
-
deliver 脚本
在Jenkinsfile文件添加ssh连接的host name作为环境变量
pipeline{ ... environment { ··· + sshHostName = "server" } ... }
deliver stage的代码
stage('deliver') { when { beforeAgent true anyOf { branch 'dev' branch 'release' } } steps { echo "start deliver" sh "./jenkins/script/deliver.sh" } } // 整个pipeline流程结束需要删除回滚标志文件 post{ alway{ } }
新建deliver.sh脚本 jenkins/script/deliver.sh
#!/bin/bash # set -x #如果是回滚的操作,我们不用再把构建上传一次啦,服务器已经有啦 if [! -d ${resetFlagFile}] then # 将本次构建的压缩包上传到资源服务器的指定目录 scp ${artifactsDir}/${GIT_COMMIT}_dist.tar.gz ${sshHostName}:/data/vueci/${BRANCH_NAME}/ || exit 1 fi # 远程执行服务器上面的脚本解压缩发布 ssh ${sshHostName} /data/vueci/deploy.sh ${BRANCH_NAME} ${GIT_COMMIT} || exit 1 # set +x
新建好需要执行,给脚本执行权限
chmod +x jenkins/script/deliver.sh
我们还得跑到资源服务器新建一个发布的脚本**/data/vueci/deploy.sh**
tar -zxvf /data/vueci/$1/$2_dist.tar.gz -C /data/vueci/$1/
新建好需要执行,给脚本执行权限
chmod +x /data/vueci/deploy.sh
现在来测试一下交付到dev环境吧
git checkout dev #更改点什么 git commit -am "发布到dev环境啦" git push
看到效果了,其实是开发环境哈哈哈随便写的哈哈哈
7 deploy 阶段
到了发布阶段啦,这个是运用在生产环境分支上的,发布前的按钮
这个步骤跟deliver差不多,多一个发布的input,来看一下代码
stage('deploy') {
when {
beforeAgent true
branch 'master'
}
steps {
// 这个就是生成一个按钮,我们用来手动发布的
input message: "Should you deploy?"
echo "start deloy"
sh './jenkins/script/deploy.sh'
}
}
新建发布脚本deploy.sh ,jenkins/script/deploy.sh
给脚本执行权限
chmod +x /data/vueci/deploy.sh
deploy.sh 脚本跟 交付的没啥区别(当然一般是正式环境和测试环境执行的脚本的一般是不同的)
#!/bin/bash
# set -x
#如果是回滚的操作,我们不用再把构建上传一次啦,服务器已经有啦
if [ ! -f $resetFlagFile ]
then
# 将本次构建的压缩包上传到资源服务器的指定目录
scp ${artifactsDir}/${GIT_COMMIT}_dist.tar.gz ${sshHostName}:/data/vueci/${BRANCH_NAME} || exit 1
fi
# 远程执行服务器上面的脚本解压缩发布
ssh ${sshHostName} /data/vueci/deploy.sh ${BRANCH_NAME} ${GIT_COMMIT} || exit 1
# set +x
现在按照最开始的 [Git flow 工作流](#Git flow 工作流) 介绍的,我们走个git flow 流程吧
ps:🌏**注意了,我们这里是把dev 当做feature功能分支哈!!!**注意了,我们这里是把dev 当做feature功能分支哈!!!
注意了,我们这里是把dev 当做feature功能分支哈!!!
先切换到dev分支
#切换到本地dev
git checkout dev
#更新远程dev,如果有多人协调在这个分支做开发的话
git pull
我们把之前的显示的内容改一下
- <HelloWorld msg="欢迎来到测试环境呀" />
+ <HelloWorld msg="欢迎来到正式环境环境呀" />
#一系列的提交操作
git status
git add .
git commit -m "修改下显示内容"
git push
#我们功能分支是从远程master分支checkout出来的,开发过程中可能master上线了其他的功能,需要更新一下
git merge origin/master
#如果合并发生冲突,我们解决好冲突后
git status
git add .
git commit "origin/master"
#解决好冲突或者没有冲突直接push啦
git push
现在我们要去远程仓库申请功能合并到release去测试啦
我们看到合并成功后,会触发构建任务啦
PS:如果dev 合并 release 有冲突的话,可以先把release 合并到 dev 解决冲突后再申请合并,这样在gitee合并那边就没冲突了
合并release测试好
现在我们准备上线,需要把release 合并到 master 分支啦
合并成功后,我们可以看到
合并成功后我们停留在deploy阶段,看一下正式环境的代码,还没更新
先我们点击上面deploy,或者那个 继续 按钮,就能发布啦
8 Notice 通知
-
上面的部署啊什么的完成了,可是什么时候完成我们不知道,只有通过不停的去刷网页啊才知道,或者问jenkins的管理者,这样就太麻烦了我们要给任务的完成状态配通知啊
公司有钉钉或者企业微信的的可以查一下资料配置很简单的,这里我们就讲一下最通用的邮件通知哈
配置邮件的通知我们要安装几个插件
Email Extension:这个用于邮箱的设置
**Config File Provider:**提供文件的存储插件,我们用来配置邮箱模板
本来我是配置了qq邮箱,可是接通知比较慢我换成了网易的163邮箱,当然一般公司都用企业邮箱,配置差不多都一样,看图吧
- 邮箱开启SMTP服务
打开我们的163邮箱开启,这两个SMTP服务,然后获取授权码,复制好待会要用
- jenkins 配置邮箱
配置完成之后记得测试一下邮件的发送
- 配置邮件模板
安装了这个插件之后Config File Provider,会在 **系统管理 **的下方看到这个,我们用来配置邮件的模板文件
点击去可以配置邮件模板文件
因为我们要在成功和失败的构建结束后都要发送邮件通知,所以我们需要两个邮箱模板,
这里我用sketch 设计了简单的邮件模板
对应邮件的模板代码:
email-success.tpl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
<style>
html,
body {
font-family: "SF Pro SC", "SF Pro Text", "SF Pro Icons", "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
.table.success {
border: 1px solid #ddd;
color: #4a4a4a;
}
.table.success td {
font-size: 16px;
overflow: hidden;
word-break: break-all;
}
td.info {
background-color: #dfb051;
height: 44px;
color: #fff;
}
td:first-child {
padding-left: 49px;
padding-right: 49px;
}
td.build-status {
background-color: #76ac35;
height: 44px;
color: #fff;
}
.table.success td pre {
white-space: pre-wrap;
}
.success-ico {
vertical-align: middle;
margin-left: 10px;
width: 25px;
/* height: 30px; */
/* margin-bottom: 20px; */
}
td.info-title {
color: #76ac35;
height: 36px;
font-weight: bold;
font-size: 16px;
}
.table.success ul {
list-style: none;
margin: 0;
padding: 0;
}
.table.success ul li {
list-style: none;
}
ul li {
height: 35px;
line-height: 35px;
font-size: 14px;
white-space: nowrap;
}
hr {
margin-left: 0;
}
</style>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="info">(大佬们好呀,本邮件是jenkins自动下发的,请勿回复!)</td>
</tr>
</table>
<br />
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="build-status">
构建成功啦
<img
class="success-ico"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAFp3oPPAAAAAXNSR0IArs4c6QAAA/ZJREFUWAm9mLtPVEEUxlkDAaKhQY1iKyWPAmNhYazUAmOMrZRYGQtii9FWwI4e/wA1JibamKixQChITEisqFgNaGI0FDasv2/unNm5r727sDLJt+f1nTNz53VvtqfHt0ajUTe9ZookgdeIN1ICwwiOSaBhDuRHp+O7JwX5wTm8MY7jlXOg9IO5EC1UYLgWglgDwTAFZ7/pJvFtOx1lInKOR/od6cQH9eOGhSxsliSiK4tcMGYIZhUIr0AdhG4DB2cywOBxY2k+CASbQy2Ba/jcjCETIoqbD6R7GrHQQ3fo4emTEgW/kOq9PjNey0Ct0TDWVdqtJzLXAjtWItaC/Njpp8YRdgj6AHBPHRcJuoJKEDE4vYJv0MdaFkh3n61SYFM0naNeYh62a7FPepEfX5KLkhyEbFYHtmrU+KmzPCPWYwf5PX5dNdL6MRKvo9jELnZQyHF97jWXh6GVcpsU2Quqmu1AXQv5FcS5DZrHo2R44ohbEk67IU4AO6Q6qNKrT0y6jFvqeRLXwV8v57OcQhtyH9gBN4oI8vt4X1FcSzcFVgqDGad44qfcOEbAg5SzwhBfeYGGsWkG+gnwzexYyq+4+dCTPJRH5pSUDdRGM/7RxJ3nK2kvJvtCT7O+Mr/L5+d+UUK7PuXrLK21m1DCW1MRe8MuUbWTtuSL1lTE1vtFSU9lbuNP6Y7Z4644LqaGUZaR9Uf3y55G4l4BnnQ3Sy6xY16SzwDizbasEbVoy1YYTsjTY6S2PfYw0EGMm+zhqEB62ysA4XAHMKp+uKvACplkZG1fSm6jWWKZpOAQMX2BzIDT4C14D3Tf6sT/ACfBWXAOXAZXwQ54BhbZEr+RB2sMQIv2GWyCwhuz3crK93VUr3mXVRWArAX/Azq6NKvqWlx1fX07uRZqSgjaLBr5StP7/zT1A1ZB+j2EYwjonEwXdY9fHwLW9lHCeSrh6xyKZy3/MUAiQXsxak8mDeeWAmZnpU9CuLbLb3+WE9uKA/GsVdXeUr4uTL3/J9ntt+KCR6kzhuf0t6Gp0ofJ7aPsPNuX+tc49JE3Br7EBAI3sSdjX5f1DVbiZVRT/Y9pME5Bfo2C59EfRna31ey14SakcM8wM3pvfgenuj0K6u2CM8zMvtWmv2TPyIGxBVI7HlvH8xfoZlO91LWArePtTpMbHIbdM6kB+YFWfQSQ3lYLHwuu02QS8veM77TlDUx3s6DTmRJ/1jqPJf7cDZx7a0O6QNI78Jh1fRIXMB2O9tQlcBHoxaep/wn0D88q+BTvCezQyNXm1d12Bc56CLRSSIrf2tOtuFUxak0Dvf1bvrVzM1NUmCJD+OfADMh+z2g2NCuaHc3Sgb9n/gFyaAu+lH9E5AAAAABJRU5ErkJggg=="
/>
</td>
</tr>
<tr>
<td class="info-title">
构建信息
<!-- <hr size="2" width="100%" align="center" /> -->
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${JOB_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因 : ${CAUSE}</li>
<li>构建日志 : <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>工作目录 : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
<hr size="2" width="100%" />
</td>
</tr>
<tr>
<td class="info-title">历史变更记录</td>
</tr>
<tr>
<td>
<ul>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新作者:%c",showPaths=true,changesFormat="[%a]",pathFormat=" %p"}
</li>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新信息:%c",showPaths=true,changesFormat="%m",pathFormat=" %p"}
</li>
</ul>
<!-- ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat=" %p"} -->
<!-- <hr size="2" width="100%" /> -->
</td>
</tr>
<!-- <tr>
<td class="info-title">构建日志(最后100行)</td>
</tr>
<tr>
<td><p><pre>${BUILD_LOG, maxLines=100}</pre></p></td>
</tr> -->
</table>
</body>
</html>
email-fail.tpl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
<style>
html,
body {
font-family: "SF Pro SC", "SF Pro Text", "SF Pro Icons", "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
.table.success {
border: 1px solid #ddd;
color: #4a4a4a;
}
.table.success td {
font-size: 16px;
overflow: hidden;
word-break: break-all;
}
td.info {
background-color: #dfb051;
height: 44px;
color: #fff;
}
td:first-child {
padding-left: 49px;
padding-right: 49px;
}
td.build-status {
background-color: #76ac35;
height: 44px;
color: #fff;
}
.table.success td pre {
white-space: pre-wrap;
}
.success-ico {
vertical-align: middle;
margin-left: 10px;
width: 25px;
/* height: 30px; */
/* margin-bottom: 20px; */
}
td.info-title {
color: #76ac35;
height: 36px;
font-weight: bold;
font-size: 16px;
}
.table.success ul {
list-style: none;
margin: 0;
padding: 0;
}
.table.success ul li {
list-style: none;
}
ul li {
height: 35px;
line-height: 35px;
font-size: 14px;
white-space: nowrap;
}
hr {
margin-left: 0;
}
</style>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="info">(大佬们好呀,本邮件是jenkins自动下发的,请勿回复!)</td>
</tr>
</table>
<br />
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="build-status">
构建成功啦
<img
class="success-ico"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAFp3oPPAAAAAXNSR0IArs4c6QAAA/ZJREFUWAm9mLtPVEEUxlkDAaKhQY1iKyWPAmNhYazUAmOMrZRYGQtii9FWwI4e/wA1JibamKixQChITEisqFgNaGI0FDasv2/unNm5r727sDLJt+f1nTNz53VvtqfHt0ajUTe9ZookgdeIN1ICwwiOSaBhDuRHp+O7JwX5wTm8MY7jlXOg9IO5EC1UYLgWglgDwTAFZ7/pJvFtOx1lInKOR/od6cQH9eOGhSxsliSiK4tcMGYIZhUIr0AdhG4DB2cywOBxY2k+CASbQy2Ba/jcjCETIoqbD6R7GrHQQ3fo4emTEgW/kOq9PjNey0Ct0TDWVdqtJzLXAjtWItaC/Njpp8YRdgj6AHBPHRcJuoJKEDE4vYJv0MdaFkh3n61SYFM0naNeYh62a7FPepEfX5KLkhyEbFYHtmrU+KmzPCPWYwf5PX5dNdL6MRKvo9jELnZQyHF97jWXh6GVcpsU2Quqmu1AXQv5FcS5DZrHo2R44ohbEk67IU4AO6Q6qNKrT0y6jFvqeRLXwV8v57OcQhtyH9gBN4oI8vt4X1FcSzcFVgqDGad44qfcOEbAg5SzwhBfeYGGsWkG+gnwzexYyq+4+dCTPJRH5pSUDdRGM/7RxJ3nK2kvJvtCT7O+Mr/L5+d+UUK7PuXrLK21m1DCW1MRe8MuUbWTtuSL1lTE1vtFSU9lbuNP6Y7Z4644LqaGUZaR9Uf3y55G4l4BnnQ3Sy6xY16SzwDizbasEbVoy1YYTsjTY6S2PfYw0EGMm+zhqEB62ysA4XAHMKp+uKvACplkZG1fSm6jWWKZpOAQMX2BzIDT4C14D3Tf6sT/ACfBWXAOXAZXwQ54BhbZEr+RB2sMQIv2GWyCwhuz3crK93VUr3mXVRWArAX/Azq6NKvqWlx1fX07uRZqSgjaLBr5StP7/zT1A1ZB+j2EYwjonEwXdY9fHwLW9lHCeSrh6xyKZy3/MUAiQXsxak8mDeeWAmZnpU9CuLbLb3+WE9uKA/GsVdXeUr4uTL3/J9ntt+KCR6kzhuf0t6Gp0ofJ7aPsPNuX+tc49JE3Br7EBAI3sSdjX5f1DVbiZVRT/Y9pME5Bfo2C59EfRna31ey14SakcM8wM3pvfgenuj0K6u2CM8zMvtWmv2TPyIGxBVI7HlvH8xfoZlO91LWArePtTpMbHIbdM6kB+YFWfQSQ3lYLHwuu02QS8veM77TlDUx3s6DTmRJ/1jqPJf7cDZx7a0O6QNI78Jh1fRIXMB2O9tQlcBHoxaep/wn0D88q+BTvCezQyNXm1d12Bc56CLRSSIrf2tOtuFUxak0Dvf1bvrVzM1NUmCJD+OfADMh+z2g2NCuaHc3Sgb9n/gFyaAu+lH9E5AAAAABJRU5ErkJggg=="
/>
</td>
</tr>
<tr>
<td class="info-title">
构建信息
<!-- <hr size="2" width="100%" align="center" /> -->
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${JOB_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因 : ${CAUSE}</li>
<li>构建日志 : <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>工作目录 : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
<hr size="2" width="100%" />
</td>
</tr>
<tr>
<td class="info-title">历史变更记录</td>
</tr>
<tr>
<td>
<ul>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新作者:%c",showPaths=true,changesFormat="[%a]",pathFormat=" %p"}
</li>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新信息:%c",showPaths=true,changesFormat="%m",pathFormat=" %p"}
</li>
</ul>
<!-- ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat=" %p"} -->
<!-- <hr size="2" width="100%" /> -->
</td>
</tr>
<!-- <tr>
<td class="info-title">构建日志(最后100行)</td>
</tr>
<tr>
<td><p><pre>${BUILD_LOG, maxLines=100}</pre></p></td>
</tr> -->
</table>
</body>
</html>
我把着两个文件放在/jenkins 目录下,方便后续的更改或者复制调试啊
那模板设置好了,怎么用?接下来的代码告诉你
配置完模板,我们得再代码上设置一下
post {
changed{
echo 'I changed!'
}
failure{
echo 'I failed!'
configFileProvider([configFile(fileId: 'email-tmp-fail', targetLocation: 'email-fail.html', variable: 'content')]) {
script {
template = readFile encoding: 'UTF-8', file: "${content}"
emailext(
subject: "Job [${env.JOB_NAME}] - Status: fail",
body: """${template}""",
recipientProviders: [culprits(),requestor(),developers()],
to: "eric_longming@163.com",
)
}
}
}
success{
echo 'I success'
configFileProvider([configFile(fileId: 'email-tmp-success', targetLocation: 'email-success.html', variable: 'content')]) {
script {
template = readFile encoding: 'UTF-8', file: "${content}"
emailext(
subject: "Job [${env.JOB_NAME}] - Status: Success",
body: """${template}""",
recipientProviders: [requestor(),developers()],
to: "eric_longming@163.com",
)
}
}
}
always{
echo 'I always'
script{
if(fileExists("${resetFlagFile}")){
sh "rm -r ${resetFlagFile}" //回滚标志文件记得删
}
}
}
unstable{
echo "unstable"
}
aborted{
echo "aborted"
}
}
-
configFileProvider:是我们安装插件的方法
- fileId: 是刚刚我们设置的邮箱模板文件的ID
- targetLocation: 是我们的模板文件编译后放的地方,这里是根目录的 emali-fail.html
- variable: 就是模板编译文件
-
readFile 是pipeline 内置的读文件的方法
-
emailext: 是插件 Email Extension的方法
-
subject:String类型,邮件的主题。
-
body:String类型,邮件的内容
-
from:发件人邮箱(默认就是我们配置的)
-
to:String类型,收件人
-
recipientProviders:List类型,收件人列表类型
类型名称 helper方法名 描述 Culprits culprits() 引发构建失败的人。最后一次构建成功和最后一次构建失败之间的变更提交者列表 Developers developers() 此次构建所涉及的变更的所有提交者列表 Requestor requestor() 请求构建的人,一般指手动触发构建的人
-
我来触发一个失败和成功的构建测试看看邮件通知的功能
当然也能配置钉钉和企业微信的通知,还有http自定义的通知等
下一篇
这个算是部署,下一篇我们开始用docker 搭建 npm私有库 + API Mock服务 ,然后用docker-compose统一管理所有的服务
获取完整小手册
关注公众号:『前端小手册』,回复:小手册
就能获整篇的PDF版本资源的下载
markdown资源我慢点出来哈,配合typora的night主题来看大概这样↓:
感谢你们的关注
最后是非常非常的希望能得到你们的关注啦~~
你们小小关注就是我们大大的动力啊,我们会给你们持续地推送原创和好文
这个是我们公众号的二维码