Docker+Jenkins+Pipeline實現(xiàn)持續(xù)集成-模板

這里記錄一些當前使用的pipeline模板和郵件模板

Java項目模板

// java項目

// 需要解析http返回結果時使用
import groovy.json.JsonSlurperClassic
import groovy.json.JsonOutput

// 按需求更改變量的值
node {
    // 參數(shù)設置
    def repoUrl = 'git@*****.git'
    def repoBranch = 'dev'
    def gitDir = ''
    def workspace = ''

    // docker鏡像信息
    def imageName = "*****/demo"
    def imageTag = "dev"

    // rancher1.X部署所需變量
    def rancher1Url = 'https://<rancher1_service>/v2-beta'  // rancher1.X地址 
    def rancher1Env = ''  // rancher1.X需部署服務所在環(huán)境的ID
    def rancher1Service = '<stack>/<service>'   // rancher1.X需部署服務的 棧/服務名

    // rancher2.X部署所需變量
    def rancher2Url = "https://<rancher2_service>/v3/project/<cluster_id>:<project_id>" // rancher2.X地址+project
    def rancher2Namespace = ""  // rancher2.X需部署服務的命名空間
    def rancher2Service = "bookkeeper"  // rancher2.X需部署服務的服務名

    def recipients = ''    // 收件人
    def jenkinsHost = ''    // jenkins服務器地址

    // 認證ID
    def gitCredentialsId = 'aa651463-c335-4488-8ff0-b82b87e11c59'
    def settingsConfigId = '3ae4512e-8380-4044-9039-2b60631210fe'
    def rancherAPIKey = 'd41150be-4032-4a53-be12-3024c6eb4204'
    def soundsId = '69c344f1-8b11-47a1-a3b6-dfa423b94d78'

    // 工具配置
    def mvnHome = 'maven3.5.2'

    env.SONAR_HOME = "${tool 'sonarscanner'}"
    env.PATH="${env.SONAR_HOME}/bin:${env.PATH}"

    try {
        // 正常構建流程
        stage("Preparation"){
            // 拉取需要構建的代碼
            git_maps = checkout scm: [$class: 'GitSCM', branches: [[name: "*/${repoBranch}"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: "${gitDir}"]], userRemoteConfigs: [[credentialsId: "${gitCredentialsId}", url: "${repoUrl}"]]]
            env.GIT_REVISION = git_maps.GIT_COMMIT[0..8]
        }
        try {
            stage('BackEndBuild') {
                // maven構建生成二進制包
                dir("${workspace}"){
                    withMaven(
                        maven: "${mvnHome}", 
                        mavenSettingsConfig: "${settingsConfigId}",
                        options: [artifactsPublisher(disabled: true)]) {
                        sh "mvn -U clean package -Dmaven.test.skip=true"
                    }
                }
            }
        }finally {
            stage('ArchiveJar') {
                // 獲取二進制包產物
                archiveArtifacts allowEmptyArchive: true, artifacts: "${workspace}target/surefire-reports/TEST-*.xml"
                archiveArtifacts allowEmptyArchive: true, artifacts: "${workspace}target/*.jar"
            }
        }
        stage('CodeCheck'){
            // sonar_scanner進行代碼靜態(tài)檢查
            withSonarQubeEnv('sonar') {
                sh """
                sonar-scanner -X -Dsonar.language=java \
                -Dsonar.projectKey=$JOB_NAME \
                -Dsonar.projectName=$JOB_NAME \
                -Dsonar.projectVersion=$GIT_REVISION \
                -Dsonar.sources=src/ \
                -Dsonar.sourceEncoding=UTF-8 \
                -Dsonar.java.binaries=target/ \
                -Dsonar.exclusions=src/test/**
                """
            }
        }
        stage("QualityGate") {
            // 獲取sonar檢查結果
            timeout(time: 1, unit: "HOURS") {
                def qg = waitForQualityGate()
                if (qg.status != 'OK') {
                    error "Pipeline aborted due to quality gate failure: ${qg.status}"
                }
            }
        }
        stage('DockerBuild'){
            // docker生成鏡像并push到遠程倉庫中 
            sh """
                rm -f ${workspace}src/docker/*.jar
                cp ${workspace}target/*.jar ${workspace}src/docker/
            """
            dir("${workspace}src/docker/"){
                def image = docker.build("${imageName}:${imageTag}")
                image.push()
                sh "docker rmi ${imageName}:${imageTag}"
            }
        }
        stage('Rancher1Deploy'){
            // Rancher1.X上進行服務的更新部署
            rancher confirm: false, credentialId: "${rancherAPIKey}", endpoint: "${rancher1Url}", environmentId: "${rancher1Env}", environments: '', image: "${imageName}:${imageTag}", ports: '', service: "${rancher1Service}"
        }
        stage('Rancher2Deploy') {
            // Rancher2.X上更新容器
            // 獲取服務信息
            def response = httpRequest acceptType: 'APPLICATION_JSON', authentication: "${rancherAPIKey}", contentType: 'APPLICATION_JSON', httpMode: 'GET', responseHandle: 'LEAVE_OPEN', timeout: 10, url: "${rancher2Url}/workloads/deployment:${rancher2Namespace}:${rancher2Service}"
            def serviceInfo = new JsonSlurperClassic().parseText(response.content)
            response.close()
            
            def dockerImage = imageName+":"+imageTag
            if (dockerImage.equals(serviceInfo.containers[0].image)) {
                // 如果鏡像名未改變族展,直接刪除原容器
                // 查詢容器名稱
                response = httpRequest acceptType: 'APPLICATION_JSON', authentication: "${rancherAPIKey}", contentType: 'APPLICATION_JSON', httpMode: 'GET', responseHandle: 'LEAVE_OPEN', timeout: 10, url: "${rancher2Url}/pods/?workloadId=deployment:${rancher2Namespace}:${rancher2Service}"
                def podsInfo = new JsonSlurperClassic().parseText(response.content)
                def containerName = podsInfo.data[0].name
                response.close()
                // 刪除容器
                httpRequest acceptType: 'APPLICATION_JSON', authentication: "${rancherAPIKey}", contentType: 'APPLICATION_JSON', httpMode: 'DELETE', responseHandle: 'NONE', timeout: 10, url: "${rancher2Url}/pods/${rancher2Namespace}:${containerName}"
                
            } else {
                // 如果鏡像名改變袭灯,使用新鏡像名更新服務
                serviceInfo.containers[0].image = dockerImage
                def updateJson = new JsonOutput().toJson(serviceInfo)
                httpRequest acceptType: 'APPLICATION_JSON', authentication: "${rancherAPIKey}", contentType: 'APPLICATION_JSON', httpMode: 'PUT', requestBody: "${updateJson}", responseHandle: 'NONE', timeout: 10, url: "${rancher2Url}/workloads/deployment:${rancher2Namespace}:${rancher2Service}"
            }
        }

        // 構建成功:聲音提示,郵件發(fā)送
        httpRequest authentication:"${soundsId}", url:'http://${jenkinsHost}/sounds/playSound?src=file:///var/jenkins_home/sounds/4579.wav'
        emailext attachlog:true, body: '$DEFAULT_CONTENT', subject: '$DEFAULT_SUBJECT', to: "${recipients}"
    } catch(err) {
        // 構建失敗:聲音提示,郵件發(fā)送
        currentBuild.result = 'FAILURE'
        httpRequest authentication:"${soundsId}", url:'http://${jenkinsHost}/sounds/playSound?src=file:///var/jenkins_home/sounds/8923.wav'
        emailext attachlog:true, body: '$DEFAULT_CONTENT', subject: '$DEFAULT_SUBJECT', to: "${recipients}"
    }
}

php+js的項目模板

與java不同的是,我們php+js的服務使用了3個容器贱鼻,php+nginx+code的方式進行部署,每次只需要更新服務中的code容器即可滋将;另外邻悬,為了防止php和js的構建污染jenkins服務,我們利用了官方的node鏡像和一個自定義已安裝好php構建環(huán)境的composer鏡像随闽,并使用docker.image('').inside{}代碼塊將構建過程放置到了docker容器當中父丰。

// php+js項目

import groovy.json.JsonSlurperClassic
import groovy.json.JsonOutput

node {
    def repoUrl = 'git@******.git'
    def repoBranch = 'develop'
    def imageName = '***/code'
    def imageTag = 'dev'
    def buildEnv = 'develop'

    // rancher2.X部署所需變量
    def rancherUrl = 'https://<rancher2_service>/v3/project/<cluster_id>:<project_id>'
    def rancherNamespace = ''
    def rancherService = ''
    def recipients = ''
    def jenkinsHost = ''    // jenkins服務器地址

    // 認證ID
    def gitCredentialsId = 'aa651463-c335-4488-8ff0-b82b87e11c59'
    def settingsConfigId = '3ae4512e-8380-4044-9039-2b60631210fe'
    def rancherAPIKey = 'd41150be-4032-4a53-be12-3024c6eb4204'
    def soundsId = '69c344f1-8b11-47a1-a3b6-dfa423b94d78'
    
    env.SONAR_HOME = "${tool 'sonarscanner'}"
    env.PATH="${env.SONAR_HOME}/bin:${env.PATH}"
    
    try {
        stage("prepare") {
            // 拉取需要構建的代碼
            git_maps = checkout scm: [$class: 'GitSCM', branches: [[name: "*/${repoBranch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], userRemoteConfigs: [[credentialsId: "${gitCredentialsId}", url: "${repoUrl}"]]]
            env.GIT_REVISION = git_maps.GIT_COMMIT[0..8]
        }
        stage('CodeCheck'){
            // sonar_scanner進行代碼靜態(tài)檢查
            withSonarQubeEnv('sonar') {
                sh """
                sonar-scanner -X \
                -Dsonar.projectKey=$JOB_NAME \
                -Dsonar.projectName=$JOB_NAME \
                -Dsonar.projectVersion=$GIT_REVISION \
                -Dsonar.sourceEncoding=UTF-8 \
                -Dsonar.modules=php-module,javascript-module \
                -Dphp-module.sonar.projectName=PHP-Module \
                -Dphp-module.sonar.language=php \
                -Dphp-module.sonar.sources=. \
                -Dphp-module.sonar.projectBaseDir=backend/app \
                -Djavascript-module.sonar.projectName=JavaScript-Module \
                -Djavascript-module.sonar.language=js \
                -Djavascript-module.sonar.sources=. \
                -Djavascript-module.sonar.projectBaseDir=front/src
                """
            }
        }
        stage("QualityGate") {
            // 獲取sonar檢查結果
            timeout(time: 1, unit: "HOURS") {
                def qg = waitForQualityGate()
                if (qg.status != 'OK') {
                    error "Pipeline aborted due to quality gate failure: ${qg.status}"
                }
            }
        }
        stage("frontBuild") {
            // 前端構建
            docker.image("node").inside() {
                sh """
                cd front
                npm install
                npm run build ${buildEnv}
                """
            }
        }
        stage("back-build") {
            // 后端構建
            docker.image("<docker_registry>/composer:v0").inside() {
                sh """
                cd backend
                composer install
                """
            }
        }
        stage("docker-code-build") {
            // 代碼鏡像構建
            sh "cp -r front/build docker/code/build"
            sh "cp -r backend docker/code/api"
            dir("docker/code") {
                docker.build("${imageName}:${imageTag}").push()
                sh "docker rmi ${imageName}:${imageTag}"
            }
        }
        stage('rancherDeploy') {
            // Rancher上更新容器
            // 查詢容器名稱
            def response = httpRequest acceptType: 'APPLICATION_JSON', authentication: "${rancherAPIKey}", contentType: 'APPLICATION_JSON', httpMode: 'GET', responseHandle: 'LEAVE_OPEN', timeout: 10, url: "${rancherUrl}/pods/?workloadId=deployment:${rancherNamespace}:${rancherService}"
            def podsInfo = new JsonSlurperClassic().parseText(response.content)
            def containerName = podsInfo.data[0].name
            print(containerName)
            response.close()
        
            // 刪除容器
            httpRequest acceptType: 'APPLICATION_JSON', authentication: "${rancherAPIKey}", contentType: 'APPLICATION_JSON', httpMode: 'DELETE', responseHandle: 'NONE', timeout: 10, url: "${rancherUrl}/pods/${rancherNamespace}:${containerName}"
        }
        stage("tear-down") {
            sh "rm -rf docker"
        }
        httpRequest authentication:"${soundsId}", url:'http://${jenkinsHost}/sounds/playSound?src=file:///var/jenkins_home/sounds/4579.wav'
        emailext attachlog:true, body: '$DEFAULT_CONTENT', subject: '$DEFAULT_SUBJECT', to: "${recipients}"
    } catch(err) {
        currentBuild.result = 'FAILURE'
        httpRequest authentication:"${soundsId}", url:'http://${jenkinsHost}/sounds/playSound?src=file:///var/jenkins_home/sounds/8923.wav'
        emailext attachlog:true, body: '$DEFAULT_CONTENT', subject: '$DEFAULT_SUBJECT', to: "${recipients}"
    }
}

Android項目模板

這里android項目只是進行了單元測試和構建過程,保證本次構建能夠成功進行

// android項目

// 按需求更改變量的值

node {
    // 參數(shù)設置
    def repoUrl = 'git@*****.git'
    def repoBranch = 'develop'
    def gitDir = ''
    def workspace = 'Demo/'
    def version = ''
    def recipients = ''
    def jenkinsHost = ''    // jenkins服務器地址

    // 認證ID
    def gitCredentialsId = 'aa651463-c335-4488-8ff0-b82b87e11c59'
    def soundsId = '69c344f1-8b11-47a1-a3b6-dfa423b94d78'

    // 工具配置
    def gradleTool = './gradlew'
    // env.GRADLE_HOME = "${tool 'Gradle3.3'}"
    // env.PATH="${env.GRADLE_HOME}/bin:${env.PATH}"

    try {
        // 正常構建流程
        stage("Preparation"){
            // 拉取需要構建的代碼
            checkout scm: [$class: 'GitSCM', branches: [[name: "*/${repoBranch}"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: "${gitDir}"]], userRemoteConfigs: [[credentialsId: "${gitCredentialsId}", url: "${repoUrl}"]]]
        }
        try {
            stage('UnitTest') {
                // 在清空前一次構建的結果后進行單元測試掘宪,并獲取測試報告
                dir("${workspace}"){
                    sh "${gradleTool} clean"
                    sh "${gradleTool} testReleaseUnitTest --stacktrace"
                }
            }
            stage('Build') {
                // 使用Gradlew進行sdk的構建
                dir("${workspace}"){
                    sh "${gradleTool} makeJar"
                }
            }
        }
        finally {
            stage('Results'){
                // 獲取單元測試報告
                sh """
                cd ${workspace}librasdk/build/reports/tests/
                tar -zcvf test_reports.tar.gz ./test*/
                """
                archiveArtifacts allowEmptyArchive: true, artifacts: "${workspace}librasdk/build/reports/tests/test_reports.tar.gz"
                // 獲取sdk構建生成的jar包
                archive "${workspace}librasdk/build/libs/*/*.jar"
            }
        }
        httpRequest authentication:"${soundsId}", url:'http://10.38.162.13:8081/sounds/playSound?src=file:///var/jenkins_home/sounds/4579.wav'
        emailext attachlog:true, body: '$DEFAULT_CONTENT', subject: '$DEFAULT_SUBJECT', to: "${recipients}"
    } catch(err) {
        // 構建失敗
        currentBuild.result = 'FAILURE'
        httpRequest authentication:"${soundsId}", url:'http://${jenkinsHost}/sounds/playSound?src=file:///var/jenkins_home/sounds/8923.wav'
        emailext attachlog:true, body: '$DEFAULT_CONTENT', subject: '$DEFAULT_SUBJECT', to: "${recipients}"
    }
}

郵件模板

標題

構建通知:${BUILD_STATUS} - ${PROJECT_NAME} -  # ${BUILD_NUMBER}!

內容

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次構建日志</title>
</head>

<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
    offset="0">
    <table width="95%" cellpadding="0" cellspacing="0"
        style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
        <tr>
            <td>(本郵件是程序自動下發(fā)的蛾扇,請勿回復!)</td>
        </tr>
        <tr>
            <td><h2>
                    <font color="#0000FF">構建結果 - ${BUILD_STATUS}</font>
                </h2></td>
        </tr>
        <tr>
            <td><br />
            <b><font color="#0B610B">構建信息</font></b>
            <hr size="2" width="100%" align="center" /></td>
        </tr>
        <tr>
            <td>
                <ul>
                    <li>項目名稱&nbsp;:&nbsp;${PROJECT_NAME}</li>
                    <li>構建編號&nbsp;:&nbsp;第${BUILD_NUMBER}次構建</li>
                    <li>觸發(fā)原因:&nbsp;${CAUSE}</li>
                    <li>構建日志:&nbsp;<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
                    <li>構建&nbsp;&nbsp;Url&nbsp;:&nbsp;<a href="${BUILD_URL}">${BUILD_URL}</a></li>
                    <li>項目&nbsp;&nbsp;Url&nbsp;:&nbsp;<a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
                </ul>
            </td>
        </tr>
        <tr>
            <td><b><font color="#0B610B">Changes Since Last
                        Successful Build:</font></b>
            <hr size="2" width="100%" align="center" /></td>
        </tr>
        <tr>
            <td>
                <ul>
                    <li>歷史變更記錄 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
                </ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"}
             <br>
            </td>
        </tr>
        <tr>
            <td><b><font color="#0B610B">Failed Test Results</font></b>
            <hr size="2" width="100%" align="center" /></td>
        </tr>
        <tr>
            <td><pre
                    style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre>
                <br></td>
        </tr>
        <tr>
            <td><b><font color="#0B610B">構建日志 (最后 100行):</font></b>
            <hr size="2" width="100%" align="center" /></td>
        </tr>
        <tr>
            <td><textarea id="test" cols="80" rows="30" readonly="readonly"
                    style="font-family: Courier New">${BUILD_LOG, maxLines=100}</textarea>
            </td>            
        </tr>
    </table>
</body>
</html>
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末魏滚,一起剝皮案震驚了整個濱河市镀首,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鼠次,老刑警劉巖更哄,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腥寇,居然都是意外死亡成翩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門花颗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捕传,“玉大人惠拭,你說我怎么就攤上這事扩劝∮孤郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵棒呛,是天一觀的道長聂示。 經(jīng)常有香客問我,道長簇秒,這世上最難降的妖魔是什么鱼喉? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮趋观,結果婚禮上扛禽,老公的妹妹穿的比我還像新娘。我一直安慰自己皱坛,他們只是感情好编曼,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剩辟,像睡著了一般掐场。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贩猎,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天熊户,我揣著相機與錄音,去河邊找鬼吭服。 笑死嚷堡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的艇棕。 我是一名探鬼主播麦到,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼欠肾!你這毒婦竟也來了瓶颠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤刺桃,失蹤者是張志新(化名)和其女友劉穎粹淋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑟慈,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡桃移,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葛碧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片借杰。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖进泼,靈堂內的尸體忽然破棺而出蔗衡,到底是詐尸還是另有隱情纤虽,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布绞惦,位于F島的核電站逼纸,受9級特大地震影響,放射性物質發(fā)生泄漏济蝉。R本人自食惡果不足惜杰刽,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望王滤。 院中可真熱鬧贺嫂,春花似錦、人聲如沸雁乡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔗怠。三九已至墩弯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寞射,已是汗流浹背渔工。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桥温,地道東北人引矩。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像侵浸,于是被迫代替她去往敵國和親旺韭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容