Jenkins Pipeline 介紹和實戰

一、概述

基於 Docker 集成 CI 環境。涉及技術:Linux(Ubuntu 14.04), Docker, Jenkins, Git/Gitlab, Web/Httpbin, Python/Pytest, UI/Selenium, Robotframework, Grid Server, Appium 等。

架構圖如下:

在這裏插入圖片描述

二、什麼是Pipeline?

Jenkins Pipeline是一套插件,支持將連續輸送Pipeline實施和整合到Jenkins。Pipeline提供了一組可擴展的工具,用於通過PipelineDSL爲代碼創建簡單到複雜的傳送Pipeline。

通常,此“Pipeline代碼”將被寫入 Jenkinsfile項目的源代碼控制存儲庫,例如:

    pipeline {
        agent any (1)
    
        stages { 
            stage('build') { (2)
                steps { (3)
                    sh 'make' (4)
                }
            }
            stage('sita'){
                steps {
                    sh 'python script.py'
                    junit 'reports/**/*.xml' (5)
                }
            }
        }
    }
  1. agent 表示Jenkins應該爲Pipeline的這一部分分配一個執行者和工作區。
  2. stage 描述了這條Pipeline的一個階段。
  3. steps 描述了要在其中運行的步驟 stage
  4. sh 執行給定的shell命令
  5. junit是由JUnit插件提供的 用於聚合測試報告的Pipeline步驟。

三、爲什麼使用Pipeline?

Jenkins從根本上講是一種支持多種自動化模式的自動化引擎。Pipeline在Jenkins上添加了一套強大的自動化工具,支持從簡單的連續集成到全面的連續輸送Pipeline的用例。通過建模一系列相關任務,用戶可以利用Pipeline 的許多功能。

Pipeline的功能和優點:

  1. 持久性:在jenkins的master按計劃和非計劃的重啓後,pipeline的job仍然能夠工作,不受影響。其實理解起來也很簡單,jenkins的master和agent通過ssh連接,如果你知道nohup或disown的話,就可以理解爲啥master的重啓不會影響agent上的job繼續運行。
  2. 可暫停性:pipeline基於groovy可以實現job的暫停和等待用戶的輸入或批准然後繼續執行。
  3. 更靈活的並行執行,更強的依賴控制,通過groovy腳本可以實現step,stage間的並行執行,和更復雜的相互依賴關係。
  4. 可擴展性:通過groovy的編程更容易的擴展插件。
  5. 設計Pipeline = 設計代碼,很優雅
  6. As Code:集中管理CI腳本、用代碼庫來管理腳本、從代碼庫直接讀取腳本,從而可以將項目CI迅速拉起來!

四、Pipeline 實現 build & scan

一、conf 配置Pipeline各個需要執行的參數: qa.xxx.com-b-master.yaml

    credentials_id: github-xxx-jenkins 
    git_base: [email protected]
    
    # If the container is running on a host who needs to resolve qa.xindong.com
    # differently, you may need to pass a host:ip mapping for the container to 
    # resolve qa.xindong.com correctly. This could be done by setting a global
    # environment variable 'ADD_HOST'. e.g. qa.xindong.com:10.10.10.100
    add_host:
      
    branch: master
    
    stages:
      checkout_src: true
      build_tools: true
      build: true
      test: false
      pack: false
      check: false
      scan: true
      sita: false
    
    email:
      recipients: qabuilder
      always: false # Always send emails regardless task status.
      failed: false # Send emails when the task fails.
      changed: false # Send emails only when the task status changes.
    
    image:
      proj: xxx-server
      user: qa
      tag: bmas
    
    sonar:
      url: https://qa.xxx.com/sonar/
    
    # To use influxdb, a target must be configured in Jenkins global configure  
    influxdb:
      target: qa.xxx.com
    
    sita:
      env: sita
  1. Jenkins 憑據:credentials_id
  2. 下載代碼地址:git_base
  3. 容器到主機的映射:add_host
  4. 服務器代碼分支:branch
  5. Jenkinsfile 執行階段:stages
  6. 完成測試發送郵件:email
  7. 服務器 Docker 鏡像:image
  8. SonarQube 掃描結果:sonar
  9. 數據庫存儲:influxdb
  10. 測試參數:sita

注:stages中,若 stage 爲 true,則 Jnekinsfile 執行對應stage,反之,跳過。

二、Jenkinsfile 實現 build 和 scan

因爲 conf 文件 qa.xxx.com-b-master.yaml 中 stages 決定是否執行。以下四個 stages 爲 true,即,

  1. checkout_src
  2. build_tools
  3. build
  4. scan
    #!/usr/bin/env groovy
    
    /**
      * Pipeline to build and test the project, and pack the deliverables.
      */
    
    // Shared library for the pipeline.
    // @Library('ci-shared-library') _
    
    // Global configuration parameters for the pipeline.
    def conf = [:]
    
    pipeline {
    
      agent any //將在Jenkins master 執行
     // agent {label 'slave'} //將在Jenkins slave節點執行
    
      // Stages could ben enabled/disabled by configuration parameters.
      stages {
        // Load parameters from configuration file.
        stage("setup") { // 讀取yaml文件內容,參考安裝配置Jenkins文檔中的“參數化構建過程”
          steps {
            script { 
              conf = readYaml(file: "conf/" + (params.conf ?: "default") + ".yaml")
            }
          }
        } // setup
    
        // Checkout project source code.
        stage("checkout-src") { //git|svn 下載源代碼
          when {
            beforeAgent true
            expression { conf.stages.checkout_src } //conf 中 stages 參數
          }
          steps {
            // Checkout source code.
            checkout([
              $class: "GitSCM", 
              branches: [[name: "*/${conf.branch}"]], //conf 中 branch 參數
              extensions: [], // 默認存儲到 workspace 目錄下
              submoduleCfg: [],
              userRemoteConfigs: [[
                credentialsId: "${conf.credentials_id}", 
                //conf 中 credentials_id 參數
                url: "${conf.git_base}:xxx/xxx-server.git" 
                //conf 中 git_base 參數
              ]]
            ])
            // Checkout proto.
            checkout([
              $class: "GitSCM",
              branches: [[name: "*/${conf.branch}"]],
              doGenerateSubmoduleConfigurations: false,
              extensions: [[
                $class: "RelativeTargetDirectory",
                relativeTargetDir: "xxx-proto" 
                //存儲到 worksapce 下的 proto 目錄
              ]],
              submoduleCfg: [],
              userRemoteConfigs: [[
                credentialsId: "${conf.credentials_id}",
                url: "${conf.git_base}:xxx/xxx-proto.git"
              ]]
            ])
            // Checkout resource.
            checkout([
              $class: "GitSCM",
              branches: [[name: "*/${conf.branch}"]],
              doGenerateSubmoduleConfigurations: false,
              extensions: [[
                $class: "RelativeTargetDirectory",
                relativeTargetDir: "bin/Debug/DesignerWork"
                //存儲到 worksapce 下的 bin/Debug/DesignerWork 目錄
              ]],
              submoduleCfg: [],
              userRemoteConfigs: [[
                credentialsId: "${conf.credentials_id}",
                url: "${conf.git_base}:xxx/DesignerWork.git"
              ]]
            ])
    
            // Checkout atelier-ci.
            checkout([
              $class: "GitSCM",
              branches: [[name: "*/master"]],
              doGenerateSubmoduleConfigurations: false,
              extensions: [[
                $class: "RelativeTargetDirectory",
                relativeTargetDir: "ci"
                //存儲到 worksapce 下的 ci 目錄
              ]],
              submoduleCfg: [],
              userRemoteConfigs: [[
                credentialsId: "${conf.credentials_id}",
                url: "${conf.git_base}:xxx/xxx-ci.git"
              ]]
            ])
          }
        } // stage checkout-src
    
    
        // Build docker images for tools.
        stage("build-tools") { //構建鏡像
          when {
            beforeAgent true
            expression { conf.stages.build_tools }
          }
          steps {
            // Build all tools for the toolchain.
            script {
              def add_host = conf.add_host ? "--add-host ${conf.add_host}" : ""
              //用於主機地址到容器的映射
              dir ("ci/toolchain") { //$ cd ci/toolchain
                sh ( // build-tools.sh 構建鏡像:builder tester scanner sita
                   """ \
                    ./build-tools.sh \
                    --user ${conf.image.user} \
                    --version ${conf.image.tag}-latest \
                    ${add_host} \
                    ${conf.image.proj} \
                  """
                )
              }
            }
          }
        } // stage build-tools
    
        // Build source.
        stage("build") { // build 服務器
          when {
            beforeAgent true
            expression { conf.stages.build }
          }
          agent {
            docker { // 生成服務器容器
              image "${conf.image.user}/${conf.image.proj}-builder:${conf.image.tag}-latest"
              args "-v /etc/hosts:/etc/hosts"
              reuseNode true
            }
          }
          steps {
            script { // 構建服務器
              sh "/opt/script/start-build.sh -w ${WORKSPACE}"
            }
          }
        } // stage build
    
        // Run unit tests.
        stage("test") { // 跳過
          when {
            beforeAgent true
            expression { conf.stages.test }
          }
          agent {
            docker {
              image "${conf.image.user}/${conf.image.proj}-tester:${conf.image.tag}-latest"
              args "-v /etc/hosts:/etc/hosts"
              reuseNode true
            }
          }
          steps {
            script {
              sh "/opt/script/start-test.sh -w ${WORKSPACE}/src"
            }
          }
        } // stage test
    
        // Check source code by static anlaysis tools.
        stage("check") { // 跳過
          when {
            beforeAgent true
            expression { conf.stages.check }
          }
          agent {
            docker {
              image "${conf.image.user}/${conf.image.proj}-scanner:${conf.image.tag}-latest"
              args "-v /etc/hosts:/etc/hosts"
              reuseNode true
            }
          }
          steps {
            script {
              sh("/opt/script/start-cppcheck.sh -w ${WORKSPACE}/src")
            }
          }
        } // stage check
    
        // Scan source code by sonar.
        stage("scan") { // sonar 掃描
          when {
            beforeAgent true
            expression { conf.stages.scan }
          }
          agent {
            docker { // 生成 sonar 容器
              image "${conf.image.user}/${conf.image.proj}-scanner:${conf.image.tag}-latest"
              args "-v /etc/hosts:/etc/hosts"
              reuseNode true
            }
          }
          steps {
            script { // 掃描 workspace 下的 src 目錄
              sh(
                """\
                /opt/script/start-scan.sh \
                    -w ${WORKSPACE}/src \
                    -Dsonar.host.url=${conf.sonar.url} \
                    -Dsonar.branch.name=${conf.branch}
                """
              )
            }
          }
        } // stage scan
    
      // 發送郵件
      changed {
          script {
          // Send notification emails.
          if (conf.email.changed) {
              emailext (
                recipientProviders: [
                  [$class: 'DevelopersRecipientProvider'], 
                  [$class: 'CulpritsRecipientProvider'], 
                  [$class: 'RequesterRecipientProvider']], 
                mimeType: 'text/html',
                to: "${conf.email.recipients}",
                subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!',
                body: "Check ${BUILD_URL} to view the results.",
                attachLog: true
              )
            }
          }
        }
    
        failure {
          script {
          // Send notification emails.
          if (conf.email.failed) {
              emailext (
                recipientProviders: [
                  [$class: 'DevelopersRecipientProvider'], 
                  [$class: 'CulpritsRecipientProvider'], 
                  [$class: 'RequesterRecipientProvider']], 
                mimeType: 'text/html',
                to: "${conf.email.recipients}",
                subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!',
                body: "Check ${BUILD_URL} to view the results.",
                attachLog: true
              )
            }
          }
        }
      } // post 
    } // pipeline
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章