DevOps / Docker / GitLab / Jenkins / SonarQube / 云计算

Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD

浅时光 · 5月4日 · 2020年 · · · 23302次已读

一、流程介绍

Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
  1. 开发人员将写好的代码提交到代码仓库。SVN或者GitLab
  2. Jenkins去GitLab仓库拉取代码,拉取到对应的Job工作空间,然后执行源码编译、构建,将源码打包成Jar包;然后根据dockerfile进行镜像构建,上传镜像到Harbor仓库
  3. 上传镜像后,Jenkins执行接下来的流程,通过SSH方式调用前后端服务器进行项目的部署

二、环境介绍

服务器名称服务器IP所需软件
GitLab192.168.66.100GitLab
Jenkins192.168.66.101Jenkins、Maven、Docker、npm
Harbor192.168.66.102Docker、docker-compose
Sonarqube192.168.66.103Sonarqube
WEB-SERVER192.168.66.104Docker
WEB-Front192.168.66.105Docker
文章来源(Source):https://www.dqzboy.com

三、安装GitLab

请参考文章【GitLab部署和实战教程】

GitLab部署和实战教程

文章来源(Source):https://www.dqzboy.com
2020-2-8 0

四、安装Jenkins

请参考文章【Jenkins部署和实战教程】

五、安装SonarQube

请参考文文章来源(Source):https://www.dqzboy.com章【SonarQube安装与汉化教程】
文章来源(Source):https://www.dqzboy.com

六、安装Harbor

请参考文章【Harbor安装与配置】
文章来源(Source):https://www.dqzboy.com

Harbor安装与配置

2020-3-24 文章来源(Source):https://www.dqzboy.com 0
文章来源(Source):https://www.dqzboy.com

七、设置流水线

1、所需插件下载

2、创建Dockerfile

  • 在Jenkins Job工作空间下的指定的项目下创建dockerfile文件,一般该文件创建好后不会更改,由于后端项目需要Java环境,这里我们需要先把oracle jdk镜像下载到本地,然后在push到私有harbor仓库中

2.1:下载公共镜像

  • 先登入到docker hub公有仓库,然后进行镜像下载
[[email protected] ~]# docker login -u用户 -p密码    #默认不加地址则是访问docker hub

#下载公共镜像
[[email protected] ~]# docker pull store/oracle/serverjre:8

#打标签
[[email protected] ~]# docker tag store/oracle/serverjre:8 私有仓库地址/images/oracle-jdk8:v1

#上传到私有仓库
[[email protected] ~]# docker push 私有仓库地址/images/oracle-jdk8:v1

2.2:编写Dockerfile

[[email protected] ~]# cd /opt/jenkins/workspace/项目路径/target/
[[email protected] target]# vim dockerfile 
FROM 私有仓库地址/images/oracle-jdk8:v1
ARG JAR_FILE
COPY ${JAR_FILE} /data/app/yp-wms.jar
EXPOSE 9090
ENTRYPOINT ["java","-jar","-Xms1024m","-Xmx1024m","-Xdebug","-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n","-Dspring.profiles.active=dev","-jar","/data/app/xxx.jar"]

3、创建Jenkinsfile

pipeline {
    //确认使用主机/节点机
    agent  { 
        node { label 'master'} 
    }
    //定义全局工具变量
    tools {
        maven 'maven3.6.2'
        jdk   'jdk1.8'
    }

    //定义所需环境变量参数
    environment {
        // 定义项目名称方便全局引用
        project = "test-php"
        // 远程主机地址,如果是同一个环境下有多台服务器,可以空格继续写(注意多个主机IP都在一个双引号中),下面stage中采用轮询方式部署
        dev_serverIP = "192.168.66.30"
        test_serverIP = "192.168.66.31"
        uat_serverIP = "192.168.66.32"
        prod_serverIP = "192.168.66.33"

        //定义ssh远程主机的用户
        server_user = "root"

        //指定部署的环境, 此处引用的DEPLOY在配置Job时进行配置,选择Choice Parameter
        DEPLOY = "$DEPLOY"     //Job web界面配置参数化构建,用来判断本次发布的环境
        JRA_FILE = "jenkins项目工作空间jra包存储路径"
        imageName = "$imageName"	//Job web界面配置参数化构建,可手动指定构建的镜像名称
        TAG = "$TAG"	//Job web界面配置参数化构建,可以手动定义本次构建的镜像tag
        Harbor_repos = "$Harbor_repos"	//Job web界面配置参数化构建,手动指定本次镜像上传到的harbor仓库名称
        harbor_url = "xx.xx.xx.xx"		//harbor仓库的地址
        harbor_auth = "ef499f29-f138-44dd-975e-ff1ca1d8c933"
    }	//jenkins中添加的harbor用户认证凭据中的用户唯一ID

    // 添加gitParameter,会自动去当前项目下的gitlab仓库寻找分支;第一次运行不会在web界面显示,需要手动先运行一次,需要安装插件Git Parameter Plug-In【如果jenkinsfile文件不在项目仓库下,则无法检索出项目分支】
    parameters {
        gitParameter(branch: '',
            branchFilter: 'origin/(.*)',
            defaultValue: 'master', // default value 必填
            description: '',
            name: 'REVISION',   // 变量名,因为我们可能支持不同类型,不仅仅是 branch,revision 名字更适合
            quickFilterEnabled: false,
            selectedValue: 'NONE',
            sortMode: 'NONE',
            tagFilter: '*',
            type: 'PT_BRANCH_TAG')  // 其他类型 PT_TAG 列出 tag,PT_BRANCH 列出分支,PT_REVISION 列出所有 commit,PT_PULL_REQUEST 列出 PR
    }

    stages {
        stage('Checkout') {
            steps {
                checkout([$class: 'GitSCM',
                          branches: [[name: "${params.REVISION}"]], // 传入分支
                          doGenerateSubmoduleConfigurations: false,
                          extensions: [],
                          gitTool: 'Default',
                          submoduleCfg: [],
                          userRemoteConfigs: [[url: '[email protected]:root/solo-b3log.git']]   //这里需要保证jenkins跟当前的git仓库地址是可以正常访问的
                        ])
            }
        }
	 //jenkins集成sonarqube进行静态代码检查
        stage('代码质检') {
            steps {
		script {
		//定义sonar scanne全局变量工具,名称必须与Jenkins全局工具配置中的名称一致
                	def sonarqubeScannerHome = tool name: 'sonar-scanner'
			//注意这里括号内的SonarQube的名称必须与jenkins中配置SonarQube服务时定义的名称保持一致
                	withSonarQubeEnv('SonarQube') {
			//这里也可以直接使用maven命令,上面def则不需要定义
			   sh "mvn -f pom.xml clean compile sonar:sonar -Dsonar.projectKey=${JOB_NAME} -Dsonar.projectName=${JOB_NAME} -Dsonar.host.url={SonarQube URL地址} -Dsonar.login={SonarQube的token}"
                           sh "${sonarqubeScannerHome}/bin/sonar-scanner -Dsonar.projectKey=${JOB_NAME} -Dsonar.sources=. -Dsonar.host.url={SonarQube UR地址} -Dsonar.login={SonarQube的token}"
                      }
		}
            }
        }
        
        stage('质检报告') {
            steps {
 		script {
		//定义代码扫描超时时间,超时后默认状态为失败
                	timeout(time: 5, unit: 'MINUTES') {
                    	def qg = waitForQualityGate()
                    	echo "---before qg:${qg.status}---"
                    	if (qg.status != 'OK') {
                        	error "未通过Sonarqube的代码质量阈检查,请及时修改!failure: ${qg.status}"
                    	}
                   }
                }
            }
        }

        // 编译构建代码
        stage('源码构建') {
            steps{
                // maven构建
                sh "mvn clean package -Dmaven.test.skip=true"
            }
        }

	stage('镜像构建') {
		//添加判断,表示镜像构建和下面上传镜像流程只在开发环境进行,其他环境只需要从harbor仓库下载即可
            when {
                environment name: 'DEPLOY',value: 'dev'
            }
            steps{
                // docker build
                sh '''
                    cd ${JRA_FILE}
                    docker build --build-arg JAR_FILE=yp-wms-war-1.0.0.jar -t ${imageName}:${TAG} .
                '''

            }
        }
        
        stage ('上传镜像') {
            when {
                environment name: 'DEPLOY',value: 'dev'
            }
            steps {
                script {
			     //给镜像打tag标签
                        sh "docker tag ${imageName}:${TAG} ${harbor_url}/${Harbor_repos}/${imageName}:${TAG}"

              //此处为了防止密码泄露,username和password为变量, credentialsId为jenkins中配置的harbor用户唯一ID
              withCredentials([usernamePassword(credentialsId: '9703d5e0-8f1a-463d-b96e-7161c7a14b72', passwordVariable: 'password', usernameVariable: 'username')]) {
                        sh "docker login -u ${username} -p ${password} ${harbor_url}"
                        sh " docker push ${harbor_url}/${Harbor_repos}/${imageName}:${TAG} "
                    }
			//删除本地刚才构建的镜像
                    sh "docker rmi -f ${imageName}:${TAG}"
                    sh "docker rmi -f ${harbor_url}/${Harbor_repos}/${imageName}:${TAG}"
                }
            }
        }
        stage('开发环境,镜像拉取部署') {
	//注意:如果某些环境是与GitLab实现自动触发的话,那么该部署环境下的判断条件when则不需要添加,前面加 # 号注释或者删除即可
            when {
                environment name: 'DEPLOY',value: 'dev'
            }
            steps {
                script {
                    sh '''
                        ssh ${server_user}@${dev_serverIP} "/script/pull_docker_images ${harbor_url} ${Harbor_repos} ${imageName} ${TAG} ${port}"
                    '''
                }
            }
        }
        stage('部署到测试环境') {
            when {
                environment name: 'DEPLOY',value: 'test'
            }
            steps {
                script {
                    //上面environment定义的全局变量在此处调用
	            //下面此参数是为了防止Jenkins执行完Job后自动杀死进程
	            withEnv(['JENKINS_NODE_COOKIE=dontkillme']) {
                    sh '''
                        ssh ${server_user}@${test_serverIP} "/script/pull_docker_images ${harbor_url} ${Harbor_repos} ${imageName} ${TAG} ${port}"
                    '''
                }
            }
        }

        stage('部署到仿真环境') {
            when {
                environment name: 'DEPLOY',value: 'uat'
            }
            steps {
                input message: '是否部署至仿真环境', ok: 'OK'
                script {
                    //上面environment定义的全局变量在此处调用
	            //下面此参数是为了防止Jenkins执行完Job后自动杀死进程
	            withEnv(['JENKINS_NODE_COOKIE=dontkillme']) {
                    sh '''
                        ssh ${server_user}@${uat_serverIP} "/script/pull_docker_images ${harbor_url} ${Harbor_repos} ${imageName} ${TAG} ${port}"
                    '''
                }
            }
        }

        stage('部署到生产环境') {
            when {
                environment name: 'DEPLOY',value: 'prod'
            }
 
            steps {
                input message: '是否部署至生产环境', ok: 'OK'
                script {
                    //上面environment定义的全局变量在此处调用
	            //下面此参数是为了防止Jenkins执行完Job后自动杀死进程
	            withEnv(['JENKINS_NODE_COOKIE=dontkillme']) {
                    sh '''
                        ssh ${server_user}@${prod_serverIP} "/script/pull_docker_images ${harbor_url} ${Harbor_repos} ${imageName} ${TAG} ${port}"
                    '''
                }
            }
        }

        stage('进行访问测试') {
		//单独在每个Job任务中进行配置http的访问地址,然后脚本中调用即可           
            steps {
                sleep time: 1, unit: 'MINUTES' 
                script {                    
                    sh label: '', script: 'curl -I -s ${HTTP_URL} |grep 200|awk \'{print $2}\''
                }
            }
        }
    }
    post {
        //成功通知
        success {
	        script {
                //DEPLOY是上面全局变量中定义的,值是在Job中配置的参数化
                   if ("${DEPLOY}" == "uat" || "${DEPLOY}" == "prod"){
	                  qyWechatNotification mentionedId: '', 
	                  mentionedMobile: '', 
	                  webhookUrl: '生产仿真环境机器人webhook'
	                }
                   else if ("${DEPLOY}" == "test") {
                      qyWechatNotification mentionedId: '', 
                      mentionedMobile: '', 
                      webhookUrl: '测试环境机器人webhook'
                   }
                   else {
	                    qyWechatNotification mentionedId: '', 
                      mentionedMobile: '', 
                      webhookUrl: '开发环境机器人webhook'
                   }
               }
           }
        //失败通知
        failure {
	        script {
                 //DEPLOY是上面全局变量中定义的,值是在Job中配置的参数化
                   if ("${DEPLOY}" == "uat" || "${DEPLOY}" == "prod"){
	                  qyWechatNotification mentionedId: '', 
	                  mentionedMobile: '', 
	                  webhookUrl: '生产仿真环境机器人webhook'
	                 }
                   else if ("${DEPLOY}" == "test") {
                      qyWechatNotification mentionedId: '', 
                      mentionedMobile: '', 
                      webhookUrl: '测试环境机器人webhook'
                   }
                   else {
	                    qyWechatNotification mentionedId: '', 
                      mentionedMobile: '', 
                      webhookUrl: '开发环境机器人webhook'
                 }
            }
        }
    }
}

4、创建部署脚本

#!/bin/bash
#接收外部参数
harbor_url=$1
#镜像仓库名称
Harbor_repos=$2
#镜像名称
imageName=$3
#调用镜像标签
tag=$4
#定义容器启动和映射端口号
port=$5

images=$harbor_url/$Harbor_repos/$imageName:$tag
echo "$images"
#查询容器是否存在,存在则删除
containerId=`docker ps -a | grep -w ${imageName}:${tag} | awk '{print $1}'`
if [ "$containerId" != "" ] ; then
#停掉容器
docker stop $containerId
#删除容器
docker rm $containerId
echo "成功删除容器"
fi
#查询镜像是否存在,存在则删除
imageId=`docker images | grep -w $imageName | awk '{print $3}'`
if [ "$imageId" != "" ] ; then
#删除镜像
docker rmi -f $imageId
echo "成功删除镜像"
fi
# 登录Harbor私服
docker login -u admin -p admin $harbor_url
# 下载镜像
docker pull $images
# 启动容器
# 启动容器
docker run -di -v /logs:/home/appadmin -p $port:$port $images
container_Id=`docker ps -a | grep -w ${imageName}:${tag} | awk '{print $1}'`
status=`docker ps |grep ${container_Id}|awk '{print $9}'`
sleep 1
if [ "$status" == "Up" ];then
    echo "容器启动成功"
else
echo "容器启动失败"
exit 1
fi

注意:

1、如果启动容器用户不为root,而是普通用户,那么需要将该用户加入到docker组

[[email protected] ~]# gpasswd -a appadmin docker

2、防火墙放开对应容器映射到宿主机的端口

3、为了部署脚本中harbor仓库的账号文章来源(Source):https://www.dqzboy.com名称不外泄,可以文章来源(Source):https://www.dqzboy.com将脚本通过shc工具进行加密

5、配置参数构建

  • 根据pipeline脚本中定义的需要通过外部传参的变量在Job任务中配置参数化构建参数
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客

注意:如文章来源(Source):https://www.dqzboy.com果项目仓库的地址和Jenkinsfile文件是在同一个仓库下的话,那么在使用Git Parameter插件时不要在高级里面单独指定Git仓库地址;而我下面这里是单独指定了项目的仓库Git地址,是因为我的项目存储Git地址与Jenkinsfile文件不在同一个项目仓库下。

Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
  • 最后选择jenkinsfile的gitlab仓库地址和名称
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客

6、执行构建任务

Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客

7、查看部署情况

Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
Jenkins+Git+Harbor+Docker实现Sprint Boot项目的CICD-浅时光博客
8 条回应
  1. 旧戏书未知2020-5-6 · 14:59

    老哥 你这个可以直接用于生产吗

    • 浅时光
      浅时光上海2020-5-6 · 15:00

      这个就是我们线上环境使用的

  2. sandy未知2020-10-21 · 16:01

    你好 想请问一下 那个jenkinsfile文件 58行中的 [email protected]:root/solo-b3log.git 这个git地址是哪里来的 是做什么用的

    • 浅时光
      浅时光上海2020-10-21 · 16:07

      这个是项目git地址,改成你自己的,用来检出代码的

      • sandy未知2020-10-21 · 16:14

        不知道我理解的是否正确。我看到后面会有git参数化构建,里面会填写相应的仓库地址。如果把仓库地址在jenkinsfile写死的话,那就用不到git参数化中的地址了,是不是以后每个项目的部署都需要写一个单独的jenkinsfile。最后会将jenkinsfile的仓库地址填写出来,如果写死的话,是不是配置一个项目的部署就需要创建一个相应jenkinsfile的仓库。

        • 浅时光
          浅时光上海2020-10-21 · 16:17

          如果你们的项目已经做到了一定的标准化,那么你就可以将这些参数在jenkinsfike中设置为变量,然后参数化构建中定义变量并赋予值

          • sandy未知2020-10-21 · 16:20

            好的 谢谢解答! 我去试一下。

            • 浅时光
              浅时光上海2020-10-21 · 16:21

              下面参数化构建中填写的git地址是为了动态获取项目仓库下的所有分支信息的

本站已安全运行: | 耗时 0.444 秒 | 查询 137 次 | 内存 20.44 MB