16.使用Kubernetes编排Jenkins Slave节点持续交付项目

在上一节的内容中,简单介绍了一下使用kubernetes插件动态生成slave节点的基本语法和使用示例,本节会根据上节介绍到的一些语法知识进行一个基础的实践,并继续补充一些使用该插件时的一些配置细节。

为了方便读者理解,本小节内容会分多个版本,以由浅入深的方式尽量将之前的pipeline语法融会贯通。

基础版

本次版本说明:

1、 PodTemplate 配置以pipeline script的方式放到pipeline脚本里,没有在Jenkins 系统配置里进行定义。

2、 Jenkins默认的Jenkins slave镜像为jnlp-slave:3.35-5-alpine,这是一个Jnlp方式的agent镜像,该镜像没有拉取/编译代码以及docker命令,需要基于此镜像自定义一个新的镜像。

3、 构建应用镜像时的Dockerfile文件通过挂载nfs的方式引用。

4、 使用最基础的shell命令实现一个简单的持续交付脚本,以方便了解该持续交付的流程。

基于上面条件,用脚本式语法编写的pipeline脚本如下:

podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
  serviceAccount: 'default', containers: [
  containerTemplate(
      name: 'jnlp',
      image: "192.168.176.155/library/jenkins-slave:latest",
      args: '${computer.jnlpmac} ${computer.name}',
      ttyEnabled: true,
      privileged: true,
      alwaysPullImage: false,
    ),
  ],
  volumes: [
        nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: true),
        hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),   
  ],
){
  node('pre-CICD') {
    stage('build') {

        container('jnlp') {
            stage('git-clone') {
                checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
            }

            stage('Build a Maven project') {
                sh 'cd base-nop/fw-base-nop && mvn clean install -DskipTests -Denv=beta'
            }

            stage('build docker image'){
                sh 'cp /tmp/Dockerfile base-nop/fw-base-nop/target/'
                sh '/usr/bin/docker build -t 192.168.176.155/library/fw-base-nop:xxxx --build-arg jar_name="fw-base-nop.jar" base-nop/fw-base-nop/target/'
            }    

            stage('push registry') {
                sh '''
                    docker login -u admin -p da88e43d88722c2c9ca09da644eeb015 192.168.176.155
                    docker push  192.168.176.155/library/fw-base-nop:xxxx
                    docker rmi 192.168.176.155/library/fw-base-nop:xxxx
                '''
            }
        }

    }
 }
}

说明:

PodTemplate(….):配置的Pod模板,配置参数以及相关说明在语法章节有过介绍,不再过多赘述;不过有一点需要注意的是containerTemplate 下的name参数和image参数的设定。

Jenkins的Agent大概分两种: 一是基于SSH的,由master主动连接slave/Agent节点; 二是基于JNLP的,使用的是HTTP协议,由Agent/slave节点主动连接master节点,每个Agent需要配置一个独特的secret。 可以参考官方说明

1、 如果name参数配置的值为jnlp,就表示使用基于Jnlp方式的jnlp-agent自启动连接Jenkins master。这就需要保证image参数指定的镜像里包含启动jnlp-agent的命令(官方提供的镜像启动命令为jenkins-slave(旧版本)或者jenkins-agent(新版本))命令,同时需要指定启动参数${computer.jnlpmac} ${computer.name;如果指定的镜像里没有这些命令,或者没有启动参数,动态pod就会创建失败。

2、 如果name参数配置的值不是jnlp,而image参数指定的镜像为jnlp agent镜像时,动态pod也会生成失败。这是因为当该参数设置的值不是jnlp时,动态生成的pod默认会启动两个容器,使用的镜像一个是连接jenkins master默认的jnlp-agent镜像,也就是jnlp-slave:3.35-5-alpine;另一个就是通过image参数自定义的镜像。这两个jnlp-agent镜像启动时都会去连接jenkins master,而基于jnlp方式使用agent时,每个客户端只能有一个agent和一个secret,所以同时启动多个jnlp-agent就会导致Pod启动失败。

3、 如果name参数配置的值不是jnlp,并且image参数指定的镜像为非jnlp agent镜像时(需要注意,此时启动容器时的参数应该去掉),同上面一样,动态生成的pod包含两个容器,一个名称是jnlp(根据默认的jnlp agent镜像默认在后台启动),一个名称是通过name参数设置的值,同时两个容器都能作为pipeline脚本执行的环境,使用时都是通过container('container_name')引用。

4、 如果Pod内有多个容器,并且只要有一个容器为jnlp容器,其他容器的名称和镜像使用没有限制,也就是说,容器名称可以不是jnlp,但是镜像可以jnlp agent镜像。

5、上面示例里的image参数配置的镜像为根据默认的jnlp-agent镜像自定义的镜像,dockerfile如下:

  FROM jenkins/jnlp-slave:3.35-5-alpine

  USER root

  RUN apk add maven git

  COPY settings.xml /usr/share/java/maven-3/conf/settings.xml
  COPY docker/docker /usr/bin/docker

其中settings.xml文件为maven配置文件,这里需要根据实际情况考虑要不要添加。

执行结果如下:

声明式脚本

对于该pipeline脚本相应的声明式语法如下:

pipeline {
  agent {
    kubernetes {
      yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    some-label: some-label-value
  namespace: 'default'
spec:
  containers:
  - name: jnlp
    image: 192.168.176.155/library/jenkins-slave:latest
    args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']
    tty: true
    privileged: true
    alwaysPullImage: false
    volumeMounts:
    - name: mount-nfs
      mountPath: /tmp
    - name: mount-docker
      mountPath: /var/run/docker.sock
  volumes: 
  - name: mount-nfs
    nfs:
      path: /data/nfs
      server: 192.168.177.43
  - name: mount-docker
    hostPath:
      path: /var/run/docker.sock
"""
    }
  }
  stages {
    stage('Run maven') {
      steps {
        container('jnlp') {
          checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])"
        }
      }
    }
    stage('build code') {
        steps {
            container('jnlp') {
              sh 'cd base-nop/fw-base-nop && mvn clean install -DskipTests -Denv=beta'
            }
        }

    }

    stage('build image'){
        steps{
            container('jnlp') {
              sh 'cp /tmp/Dockerfile base-nop/fw-base-nop/target/'
              sh '/usr/bin/docker build -t 192.168.176.155/library/fw-base-nop:xxxx --build-arg jar_name="fw-base-nop.jar" base-nop/fw-base-nop/target/'
            }
        }
    }

    stage('push to registry') {
        steps {
            container('jnlp') {
               sh '''
                  docker login -u admin -p da88e43d88722c2c9ca09da644eeb015 192.168.176.155
                  docker push  192.168.176.155/library/fw-base-nop:xxxx
                  docker rmi 192.168.176.155/library/fw-base-nop:xxxx
                '''
            }
        } 
    }
  }
}

说明:

声明式pipeline语法对于格式有严格要求,所以如果遇到有语法错误的情况需要有耐心排查。

使用kubernetes插件时,使用脚本式流水线语法相对于声明式语法要更加简单,所以建议使用脚本式语法。

Config File Provider Plugin

关于maven配置文件settings.xml的使用还有另一种方式,就是通过jenkins的Config File Provider Plugin插件。这个插件的作用就是在 Jenkins 中存储以properties、xml、json、Groovy结尾的文件以及Maven settings.xml内容。下面演示一下如何使用该插件。

点击”系统管理—>Managed files—>Add a new Config—>Global Maven settings.xml“,将上面setting.xml文件的内容放到”Content”对应的输入框里,如下所示:

编辑好提交即可。

然后使用片段生成器configfileProvider:...根据配置的maven settings.xml文件生成语法片段,比如:

配置好后就可以编辑pipeline脚本了,为了测试该插件的作用,我直接使用docker hub官方的maven镜像,脚本如下:

podTemplate(cloud: 'kubernetes', label: 'pre-CICD', containers: [
  containerTemplate(
      name: 'maven',
      image: "maven:latest",
      command: 'cat',
      ttyEnabled: true,
      privileged: true,
      alwaysPullImage: false)
  ],
  volumes: [
        nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
        hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  ],
){
  node('pre-CICD') {

    stage('build') {
        container('maven') {
            stage('clone code') {
                sh "git clone http://root:[email protected]/root/base-nop.git"
            }
            configFileProvider([configFile(fileId: 'f67fdaf1-4b17-4caa-86ad-e841f387ac7a', targetLocation: 'settings.xml')]) {
                stage('Build project') {
                    sh 'cd base-nop/fw-base-nop && mvn clean install -DskipTests -Denv=beta --settings ${WORKSPACE}/settings.xml'
                }
            }

        }
    }

 }

}

说明:

通过mvn命令编译代码时加上--settings参数,用于指定该文件。上面示例可以看到在指定settings.xml文件时添加了该job的workspace路径,这是因为在使用configFileProvider插件时,该插件默认会将指定的settings.xml文件拷贝到job的workspace目录下,如果不添加路径,有可能会报该文件找不到的错误。

基础版的持续交付脚本就算是完成了。此版本的脚本只是为了让大家熟悉一下代码编译和镜像构建的一个基本流程,下面的版本将会该该版本进行一个简单的优化。继续往下看。

进阶版

说明(相对上一个版本):

1、 本次版本开始引入变量,以提高灵活性。需要注意的是,引用变量的时候要使用双引号(“”)。- 2、 将脚本内部分命令通过kubernetes插件和Docker插件内的相关方法实现。- 3、 挂载共享目录,用于挂载maven的.m2仓库,提高代码编译效率。

首先,用代码片段生成器生成部分命令的相关语法片段:

checkout:Check out from version control:生成拉取代码的片段,前面有介绍过,不在过多说明。

使用docker pipeline插件的withDockerRegistry片段生成器生成对docker Registry认证的语法片段,如下所示:

pipeline脚本如下

def project_name = 'fw-base-nop'
def registry_url = '192.168.176.155'

podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
 , containers: [
  containerTemplate(
      name: 'jnlp',
      image: "192.168.176.155/library/jenkins-slave:latest",
      args: '${computer.jnlpmac} ${computer.name}',
      ttyEnabled: true,
      privileged: true,
      alwaysPullImage: false,
    ),
  ],
  volumes: [
       nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
       nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
       hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),   
  ],
    nodeSelector: 'kubernetes.io/hostname=192.168.176.160',
){
  node('pre-CICD') {
        stage('build') {
            container('jnlp') {
                stage('clone code') {
                    checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])

                    script {
                        imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
                    }
                    echo "${imageTag}"
                }

                stage('Build a Maven project') {
                    sh "cd ${project_name} && mvn clean install -DskipTests -Denv=beta"
                }

                withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
                    stage('build and push docker image') {
                        sh "cp /tmp/Dockerfile ${project_name}/target/"
                        def customImage = docker.build("${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
                        echo "推送镜像"
                        customImage.push()
                    }
                    stage('delete image') {
                        echo "删除本地镜像"
                        sh "docker rmi -f ${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}"
                    }   
                }
            }
        }
  }
}

说明

volumes配置中挂载maven的.m2仓库到nfs server上,以提高代码编译速度。

clone code阶段获取应用代码最后提交时的commit short id作为镜像构建时的tag。

使用docker pipeline语法进行镜像的构建和推送到仓库操作。

nodeSelector 用来选择一台固定的节点专门用来进行构建工作,以防止在拉取基础镜像时生成pod过慢影响效率,这里我使用了node节点默认自带的标签,如果不想用这个标签也可以自定义一个标签。

该版本同样使用pipeline script的方式将脚本代码直接放到了job中。实际工作中,我们也可以将上面的代码放到Jenkinsfile文件中,并将该文件放到应用代码仓库的根目录(也可以单独放置),在配置pipeline job时使用pipeline script from SCM的方式,配置该Jenkinsfile的仓库地址,拉取后会自动进行构建。比如:

执行后会自动拉取该文件并执行。需要说明的是,如果该文件放到了应用代码仓库的根目录,拉取该文件时并不会拉取代码,所以在Jenkinsfile中拉取代码的操作是不能去掉的。

对应的声明式脚本,只需要在版本一的基础上,替换做了更改的部分,并将用到的docker pipeline插件的方法加上script{}块即可。

在Cloud中定义PodTemplate

版本说明:

前两个版本的PodTemplate配置均通过在pipeline中使用代码的方式进行配置的,本次版本将PodTemplate配置通过Jenkins UI放到了全局配置里。

编辑最开始“配置Jenkins连接kubernetes”时创建的cloud,增加PodTemplate和container的配置。如下所示:

-

上面配置的参数说明应该不用再重复介绍了,有不懂的参数可以参考之前的文章即可。配置保存后修改pipeline,只需要将PodTemplate方法配置的部分去掉即可。

pipeline如下

def project_name = 'fw-base-nop'  //项目名称
def registry_url = '192.168.176.155' //镜像仓库地址 

//node 这里依旧是填写podtemplate设置的标签的名称
node('pre-CICD') {
        stage('build') {

            container('jnlp') {
                stage('git-clone') {
                    checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])

                    script {
                        imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
                    }
                    echo "${imageTag}"
                }

                stage('Build a Maven project') {
                    sh "cd ${project_name} && mvn clean install -DskipTests -Denv=beta"
                }

                withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
                    stage('build and push docker image') {
                        sh "cp /tmp/Dockerfile ${project_name}/target/"
                        def customImage = docker.build("${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
                        echo "推送镜像"
                        customImage.push()
                    }
                    stage('delete image') {
                        echo "删除本地镜像"
                        sh "docker rmi -f ${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}"
                    }
                }
            }
        }
}  

同版本2一样,在配置pipeline类型的job时该脚本既可以通过Pipeline script的方式使用,也可以通过Pipeline script from SCM的方式使用。

同时,在使用其他类型(比如 自由风格类型、maven类型)的job时也能够使用该PodTemplate方法,用于动态生成slave。比如创建一个自由风格类型的job,配置job时勾选 Restrict where this project can be run ,在显示的Label Expression 输入框里输入我们上边配置PodTemplate时设定的标签(Labels) 名称 pre-CICD即可。

构建job时就会自动启动这个代理,如下所示:

需要注意的是,如果使用这种方式的话,pipeline中的代码就不在适合此job,需要通过编写shell脚本或者其他方式(比如ansible)实现持续交付。

sonarqube

既然是持续交付,那么肯定少不了代码分析。上面一系列版本只是将代码进行了编译和构建,正常流程还需要进行代码质量分析,这就用到了之前搭建的sonarqube平台。关于sonarqube在前面的文章有详细介绍,这里不重复说明,接下来主要看一下如何在pod中使用sonar-scanner命令。

使用sonar-scanner命令,既可以使用在Jenkins UI中配置的sonar-scanner命令工具,也可以通过自定义sonar-scanner容器镜像的方式引用此命令。下面对这两种方法分别进行介绍。

首先看一下使用jenkins系统内配置的sonar-scanner工具(PodTemplate配置同上)实现代码质量分析。

def project_name = 'fw-base-nop'  //项目名称
def registry_url = '192.168.176.155' //镜像仓库地址

node('pre-CICD') {
    stage('build') {      
        stage('git-clone') {
            container('jnlp'){
                stage('clone code'){
                    checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
                    script {
                        imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
                    }
                    echo "${imageTag}"
                }
                stage('Build a Maven project') {
                    echo "${project_name}"
                    sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
                }
            }
        }

        stage('sonar'){
            def sonarqubeScannerHome = tool name: 'sonar-scanner-4.2.0'
            withSonarQubeEnv(credentialsId: 'sonarqube') {
                sh "${sonarqubeScannerHome}/bin/sonar-scanner -X "+
                "-Dsonar.login=admin " +
                "-Dsonar.language=java " + 
                "-Dsonar.projectKey=${JOB_NAME} " + 
                "-Dsonar.projectName=${JOB_NAME} " + 
                "-Dsonar.projectVersion=${BUILD_NUMBER} " + 
                "-Dsonar.sources=${WORKSPACE}/fw-base-nop " + 
                "-Dsonar.sourceEncoding=UTF-8 " + 
                "-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " + 
                "-Dsonar.password=admin " 
            }
        }
        withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
            stage('build and push docker image') {
                sh "cp /tmp/Dockerfile ${project_name}/target/"
                def customImage = docker.build("${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
                echo "推送镜像"
                customImage.push()
            }
            stage('delete image') {
                echo "删除本地镜像"
                sh "docker rmi -f ${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}"
            }
        }
    }
}

该示例通过tool指令指定了在jenkins的global tool configuration里配置的sonar-scanner环境变量,并赋给一个变量,通过withSonarQubeEnv片段生成器对sonar认证并进行代码分析操作。有关sonar-scanner命令各参数的说明在”基础工具配置”章节有做介绍,这里不在过多说明。

使用sonar-scanner自定义镜像

除了使用jenkins系统配置sonar-scanner工具,也可以根据sonar-scanner工具自定义一个sonar-scanner镜像,并使用该镜像启动一个容器来进行代码分析工作。

sonar-scanner镜像默认可以从docker hub上获取,但是为了杜绝不必要的错误(测试中曾经遇到过),我选择了自定义。可以使用之前定义好的jnlp-agent镜像作为基础镜像,这样在持续交付步骤使用一个镜像就可以完成所有工作;有些人觉得这样会使镜像臃肿,也可以使用一个包含jdk的小体积镜像作为基础镜像,当然也可以根据自己的实际情况自定义。

无论使用哪种镜像,都需要修改${SONAR-SCANER_HOME}/bin/sonar-scanner文件下的use_embedded_jre参数的值,默认为true,需要改成fasle,如下:

use_embedded_jre=false

为什么要修改此参数呢?是因为use_embedded_jre参数为true时,sonar-scanner命令默认会使用自己提供的jre,路径为$sonar_scanner_home/jre,而不会使用系统环境下的jre,所以需要将此参数改为false,否则会报找不到java环境变量的错误,如下所示

sonar-scanner:exec: line 73: xxx/sonar-scanner-4.2.0-linux/jre/bin/java:  not foun

修改好后编辑dockerfile,比如使用上面构建的jnlp-agent镜像作为基础镜像。

FROM 192.168.176.155/library/jenkins-slave:latest
COPY sonar-scanner-4.2.0/ /opt/sonar-scanner-4.2.0/

ENV SONAR_SCANNER_HOME /opt/sonar-scanner-4.2.0/
ENV SONAR_RUNNER_HOME ${SONAR_SCANNER_HOME}
ENV PATH $PATH:${SONAR_SCANNER_HOME}/bin

如果想使用openjdlk镜像作为基础镜像,可以将基础镜像修改为openjdk:8-jre-alpine3.7,编辑好构建即可。

使用docker命令测试一下。

docker run -it --rm 192.168.176.155/library/jenkins-slave:sonar sonar-scanner

输出如下信息表示镜像构建成功 。

$ docker run -it --rm a9592584d82d sonar-scanner
INFO: Scanner configuration file: /opt/sonar-scanner-4.2.0/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarQube Scanner 4.2.0.1873
INFO: Java 1.8.0_212 IcedTea (64-bit)
INFO: Linux 5.4.13-1.el7.elrepo.x86_64 amd64
INFO: User cache: /root/.sonar/cache
INFO: SonarQube server 6.7.5

......

构建好镜像,对pipeline脚本进行简单修改,为了区分显示,又新添加一个containerTemplate配置用于定义sonar镜像,同时在引用镜像时新增了一个container指令用于引用新镜像。如下示例:

def project_name = 'fw-base-nop'  //项目名称
def registry_url = '192.168.176.155' //镜像仓库地址

podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
  serviceAccount: 'default', containers: [
  containerTemplate(
      name: 'jnlp',
      image: "192.168.176.155/library/jenkins-slave:latest",
      args: '${computer.jnlpmac} ${computer.name}',
      ttyEnabled: true,
      privileged: true,
      alwaysPullImage: false,
    ),
  containerTemplate(
      name: 'sonar',
      image: "192.168.176.155/library/jenkins-slave:sonar",
      ttyEnabled: true,
      privileged: true,
      command: 'cat',
      alwaysPullImage: false,
    ),
  ],
  volumes: [
       nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
       hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
       nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
  ],
){
  node('pre-CICD') {
    stage('build') {
        container('jnlp'){
            stage('clone code'){
                checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
                script {
                    imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
                }
                echo "${imageTag}"
            }
            stage('Build a Maven project') {
                echo "${project_name}"
                sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
            }
       }

        container('sonar'){
            stage('sonar test'){
                withSonarQubeEnv(credentialsId: 'sonarqube') {
                    sh "sonar-scanner -X "+
                    "-Dsonar.login=admin " +
                    "-Dsonar.language=java " + 
                    "-Dsonar.projectKey=${JOB_NAME} " + 
                    "-Dsonar.projectName=${JOB_NAME} " + 
                    "-Dsonar.projectVersion=${BUILD_NUMBER} " + 
                    "-Dsonar.sources=${WORKSPACE}/fw-base-nop " + 
                    "-Dsonar.sourceEncoding=UTF-8 " + 
                    "-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " + 
                    "-Dsonar.password=admin " 
                }
            }
       }
       withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
            stage('build and push docker image') {
                sh "cp /tmp/Dockerfile ${project_name}/target/"
                def customImage = docker.build("${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
                echo "推送镜像"
                customImage.push()
            }
            stage('delete image') {
                echo "删除本地镜像"
                sh "docker rmi -f ${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}"
            }
        }
    }
  }  
}         

有关jenkins与kubernetes集成持续交付的配置就简单的介绍到这里,下一章节内容介绍一下将代码持续部署到kubernetes集群中。