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

posted @ 2022-03-09 16:35  西边人  阅读(437)  评论(0编辑  收藏  举报