Jenkins+Docker+Spring Java项目持续集成(单机版)
1、大致流程
流程说明:
1)开发人员每天把代码提交到 Gitlab 代码仓库
2)Jenkins 从 Gitlab 中拉取项目源码,编译并打成jar包,然后构建成 Docker 镜像,将镜像上传到 Harbor 私有仓库。
3)Jenkins 发送 SSH 远程命令,让生产部署服务器(应用服务器)到 Harbor 私有仓库拉取镜像到本地,然后创建容器。
4)最后,用户可以访问到容器(api或网站)
2、服务器规划
注:上面表格中机器标识是为了我在下面文章种描述方便加上去的,没实际作用。_
3、环境准备
3.1 项目源码
因为我这边描述的是单机版,所以项目源码找了一个由 sping 架构的微服务,前后端不分离的。不是传统意义上的 SpringCloud sping(全家桶)。整个项目前后端只有一个 jar包。
3.2 Docker、Harbor等软件安装
根据上面表格可以看出来,具体需要安装那些软件,按需安装即可,因为之前已经写过这些软件的安装过程,不再重复撰述了。
4、持续集成之创建 Jenkins Pipeline 流水线项目
注意,本文的重点是将本地运行的 java 微服务 jar包,构建成 docker 镜像,然后部署到生产服务器上。这个这套流程是通过 Jenkins Pipeline 的形式去完成,也是下面要讲的。Jenkins 处理这个流程有很多方式,Pipeline(流水线)只是其中一种方式,也是官方推荐的。
4.1 Jenkins项目构建类型-Pipeline流水线项目创建
Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点
的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作。
使用 Pipeline 的优点(来自翻译自官方文档):
Pipeline以代码的形式实现,通常被检入源代码控制,使团队能够编辑,审查和迭代其传送流
程。
4.1.1 安装 Pipeline 插件
Jenkins中依次打开,Manage Jenkins->Manage Plugins->可选插件,搜素。
安装插件后可以创建项目了。
4.1.2 配置构建
- 参数化构建,根据分支拉取代码
4.1.3 创建 Jenkinsfile 文件-拉取代码
项目根目录创建 Jenkinsfile 文件,编写流水线脚本。
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage('拉取代码') {
steps {
checkout(\[$class: 'GitSCM', branches: \[\[name: '*/${branch}'\]\], extensions: \[\], userRemoteConfigs: \[\[credentialsId: '32d07987-6f74-4b74-bb91-a9f5509', url: 'http://192.168.5.111:80/gitee/xx/xx/cabast'\]\]\])
echo '拉取代码成功'
}
}
}
}
说明,这里需要在Jenkins中配置好git仓库的相关凭证,就是Jenkins和git仓库通信。具体配置方法看前面的准备环境中,Jenkins安装链接。
现在构建验证下。
4.1.4 构建镜像
既然代码拉取下来了,就可以构建镜像了,使用 Dockerfile 编译、生成镜像。
这里简单的介绍下 jar包构建成 Docker 镜像的基础知识。
怎么把Java应用打包成Docker镜像?对熟悉Docker的同学这应该是一个很简单的问题,把项目打包成JAR包然后在Dockerfile里用ADD命令把JAR文件放到镜像里,启动命令设置执行这个JAR文件即可。
FROM openjdk:8-jre
ADD target/*.jar /application.jar
ENTRYPOINT \["java", "-jar","/application.jar"\]
比如上面这个 dockerfile,就是把 本地 JAR 从 target 目录里添加到 Docker 镜像中,以及将 jar -jar /application.jar 设置成容器的启动命令这两步操作。
不过除了这种最原始的方法外我们还可以使用 Maven 的一些插件,或者 Docker 的多阶段打包功能来完成把 Java应用打包成 Docker 镜像的动作。
Spotify 公司的 dockerfile-maven-plugin 和 Google 公司出品的 jib-maven-plugin 是两款比较有名的插件,下面简单介绍一下 dockerfile-maven-plugin 的配置和使用。也是本次使用的方法。
1)项目的 pom.xml 文件中加入 dockerfifile-maven-plugin 插件
说明,红框里的很多参数可以自己自定义的,如果不熟悉sping,不用改直接用即可。
其中,JAR_FILE 这个参数是传给 dockerfile 的,所以必须跟 dockerfile 中的一致才行。
这里把红框中的代码贴出来,方便复制。
<!-- dockerfile-maven-plugin -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.7</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR\_FILE>${project.build.finalName}.jar</JAR\_FILE>
</buildArgs>
</configuration>
</plugin>
2)在微服务项目根目录下建立 Dockerfile 文件,注意修改端口
FROM openjdk:8-jdk-alpine
EXPOSE 8082
ARG JAR_FILE
ADD target/${JAR_FILE} /app.jar
ENTRYPOINT \["java", "-jar","/app.jar"\]
# ${JAR_FILE} 这个参数是从pox.xml 文件中传过来的,名称必须一致才行。
3)修改 Jenkinsfile 构建脚本
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage('拉取代码') {
steps {
checkout(\[$class: 'GitSCM', branches: \[\[name: '*/${branch}'\]\], extensions: \[\], userRemoteConfigs: \[\[credentialsId: '32d07987-6f74-4b74-bb91-a9f5509', url: 'http://192.168.5.111:80/gitee/xx/xx/cabast'\]\]\])
echo '拉取代码成功'
}
}
stage('编译打包成镜像') {
steps {
//sh "mvn -f ${project_name} clean package" //暂不用这种,如果多个微服务,多个pom工程可以参数化构建的时候指定对应项目。
sh "mvn -e clean package dockerfile:build"
echo "编译成功"
}
}
}
}
说明,Jenkinsfile 这个文件的脚本内容,随着配置的增加不断的在更新,不要搞错。
还需要说明的是,以上配置的 Jenkinsfile、Dockerfile、pox.xml(项目源码自带),都是在项目根目录编辑或新增的,其中,Jenkinsfile、Dockerfile 是新添加的,pox.xml 项目自带的,只需编辑添加内容即可。最终这三个文件随同项目源码一同 push 到代码托管服务器,上面表格中的机器D中,Jenkins 构建的时候会拉取这些文件。
4.1.5 将镜像上传到 Harbor 镜像仓库
大致意思,Jenkins 需要跟 Harbor 通信并且登录后才能上传镜像,所以必须先配置好通信这一步,操作跟Jenkins通信git仓库一样。在Jenkins先生成harbor凭据(就是Jenkins和harbor绑定)。
1)Jenkins 中生成 harbor 账号凭据
2)生成凭证脚本代码
3)继续更新 Jenkinsfile 文件
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
environment{
jar\_file\_name="dwq"
tag="1.0.0"
def imageName ="${jar\_file\_name}:${tag}"
DT="\`date +%m%d%H%M\`"
newimageName="cn.keyi/dwq"
def newtag ="v${DT}"
harbor_url="192.168.108.5:85"
harbor\_project\_name="project_dwq"
port=8082
}
stages {
stage('拉取代码') {
steps {
checkout(\[$class: 'GitSCM', branches: \[\[name: '*/${branch}'\]\], extensions: \[\], userRemoteConfigs: \[\[credentialsId: '32d07987-6f74-4b74-bb91-a9f5509', url: 'http://192.168.5.111:80/gitee/xx/xx/cabast'\]\]\])
echo '拉取代码成功'
}
}
stage('编译打包成镜像') {
steps {
//sh "mvn -f ${project_name} clean package" //暂不用这种,如果多个微服务,多个pom工程可以参数化构建的时候指定对应项目。
sh "mvn -e clean package dockerfile:build"
echo "编译成功"
}
}
stage('镜像打标签并上传') {
steps {
//对镜像打上标签
sh "docker tag ${imageName} ${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}"
//上传镜像的前提需要登录对应镜像仓库,登录仓库需要先绑定账号和凭据
withCredentials(\[usernamePassword(credentialsId: '42521--69904', passwordVariable: 'Din1', usernameVariable: 'dwqn')\]) {
//登录镜像仓库
sh "docker login ${harbor_url} -u ${dqin} -p ${Dadn1}"
echo "harbor 登录成功"
//推送镜像
sh "docker push ${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}"
echo "上传完成"
}
}
stage('上传完成后删除本地镜像') {
steps {
sh "docker rmi -f ${newimageName}"
sh "docker rmi -f ${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}"
echo "删除成功"
}
}
}
}
说明,上传(推送)镜像前先需要对镜像打标签(第42行),上传镜像先需要Jenkins登录的harbor(第47行),登录前先需要绑定凭据(第45行)。绑定凭据这行代码可以在流水线语法生成器中生成,上面截图中所示。
注意,Jenkins 登录到 harbor 这里(机器A登录到机器C),还需要配置一步,机器C的 harbor 访问地址包括端口添加到机器A的 docker 信任列表里。在机器A操作如下。
vim /etc/docker/daemon.json
# 添加如下内容,如果这个文件有多行内容,每行尾部需要逗号隔开,最后一行不需要,json语法。
"insecure-registries": \["192.168.66.102:85"\]
还需要注意,上面流水线脚本中打标签和推送的docker命令,我不使用变量方式粘贴下。
# 给镜像打标签
docker tag dwq:1.0.0 192.168.108.5:85/dwq_pojname/newdwq:v1
# dwq:1.0.0 这个是编译完后打包出来的默认镜像名,这里不能改成别的,如果改的话可能需要在pox.xml中提前定义属性的方式改我没搞定,java不熟悉。
# dwq_pojname 这个是harbor配置完后,创建的空间名称,所以需要提前配置和harbor(参考前面环境准备那一节)。
# newdwq:v1 这个是打完标签后显示的镜像名称,可以自定义。
# 推送镜像(上传)
docker push 192.168.108.5:85/dwq_pojname/cn.key/newdwq:v1
4)构建验证推送效果
4.1.6 生产服务器拉取镜像并发布
大致流程,Jenkins SSH 插件远程调用拉取 harbor 仓库中的镜像,并将其部署在生产服务器(机器B)。
1)安装 Publish Over SSH 插件
2)Jenkins 上配置远程生产部署服务器
系管理-系统配置->下拉找到远程服务器配置
说明,截图中标1的地方填写Jenkins服务器(机器A)上生成的私钥,标2、3的地方填写生产部署服务器(机器B)的ip和登录用户名。其它地方自定义。
注意,因为Jenkins服务器需要在远程服务器上执行脚本,所以Jenkins需要免密登录到生产部署服务器,即(机器A免密登录到机器B)。需要提前配置好免密登录,具体配置方法参照之前写过的文章,两台Linux服务器之间免密登录,点击查看 。
其实,就是将机器A的公钥拷贝到机器B,如下快速操作。机器A上执行,前提A上已经生成了密钥对。
ssh-copy-id 192.168.108.4
3)生成远程调用模板代码
在流水线语法生成器中生成调用SSH插件模板代码
说明,第一个红框是插件ssh名字,第二个红框是远程生产部署服务器的名字,上面设置好的这里下拉选择,其它的均不填。
# 生成的代码片段如下
sshPublisher(publishers: \[sshPublisherDesc(configName: 'master_server', transfers: \[sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '\[, \]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')\], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)\])
说明,这行调用代码,就是Jenkins远程登录到生产部署服务器上(机器B),并执行远程服务器上的部署脚本。所以,这行代码上需要添加执行的 shll 脚本和相关参数。添加shll脚本后更新如下
sshPublisher(publishers: \[sshPublisherDesc(configName: 'uatserver\_108.4\_docker', transfers: \[sshTransfer(cleanRemote: false, excludes: '', execCommand: "/data/api/dokapp/deploy.sh ${harbor\_url} ${harbor\_project_name} ${newimageName} ${newtag} ${port}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '\[, \]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')\], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)\])
# 注意更新了这里 execCommand: "/data/api/dokapp/deploy.sh ${harbor\_url} ${harbor\_project_name} ${newimageName} ${newtag} ${port}"
# deploy.sh 这个是机器B上的部署脚本,前面是路径,后面跟5个参数,Jenkinsfile中都定义过,这个5个参数用在部署脚本里接受。
机器B上的部署脚本如下。
#! /bin/sh
# 接收外部参数,在jenkinsfile和设置定义的参数对应
harbor_url=$1
harbor\_project\_name=$2
newimageName=$3
newtag=$4
port=$5
imageName=${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}
echo "${imageName}"
#查询容器是否存在,存在则删除
containerId=\`docker ps -a | grep -w ${project_name}:${tag} | awk '{print $1}'\`
if \[ "${containerId}" != "" \] ; then
#停掉容器
docker stop ${containerId}
#删除容器
docker rm ${containerId}
echo "成功删除容器"
fi
#查询镜像是否存在,存在则删除
imageId=\`docker images | grep -w ${project_name} | awk '{print $3}'\`
if \[ "${imageId}" != "" \] ; then
#删除镜像
docker rmi -f ${imageId}
echo "成功删除镜像"
fi
# 登录Harbor
docker login ${harbor_url} -u dwqadmin -p Dwqadmin1
# 下载镜像
docker pull ${imageName}
# 启动容器
docker run -dit --name=dwq -p ${port}:${port} ${imageName}
#脱掉变量的启动容器命令,为了方便调试
docker run -di --name=dwq -p 8082:8082 192.168.108.5:85/project_dwq/cn.keyi/dwq:v03040955
echo "容器启动成功"
说明,上面的部署脚本我是放在远程生产部署服务器上的(机器B),也可以放在项目源码根目录,跟jenkfile文件一样,或者 Jenkins 服务器上(机器A),Jenkins SSH远程链接后,先复制部署脚本到机器B,再执行也可以。只需要在Jenkinsfile中应用部署那部分的shell脚本改下即可,可以参考下面的其它方案2。
3)再次更新 jenkfile 文件
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
environment{
jar\_file\_name="dwq"
tag="1.0.0"
def imageName ="${jar\_file\_name}:${tag}"
DT="\`date +%m%d%H%M\`"
newimageName="cn.keyi/dwq"
def newtag ="v${DT}"
harbor_url="192.168.108.5:85"
harbor\_project\_name="project_dwq"
port=8082
}
stages {
stage('拉取代码') {
steps {
checkout(\[$class: 'GitSCM', branches: \[\[name: '*/${branch}'\]\], extensions: \[\], userRemoteConfigs: \[\[credentialsId: '32d07987cec09', url: 'http://192.168.108.6:8080/gitee/cabt'\]\]\])
echo '拉取代码成功'
}
}
/*stage('编译,安装公共子工程') {
// steps {
// sh "mvn -f common clean install"
// }
}*/
stage('编译打包成镜像') {
steps {
//sh "mvn -f ${project_name} clean package" //暂不用这种,如果多个微服务,多个pom工程可以参数化构建的时候指定对应项目。
sh "mvn -e clean package dockerfile:build"
echo "编译成功"
}
}
stage('镜像打标签并上传') {
steps {
//对镜像打上标签
sh "docker tag ${imageName} ${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}"
//上传镜像的前提需要登录对应镜像仓库,登录仓库需要先绑定账号和凭据
withCredentials(\[usernamePassword(credentialsId: '425e4-69978f422d04', passwordVariable: 'Dadmin1', usernameVariable: 'admin')\]) {
//登录镜像仓库
sh "docker login ${harbor_url} -u ${admin} -p ${Ddmin1}"
echo "harbor 登录成功"
//推送镜像
sh "docker push ${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}"
echo "上传完成"
}
}
}
stage('上传完成后删除本地镜像') {
steps {
sh "docker rmi -f ${newimageName}"
sh "docker rmi -f ${harbor\_url}/${harbor\_project_name}/${newimageName}:${newtag}"
echo "删除成功"
}
}
stage('应用部署') {
steps {
sshPublisher(publishers: \[sshPublisherDesc(configName: 'uatserver\_108.4\_docker', transfers: \[sshTransfer(cleanRemote: false, excludes: '', execCommand: "/data/api/dokapp/deploy.sh ${harbor\_url} ${harbor\_project_name} ${newimageName} ${newtag} ${port}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '\[, \]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')\], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)\])
echo "启动成功"
}
}
}
}
4.1.7 部署脚本其它方案
其它方案1,应用部署这块,案例中用 Jenkins 的 Publish Over SSH 远程链接机器B,也可以不用这个插件,直接机器A用shell脚本登录到机器B,然后在机器B执行部署脚本,可以如下方式。
stage('应用部署') {
steps {
sh
'''
sshpass -p '机器B root密码' ssh -p 22 -o StrictHostKeyChecking=no root@192.168.108.4 << reallsh
sh /data/api/dokapp/deploy.sh
exit
'''
echo "启动成功"
说明,如果用这种方式,还需要修改部署脚本的那5个外部参数改成固定参数即可。
其它方案2,应用部署这块,将部署脚本放到其它服务器,机器A用shell脚本登录到机器B,然后把部署脚本传送过去再执行脚本,可以如下方式。
stage('应用部署') {
steps {
sh
'''
//传送部署脚本到机器B
scp /data/jenkins/jenkins_home/workspace/03-dwq-api-Pipeline-vm/deploy.sh root@192.168.108.4:/data/api/dokapp
//远程执行脚本
ssh -p 22 root@192.168.108.4 "chmod +x /data/api/dokapp/deploy.sh & sh /data/api/dokapp/deploy.sh"
'''
echo "启动成功"
说明,此方案中部署脚本放在本地项目根目录,代码提交后部署脚本推送到代码托管服务器(机器D),Jenkins构建后,部署脚本被拉取到Jenkins(机器A)的工作空间目录下。
5、总结
Jenkins 流水线构建 jar 微服务,打包成镜像并发布到生产服务器,大致流程很简单。
1、拉取代码
2、编译,打包成 images 镜像
3、给镜像打标签
4、push镜像到harbor
5、拉取镜像到生产部署服务器
6、部署
当然,还有很多问题,比如项目回滚,多个微服务参数化发布等,还有集群下的发布。后期再弄弄k8s的发布。
● QQ群:330374464
● 公众号:软件测试资源站(ID:testpu)
● CSDN:https://blog.csdn.net/mcfnhm
● 语雀:https://www.yuque.com/testpu/pro