Jenkins流水线自动化部署项目
1. 自动化部署介绍
1.1 行业现状介绍
随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。互联网软件的开发和发布,已经形成了一套标准流程。
如: 在互联网企业中,每时每刻都有需求的变更,bug的修复, 为了将改动及时更新到生产服务器上,下面的图片我们需要每天执行N多次,开发人员完整代码自测后提交到git,然后需要将git中最新的代码生成镜像并部署到测试服务器,如果测试通过了还需要将最新代码部署到生产服务器:
显然如果采用手动方式操作,那将导致大量的时间浪费在运维部署方面,影响工作效率;
现在的互联网企业,基本都会采用以下方案解决:
- 持续集成(Continuous integration,简称 CI)
- 持续部署(continuous deployment, 简称 CD)
1.2 devops理念介绍
1. 持续集成
持续集成 (Continuous integration,简称 CI) 指的是,频繁地(一天多次)将代码集成到主干。
它的好处主要有两个。
-
快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
-
防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
Martin Fowler 说过,”持续集成并不能消除 Bug,而是让它们非常容易发现和改正。”
与持续集成相关的,还有两个概念,分别是持续交付和持续部署。
2. 持续交付
持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。
持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。
3. 持续部署
持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。
持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。
持续部署的前提是能自动化完成测试、构建、部署等步骤。
4 演示流程说明
为了保证团队开发人员提交代码的质量,减轻了软件发布时的压力;
持续集成中的任何一个环节都是自动完成的,无需太多的人工干预,有利于减少重复
过程以节省时间、费用和工作量;接下来我们会演示一套基本的自动化持续集成和持续部署方案,来帮助大家理解互联网企业的软件部署方案。
流程如下:
实现流程:
- 开发人员持续将功能代码集成到公共功能集成分支,如: dev
- 当功能积攒到一定节点,需要进行线上项目升级,需要基于持续集成软件进行一键发布;
- 持续集成工具先对dev|master分支代码进行拉取更新,然后对最新功能代码进行清理、编译、测试、打包、打镜像、部署等动作;
- 如果构建失败,则发送邮件提醒代码提交人员或管理员;
要实现上面流程,我们需要了解一款支持持续集成的软件:jenkins;
2. jenkins环境准备
2.1 jenkins介绍
Jenkins,原名Hudson,2011年改为现在的名字,它 是一个开源的实现持续集成的
软件工具。官方网站:https://www.jenkins.io/zh/
Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和提醒功能,还能用图
表的形式形象地展示项目构建的趋势和稳定性。
特点:
- 易配置:提供友好的GUI配置界面;
- 变更支持:Jenkins能从代码仓库(Subversion/CVS)中获取并产生代码更新列表并
输出到编译输出信息中;
支持永久链接:用户是通过web来访问Jenkins的,而这些web页面的链接地址都是
永久链接地址,因此,你可以在各种文档中直接使用该链接; - 集成E-Mail/RSS/IM:当完成一次集成时,可通过这些工具实时告诉你集成结果(据
我所知,构建一次集成需要花费一定时间,有了这个功能,你就可以在等待结果过程
中,干别的事情); - JUnit/TestNG测试报告:也就是用以图表等形式提供详细的测试报表功能;
- 支持分布式构建:Jenkins可以把集成构建等工作分发到多台计算机中完成;
文件指纹信息:Jenkins会保存哪次集成构建产生了哪些jars文件,哪一次集成构建使
用了哪个版本的jars文件等构建记录; - 支持第三方插件:使得 Jenkins 变得越来越强大
2.2 安装配置jenkins
jenkins安装
1. 基于Docker逐步安装jenkins
1.1 下载安装
下载jenkins镜像
# 拉取镜像
docker pull jenkins/jenkins:2.387.3
创建jenkins容器
# 构建jenkins数据挂在目录
mkdir -p /usr/local/images/jenkins-data
# 启动容器
docker run \
-u root \
-d \
-p 58888:8080 \
--name myjenkins \
--env JAVA_OPTS="-Duser.timezone=GMT+08" \
-v /usr/local/images/jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(which docker):/usr/bin/docker \
jenkins/jenkins:2.387.3
查看jenkins启动日志
docker logs -f myjenkins
启动成功后 访问:
http://192.168.200.129:58888
解锁jenkins
第一次运行时,需要先解锁jenkins
具体步骤:
- 去容器中 指定文件查看管理员密码
- 将密码拷贝到文本框
- 点击继续即可
具体解锁的管理员密码,在jenkins的安装目录中,因为我们是采用的容器安装,所以需要进入到容器中查看,命令如下:
# 进入到jenkins容器
docker exec -it myjenkins bash
# 查看密码
cat /var/jenkins_home/secrets/initialAdminPassword
# 将密码复制到上图管理员密码文本框,然后点击继续 完成解锁
安装推荐插件
jenkins的各项功能,依赖各种插件,可以手工选择安装也可以按照推荐安装
课程中直接安装推荐插件, 注意如果加速没配置成功,这里将会非常慢,或者下载失败
可能因为网络会安装失败,我们课程主要使用git、pipeline 和 chinese中文插件,可以直接继续忽略错误插件,或者点击重试尝试重新安装:
创建管理员用户
插件安装完毕后,会进入到设置管理员用户页面,按自己需求设置就好,后续登录可以使用
设置完毕后点击保存并完成则进入到jenkins欢迎页
可统一账户密码:root|root
接下来,jenkins会让我们确认jenkins服务端的地址,直接下一步就好,
然后点击开始使用jenkins进入到jenkins页面
进入到jenkins页面, 如果这个时候你的页面都是英文的话,重启下就好
(因为上面安装默认插件中已经安装了 中文插件)
重启jenkins可将Manager Jenkins转化成中文样式;
1.2 插件安装
核心插件:git、git Paramter、maven integretion 、maven pipeline 等
(1) 下载maven插件:
点击系统管理 --> 点击插件管理 --> 进入到插件管理页面
点击可选插件 --> 输入maven --> 勾选Maven Integration --> 下载待重启安装
等待下载完成后,重启jenkins容器即可
安装其它扩展插件:
2.基于已有数据包挂载安装
逐步安装jenkins过程较为繁琐,且需要安装各种jenkins插件,而有时这些插件安装非常耗时,在这里已为大家提供了安装好的数据包,省却了插件安装过程:
安装流程如下:
- 将资料包:jenkins安装\资料\jenkins-data.tar.gz上传到/usr/local/images/目录下;
- 解压jenkins-data.tar.gz获取/usr/local/images/jenkins-data
tar -zxvf jenkins-data.tar.gz
- 拉取jenkins镜像,并启动
# 拉取镜像 docker pull jenkins/jenkins:2.387.3 # 启动容器 docker run \ -u root \ -d \ -p 58888:8080 \ --name myjenkins \ --env JAVA_OPTS="-Duser.timezone=GMT+08" \ -v /usr/local/images/jenkins-data:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/usr/bin/docker \ jenkins/jenkins:2.387.3
- 访问登录页面, 账号root | 密码root
2.3 部署环境配置
1.安装maven环境
将资源中的maven安装包,拷贝到容器中解压即可,在配置好阿里云镜像
将maven压缩包拷贝容器解压:
# 1.将maven压缩包上传到/usr/local/images/jenkins-data下,等价于上传到容器内的/var/jenkins_home下
# 2.将maven解压
tar -zxvf /var/jenkins_home/apache-maven-3.6.3-bin.tar.gz
配置maven镜像:
# 在jenkins-data目录下配置阿里云镜像
vi /usr/local/images/jenkins-data/apache-maven-3.6.3/conf/settings.xml
# 阿里云镜像 maven.ali
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>ali_repo</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
阿里云地址有变更,详见:https://developer.aliyun.com/mvn/guide
设置maven环境变量:
# 进入jenins容器内,发现容器内没有安装vi|vim:
docker exec -it myjenkins bash
# 设置maven_home环境变量时,可将/etc/profile文件复制到/ver/jenkins_home下,在容器外部修改,然后再移动回去即可
vi /etc/profile
#点击i进入编辑模式 输入
MAVEN_HOME=/var/jenkins_home/apache-maven-3.6.3
export MAVEN_HOME
export PATH=${
PATH}:${MAVEN_HOME}/bin
#保存退出
:wq
# 进入容器,配置资源生效(这样就不用重启系统了)
source /etc/profile
# 查看是否配置成功
mvn -v
2.jenkins中配置maven环境
系统管理中点击全局工具配置
- 新增maven
- name随意,MAVEN_HOME: /var/jenkins_home/apache-maven-3.6.3
- 取消勾选自动安装
- 保存即可
3.配置jdk环境
配置环境变量:默认javahome路径可进入jenkins中通过whereis java指令获取;
3. jenkins流水线配置
3.1 流水线参数设置
下载配置完毕重启jenkins,然后构建流水线任务:
填写公共参数:
定义服务列表变量-services:
注意事项:
对于项目一,需要将名字改为:stock_backend和stock_job两个服务名字,且以逗号间隔!!!
不要勾选 Quote Value 选款,否则后续解析需要特殊处理!!!
定义第二个参数:stockURL:
此处填写gitlab代码仓库地址:
维护gitlab代码分支列表展示,我们取名为:stockBranche
参数选择完毕,最终效果:
注意:点击Build with Paramters显示效果!!!
补充docker打镜像的tag:
3.2 流水线脚本设置
接下来构建流水脚本:
默认拉去master分支,然后从master下可切换到其它分支
在工程根目录添加Jenkinsfile脚本:
jenkins-pipeline语法参考官网:https://www.jenkins.io/zh/doc/book/pipeline/syntax/
pipeline {
agent any
options {
timestamps()
}
tools {
maven 'mavenstock'
jdk 'java11'
}
// environment {
// BUILD_ID = 'v1.0'
// }
stages {
// stage('清除工作空间') {
// steps {
// cleanWs()
// }
// }
stage('更新Git代码') {
steps {
echo "正在拉取代码..."
echo "当前分支:${stockBranch},当前服务:${services}"
checkout([$class: 'GitSCM',
branches: [[name: stockBranch]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: '890fda75-00af-437a-926d-c96cf198a186', url: stockUrl]]
])
sh "pwd"
}
}
stage('重新Maven打包') {
steps {
script {
echo "打包:stock_common工程....."
// sh "mvn clean install -DskipTests -f ./stock_common/pom.xml"
// echo "需要打包的服务:${services}......"
// for(ps in services.tokenize(",")){
// echo "cd ./${ps} && mvn clean install -DskipTests"
// sh "cd ./${ps} && mvn clean install -DskipTests"
// }
echo "整体打包...."
sh "mvn clean install -DskipTests"
}
}
}
stage('重新构建镜像') {
steps {
echo "当前打镜像tag:${imageTag}"
script {
for (ds in services.tokenize(",")) {
sh "pwd"
echo "cd ./docker/${ds} && docker build -t ${ds}:${imageTag} ."
sh "cd ./docker/${ds} && docker build -t ${ds}:${imageTag} ."
}
}
}
}
stage('部署服务'){
steps {
script {
for (ws in services.tokenize(",")) {
echo "部署升级:${ws}服务"
sh "cd ./docker/ && chmod +x ./deploy.sh && sh deploy.sh ${ws} ${imageTag}"
}
}
}
}
}
post {
always {
echo '任务构建完毕'
}
}
}
3.3 准备要部署的工程
3.3.1 后台工程打包准备
在stock_backend工程添加打包名称与打包存入位置:
<build>
<!--打包名称-->
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--jar包保存位置-->
<outputDirectory>../docker/stock_backend</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
在stock_job做相同操作:
<build>
<!--打包名称-->
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--jar包保存位置-->
<outputDirectory>../docker/stock_job</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
运行打包指令,效果:
3.3.2 定义Dockerfile
定义Dockerfile文件:
FROM java:8
COPY ./app.jar /tmp
ENTRYPOINT java -jar /tmp/app.jar
存放位置如下:
3.3.3 定义容器发布脚本
分别在工程docker目录下定义容器部署的通用脚本:
#!/bin/bash
# 容器名称
container_name=$1
# 镜像名称
image_name=$1
# 镜像tag
image_tag=$2
# 判断容器是否存在
if docker ps -a | grep $container_name | awk '{print $1}'; then
echo "容器 $container_name 存在"
if docker ps | grep $container_name | awk '{print $1}';then
echo "关闭正在运行的容器 $container_name"
docker stop `docker ps | grep $container_name | awk '{print $1}'`
else
echo "容器 $container_name 都已关闭"
fi
# 删除容器
echo "删除容器 $container_name"
docker rm `docker ps -a | grep $container_name | awk '{print $1}'`
else
echo "容器 $container_name 不存在"
fi
# 删除镜像
#if docker images | grep $image_name | awk '{print $3}'; then
# echo "镜像 $image_name 存在"
#
# echo "删除镜像 $image_name"
# docker rmi `docker images | grep $image_name | awk '{print $3}'`
#else
# echo "镜像 $image_name 不存在"
#fi
# 重新构建镜像
#echo "重新构建镜像 $image_name"
#docker build -t $image_name .
# 启动容器
echo "启动容器 $container_name"
if [ $container_name = "stock_backend" ]; then
docker run -d --name $container_name -p 8091:8091 $image_name:$image_tag
elif [ $container_name = "stock_job" ]; then
docker run -d --name $container_name -p 8092:8092 $image_name:$image_tag
fi
4.整体演示
更新代码推送到gitlab远程仓库后,在jenkins控制终端进行发布操作: