1. 程式人生 > >[持續交付實踐] pipelne:pipeline 使用之項目樣例

[持續交付實踐] pipelne:pipeline 使用之項目樣例

perf 腳本類 期待 hot ogr toc 補充 dubbo tor

項目說明

本文將以一個微服務項目的具體pipeline樣例進行腳本編寫說明。一條完整的pipeline交付流水線通常會包括代碼獲取、單元測試、靜態檢查、打包部署、接口層測試、UI層測試、性能專項測試(可能還有安全、APP等專項)、人工驗收等研發測試環節,還會包括灰度發布、正式發布等發布環節。

補充說明:
1.此項目的部署還是使用傳統虛擬機服務器的方式,暫未采用docker容器,docker容器與pipeline的結合後面會有其他專題進行說明。
2.灰度發布、正式發布等發布環節,由於涉及到線上發布系統對接,該項目暫未包括,後續專題展開。
3.采用jenkins官方推薦的declarative pipeline方式實現。

交付流水線(BlueOcean)

Jenkins UI從2006年-2016年,幾乎沒有變化。為了適應Jenkins Pipeline和 Freestyle jobs任務,Jenkins推出了BlueOcean UI,其目的就是讓程序員執行任務時,降低工作流程的復雜度和提升工作流程的清晰度
BlueOcean目前為止還是作為一個插件,需要Jenkins版本2.7.x以上,按照jenkins社區的規劃,未來也許會逐漸取代原有的jenkins界面。
當然目前功能上還不是太成熟,期待未來更加強大吧,不過UI界面上確實非常漂亮。

參數化構建界面

技術分享

交付流水線界面

技術分享

腳本詳解

Pipeline支持兩種語法:Declarative Pipeline(在Pipeline 2.5中引入,結構化方式)和Scripted Pipeline,兩者都支持建立連續輸送Pipeline。
為與BlueOcean腳本編輯器兼容,通常建議使用Declarative Pipeline的方式進行編寫,從jenkins社區的動向來看,很明顯這種語法結構也會是未來的趨勢。
下面的樣例以Declarative Pipeline的方式進行詳細講解,關鍵步驟都增加了註解和說明,部分公司敏感信息做了脫敏處理。

#!groovy
pipeline {
    //在任何可用的代理上執行Pipeline
    agent any
    //參數化變量,目前只支持[booleanParam, choice, credentials, file, text, password, run, string]這幾種參數類型,其他高級參數化類型還需等待社區支持。
    parameters {
    //git代碼路徑【參數值對外隱藏】
    string(name:‘repoUrl‘, defaultValue: ‘[email protected]*****.com:*****/*****.git‘, description: ‘git代碼路徑‘)
    //repoBranch參數後續替換成git parameter不再依賴手工輸入,JENKINS-46451【git parameters目前還不支持pipeline】
    string(name:‘repoBranch‘, defaultValue: ‘master‘, description: ‘git分支名稱‘)
    //pom.xml的相對路徑
    string(name:‘pomPath‘, defaultValue: ‘pom.xml‘, description: ‘pom.xml的相對路徑‘)
    //war包的相對路徑
    string(name:‘warLocation‘, defaultValue: ‘rpc/war/target/*.war‘, description: ‘war包的相對路徑 ‘)
    //服務器參數采用了組合方式,避免多次選擇,使用docker為更佳實踐【參數值對外隱藏】
    choice(name: ‘server‘,choices:‘192.168.1.107,9090,*****,*****\n192.168.1.60,9090,*****,*****‘, description: ‘測試服務器列表選擇(IP,JettyPort,Name,Passwd)‘)
    //測試服務器的dubbo服務端口
    string(name:‘dubboPort‘, defaultValue: ‘31100‘, description: ‘測試服務器的dubbo服務端口‘)
    //單元測試代碼覆蓋率要求,各項目視要求調整參數
    string(name:‘lineCoverage‘, defaultValue: ‘20‘, description: ‘單元測試代碼覆蓋率要求(%),小於此值pipeline將會失敗!‘)
    //若勾選在pipelie完成後會郵件通知測試人員進行驗收
    booleanParam(name: ‘isCommitQA‘,description: ‘是否郵件通知測試人員進行人工驗收‘,defaultValue: false )
    }
    //環境變量,初始確定後一般不需更改
    tools {
        maven ‘maven3‘
        jdk   ‘jdk8‘
    }
    //常量參數,初始確定後一般不需更改
    environment{
        //git服務全系統只讀賬號cred_id【參數值對外隱藏】
        CRED_ID=‘*****-****-****-****-*********‘
        //測試人員郵箱地址【參數值對外隱藏】
        QA_EMAIL=‘*****@*****.com‘
        //接口測試(網絡層)的job名,一般由測試人員編寫
        ITEST_JOBNAME=‘Guahao_InterfaceTest_ExpertPatient‘
    }
    options {
        //保持構建的最大個數
        buildDiscarder(logRotator(numToKeepStr: ‘10‘)) 
    }
    //定期檢查開發代碼更新,工作日每晚4點做daily build
    triggers {
        pollSCM(‘H 4 * * 1-5‘)
    }
    //pipeline的各個階段場景
    stages {
        stage(‘代碼獲取‘) {
            steps {
            //根據param.server分割獲取參數,包括IP,jettyPort,username,password
            script {
                def split=params.server.split(",")
                serverIP=split[0]
                jettyPort=split[1]
                serverName=split[2]
                serverPasswd=split[3]
            }
              echo "starting fetchCode from ${params.repoUrl}......"
              // Get some code from a GitHub repository
              git credentialsId:CRED_ID, url:params.repoUrl, branch:params.repoBranch
            }
        }
        stage(‘單元測試‘) {
            steps {
              echo "starting unitTest......"
              //註入jacoco插件配置,clean test執行單元測試代碼. All tests should pass.
              sh "mvn org.jacoco:jacoco-maven-plugin:prepare-agent -f ${params.pomPath} clean test -Dautoconfig.skip=true -Dmaven.test.skip=false -Dmaven.test.failure.ignore=true"
              junit ‘**/target/surefire-reports/*.xml‘
              //配置單元測試覆蓋率要求,未達到要求pipeline將會fail,code coverage.LineCoverage>20%.
              jacoco changeBuildStatus: true, maximumLineCoverage:"${params.lineCoverage}"
            }
        }
        stage(‘靜態檢查‘) {
            steps {
                echo "starting codeAnalyze with SonarQube......"
                //sonar:sonar.QualityGate should pass
                withSonarQubeEnv(‘SonarQube‘) {
                  //固定使用項目根目錄${basedir}下的pom.xml進行代碼檢查
                  sh "mvn -f pom.xml clean compile sonar:sonar"
                }
                script {
                timeout(10) { 
                    //利用sonar webhook功能通知pipeline代碼檢測結果,未通過質量閾,pipeline將會fail
                    def qg = waitForQualityGate() 
                        if (qg.status != ‘OK‘) {
                            error "未通過Sonarqube的代碼質量閾檢查,請及時修改!failure: ${qg.status}"
                        }
                    }
                }
            }
        }

        stage(‘部署測試環境‘) { 
            steps {
                echo "starting deploy to ${serverIP}......"
                //編譯和打包
                sh "mvn  -f ${params.pomPath} clean package -Dautoconfig.skip=true -Dmaven.test.skip=true"
                archiveArtifacts warLocation
                script {
                    wrap([$class: ‘BuildUser‘]) {
                    //發布war包到指定服務器,虛擬機文件目錄通過shell腳本初始化建立,所以目錄是固定的
                    sh "sshpass -p ${serverPasswd} scp ${params.warLocation} ${serverName}@${serverIP}:htdocs/war"
                    //這裏增加了一個小功能,在服務器上記錄了基本部署信息,方便多人使用一套環境時問題排查,storge in {WORKSPACE}/deploy.log  & remoteServer:htdocs/war
                    Date date = new Date()
                    def deploylog="${date.toString()},${BUILD_USER} use pipeline  ‘${JOB_NAME}(${BUILD_NUMBER})‘ deploy branch ${params.repoBranch} to server ${serverIP}"
                    println deploylog
                    sh "echo ${deploylog} >>${WORKSPACE}/deploy.log"
                    sh "sshpass -p ${serverPasswd} scp ${WORKSPACE}/deploy.log ${serverName}@${serverIP}:htdocs/war"
                    //jetty restart,重啟jetty
                    sh "sshpass -p ${serverPasswd} ssh ${serverName}@${serverIP} ‘bin/jettyrestart.sh‘ "
                    }
                }
            }
        }

      stage(‘接口自動化測試‘) {
            steps{
                echo "starting interfaceTest......"
                script {
                 //為確保jetty啟動完成,加了一個判斷,確保jetty服務器啟動可以訪問後再執行接口層測試。
                 timeout(5) {
                     waitUntil {
                        try {
                            //確保jetty服務的端口啟動成功
                            sh "nc -z ${serverIP} ${jettyPort}"
                            //sh "wget -q http://${serverIP}:${jettyPort} -O /dev/null"
                            return true
                        } catch (exception) {
                            return false
                            }
                        }
                    }
                //將參數IP和Port傳入到接口測試的job,需要確保接口測試的job參數可註入
                 build job: ITEST_JOBNAME, parameters: [string(name: "dubbourl", value: "${serverIP}:${params.dubboPort}")]
                }
            }
        }

        stage(‘UI自動化測試‘) { 
             steps{
             echo "starting UITest......"
             //這個項目不需要UI層測試,UI自動化與接口測試的pipeline腳本類似
             }
         }

        stage(‘性能自動化測試 ‘) { 
            steps{
                 echo "starting performanceTest......"
                //視項目需要增加性能的冒煙測試,具體實現後續專文闡述
                }
        }

        stage(‘通知人工驗收‘){
            steps{
                script{
                    wrap([$class: ‘BuildUser‘]) {
                    if(params.isCommitQA==false){
                        echo "不需要通知測試人員人工驗收"
                    }else{
                        //郵件通知測試人員人工驗收
                         mail to: "${QA_EMAIL}",
                         subject: "PineLine ‘${JOB_NAME}‘ (${BUILD_NUMBER})人工驗收通知",
                         body: "${BUILD_USER}提交的PineLine ‘${JOB_NAME}‘ (${BUILD_NUMBER})進入人工驗收環節\n請及時前往${env.BUILD_URL}進行測試驗收"
                    }

                    }
                }
            }
        }

        // stage(‘發布系統‘) { 
        //     steps{
        //         echo "starting deploy......"
        //     //    TODO發布環節後續專題闡述
        //     }
        // }
    }
}

當然,這個pipeline腳本只是針對該項目的場景展開並盡可能做到通用(在本公司大多數的項目還是適用的,只需要對參數化參數進行調整),部分項目會根據自己項目的情況進行裁剪和微調,比如一些耗時長的stage會使用parallel並發的方式進行等等。

[持續交付實踐] pipelne:pipeline 使用之項目樣例