Jenkins Pipeline

要实现在 Jenkins 中的构建工作,可以有多种方式,我们这里采用比较常用的 Pipeline 这种方式。Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作。

Jenkins Pipeline 有几个核心概念:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh ‘make’,就相当于我们平时 shell 终端中执行 make 命令一样。

那么我们如何创建 Jenkins Pipline 呢?

  • Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好
  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
  • 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法

这里我们使用 podTemplate 来定义不同阶段使用的的容器;这里给一个 demo

def dockerreg='10.x.x.x'
podTemplate(nodeSelector: 'nvidia.com/gpu.present=true',cloud: 'kubernetes',label: "vbank-restful", imagePullSecrets: [ 'harbor' ], containers: [
        containerTemplate(name: 'docker', image: "${dockerreg}/common/docker:20.10.18", ttyEnabled: true, alwaysPullImage: true, command: 'cat'),
        containerTemplate(name: 'maven', image: "${dockerreg}/common/maven:3.3.9_8u152", ttyEnabled: true, command: 'cat'),
        containerTemplate(name: 'jnlp', image: "${dockerreg}/devopstools/jenkins/jnlp-slave:4.13.2-1-jdk11", ttyEnabled: true,  args: '${computer.jnlpmac} ${computer.name}'),
        containerTemplate(name: 'sonarcli', image: "${dockerreg}/devopstools/sonarsource/sonar-scanner-cli:4.8.0", ttyEnabled: true, command: 'cat'),
		//python包用于自动化测试时使用
        //containerTemplate(name: 'python', image:"${dockerreg}/tester/python:2.7.13", ttyEnabled: true, alwaysPullImage: false, command: 'cat'),
    ],
    volumes: [
                hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock'),
                persistentVolumeClaim(claimName: 'maven-claim', mountPath: '/root/.m2', readOnly: false),
                persistentVolumeClaim(claimName: 'jarfile-claim', mountPath: '/webapps', readOnly: false),
            ],
    )
    {

        node('vbank-restful') {
            try {
                stage('check out viid code release')
				giturl="git@gitlab.xxx.com:yyy/viid/viid.git"
                if (tag_version != "master") {
                    git credentialsId: 'gitvimicro', url: "${giturl}"
                    env.check_to_tag="$tag_version"
                    sh '[ -n "${check_to_tag}" ] &&  git checkout  ${check_to_tag} ||  { echo -e "切换至指定的tag的版本,tag:${check_to_tag} 不存在或为空,请检查输入的tag!" && exit 111; }'
                    git_branch = "$tag_version"
                    tag_name = "$tag_version"

                } else if (branch != "") {
                    git credentialsId: 'gitvimicro', url: "${giturl}", branch: branch
                    git_branch = "$branch"
                    tag_name = ""
                } else {
                     git credentialsId: 'gitvimicro', url: "${giturl}"

                     tag_name = ""
                     git_branch =  sh (script: "git symbolic-ref --short -q HEAD", returnStdout: true).trim()
                }

                stage('get branch name and version digist')
                git_commit = sh (script: "git rev-parse --short HEAD", returnStdout: true).trim()
                last_author = sh (returnStdout: true, script: "git show -s --pretty=%an").trim()
                nowtime = sh (returnStdout: true, script: 'date +%Y%m%d%H%M%S').trim()

                container('sonarcli') {
                    stage('sonarqube check ')
                    withSonarQubeEnv(credentialsId: 'sonarqube') {
						sh "sonar-scanner -Dsonar.projectName=${JOB_NAME} -Dsonar.projectKey=${JOB_NAME} -Dsonar.java.binaries=. -Dsonar.java.source=1.8"
					}
				    timeout(time: 10) {
				        sleep 10
						waitForQualityGate abortPipeline: true
					}
               }
                container("maven") {
                    stage('mvn build')

                    echo "now build revision: ${git_branch} ${git_commit}"
                    sh "mvn clean install -U --settings /usr/share/maven/ref/settings-docker.xml -Dmaven.test.skip=true -Dmaven_addr=${maven_addr}"
					//image search模块单独打包
				    // sh "cd viid-image-search && mvn clean package -U --settings /usr/share/maven/ref/settings-docker.xml -Dmaven.test.skip=true -Dmaven_addr=${maven_addr}"
                    //sh "mvn clean install -U --settings /usr/share/maven/ref/settings-docker.xml -Dmaven_addr=${maven_addr} -DskipTests -am -pl viid-image-search"
                    //sh "cd viid-image-search && mvn clean install -U --settings /usr/share/maven/ref/settings-docker.xml -Dmaven.test.skip=true -Dmaven_addr=${maven_addr}"
                    // viid-log-analyse模块打包
                    // sh "cd viid-log-analyse && mvn clean package -U --settings /usr/share/maven/ref/settings-docker.xml -Dmaven.test.skip=true -Dmaven_addr=${maven_addr}"
                    //sh "mvn clean install -U --settings /usr/share/maven/ref/settings-docker.xml -Dmaven_addr=${maven_addr} -DskipTests -am -pl viid-log-analyse"


                    stage('backup build targets')
                    if (tag_name ==~ /release-(.*)/) {
                        version = nowtime + '-' + tag_name
                    } else {
                        version = nowtime + "-" + git_commit
                    }
                    /*
					//viid-notify
					sh 'if [ ! -d /webapps/viid/viid-notify ]; then mkdir -p /webapps/viid/viid-notify; fi'
                    sh 'cp viid-notify/target/*.jar /webapps/viid/viid-notify/viid-notify-' + version + '.jar'
                    sh 'cp viid-notify/src/main/resources/application.properties /webapps/viid/viid-notify/application-' + version + '.properties'

					//redis-db
					sh 'if [ ! -d /webapps/viid/redis-db ]; then mkdir -p /webapps/viid/redis-db; fi'
					sh 'cp viid-data-redis-sync-to-db/target/*.jar /webapps/viid/redis-db/redis-db-' + version + '.jar'
					sh 'cp viid-data-redis-sync-to-db/src/main/resources/application.properties /webapps/viid/redis-db/application-' + version + '.properties'


                    //viid-sample
                    sh """
					if ls viid-sampling/target/viid-sampling-*.jar > /dev/null 2>&1;
					then
						if [ ! -d /webapps/viid/viid-sampling ]; then mkdir -p /webapps/viid/viid-sampling; fi
					    cp viid-sampling/target/viid-sampling-*.jar /webapps/viid/viid-sampling/viid-sampling-${version}.jar \
					    && cp viid-sampling/src/main/resources/application.properties /webapps/viid/viid-sampling/application-${version}.properties;
					fi
					"""

					//viid-searchimage
                    sh """
					if ls viid-image-search/target/viid-image-search-*.jar > /dev/null 2>&1;
					then
						if [ ! -d /webapps/viid/viid-image-search ]; then mkdir -p /webapps/viid/viid-image-search; fi
					    cp viid-image-search/target/viid-image-search-*.jar /webapps/viid/viid-image-search/viid-image-search-${version}.jar \
					    && cp viid-image-search/src/main/resources/application.yml /webapps/viid/viid-image-search/application-${version}.yml;
					fi
					"""
					*/
                }



                container("docker") {
					stage 'docker build'
					if (tag_name ==~ /release-(.*)/) {
						tag_name = (tag_name =~ /release-(.*)/)[0][1]
                        //platform = "linux/amd64,linux/arm64"
                    } else {
                        tag_name = git_commit
                        //platform = "linux/amd64"
                    }



                    sh """
                    export registry=10.200.82.51:80
                    export tag_name=${tag_name}
                    export os_version=${os_version}
                    wget http://10.200.82.35/packages/kubeconfig/config -O /config
                    export KUBECONFIG=/config
                    KUBECONFIG=/config docker buildx create --bootstrap --name builder --driver=kubernetes '--driver-opt="image=moby/buildkit:buildx-stable-1","namespace=jenkins"' --use
                    docker login -u jenkins -p Jenkins123 \${registry}
                    docker buildx bake -f docker-compose.yml --set *.platform=${platform}  --set *.args.REGISTRY=${registry} --set *.args.GITHASH=${git_commit} --set *.args.VERSION=${version} --set *.args.OS_VERSION=${os_version} --push
                    """



				}

            } catch(e) {
                currentBuild.result = "FAILED"
                throw e
            } finally {
                // def svnrevision = sh (script: "svn info --show-item last-changed-revision", returnStdout: true).trim()
                stage("sendemail")
                notifyBuild(currentBuild.result, git_branch, version)
            }
        }

    }

def notifyBuild(String buildStatus = 'STARTED', String gitbranch = 'unknown', String svnrevision='unkonwn') {
    buildStatus = buildStatus ?: 'SUCCESS'
	gitbranch = gitbranch ?: 'unkonwn'
    svnrevision = svnrevision ?: 'unknown'
    // def colorName = 'RED'
    // def colorCode = '#FF0000'
    if (tag_name ==~ /release-(.*)/) {
        subject ="${JOB_NAME} - git # ${tag_name} - ${buildStatus}"
    } else {
        subject = "${JOB_NAME} - git # ${gitbranch} ${svnrevision} ${last_author}- ${buildStatus}"
    }
    def CAUSE= getLastBuildCause()

    if (env.gitlabMergeRequestId) {
    details = '''
         (本邮件由程序自动发送,请勿回复!)<br/>
            这是vbank_restful部署程序发布的版本及构建信息<br/><hr/>
            项目名称:${JOB_NAME}<br/><hr/>
            构建编号: ${BUILD_NUMBER}<br/><hr/>
            构建原因: ${CAUSE}<br/><hr/>
            构建分支:${gitbranch}<br/><hr/>
            构建描述:${gitlabMergeRequestTitle}<br/><hr/>
            构建日志地址: <a href="${BUILD_URL}console">${BUILD_URL}console</a><br/>
            构建地址: <a href="$BUILD_URL">${BUILD_URL}</a><br/><hr/>
        '''
    } else {
         details = '''
         (本邮件由程序自动发送,请勿回复!)<br/>
            这是vbank_restful部署程序发布的版本及构建信息<br/><hr/>
            项目名称:${JOB_NAME}<br/><hr/>
            构建编号: ${BUILD_NUMBER}<br/><hr/>
            构建原因: ${CAUSE}<br/><hr/>
            构建日志地址: <a href="${BUILD_URL}console">${BUILD_URL}console</a><br/>
            构建地址: <a href="$BUILD_URL">${BUILD_URL}</a><br/><hr/>
        '''
    }

    if (tag_name ==~ /release-(.*)/) {
        emailext(
          subject: subject,
          mimeType: 'text/html',
          body: details,
          to: emailname
        )
    } else {
        emailext(
          subject: subject,
          mimeType: 'text/html',
          body: details,
          to: emailname,
          //attachLog: true,
          //attachmentsPattern: 'web_page/test_reports/*.html'
          )
    }
}

@NonCPS
def getLastBuildCause() {
    def causes = currentBuild.rawBuild.getCauses()
    return causes.last().getShortDescription()
}

问题 1:

提示不能解析我们的 GitLab 域名,这是因为我们的域名都是自定义的,我们可以通过在 CoreDNS 中添加自定义域名解析来解决这个问题(如果你的域名是外网可以正常解析的就不会出现这个问题了):

$ kubectl edit cm coredns -n kube-system
apiVersion: v1
data:
  Corefile: |
    .:53 {
        log
        errors
        health {
          lameduck 5s
        }
        ready
        hosts {  # 添加自定义域名解析
          192.168.0.100 git.k8s.local
          192.168.0.100 jenkins.k8s.local
          192.168.0.100 harbor.k8s.local
          fallthrough
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           upstream
           fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
......

修改完成后,隔一小会儿,CoreDNS 就会自动热加载,我们就可以在集群内访问我们自定义的域名了。然后肯定没有权限,所以需要配置帐号认证信息。