使用Jenkins構建CI/CD之多分支流水線指北(實戰)

手把手帶你一塊兒從0到1搭建一個企業級的自動化構建流程 網上完整的關於多分支流水線的配置不多,但願這篇不短的文章能給你帶來幫助html

簡介

  • 從0到1打造先後端自動化工做流
  • 企業級的實踐筆記
  • 最佳實踐(可在此基礎逐步完善)

緣起

因爲公司的Jenkins配置沒有部署成功的通知,在我學了幾天的Jenkins後終因而對公司的Jenkins配置下手了,結果我剛裝完dingtalk插件自動重啓後,發現以前主管配置的構建項目數據都丟失了,害,正好給了我練手的機會,因而就有了如下從0到1的辛酸歷程。前端

在Docker中安裝並運行Jenkins

這裏假設你的服務器已經裝好了dockerjava

使用的鏡像是jenkinsci/blueocean,這是一個jenkins的穩定及持續維護的鏡像源,自己就集成了Blue Ocean等使用插件,很是方便。node

拉取鏡像

docker pull jenkinsci/blueocean
複製代碼

運行Jenkins

docker run -idt --name kmywjenkins -p 9090:8080 -p 60000:50000 -v jenkins-data:/var/jenkins_home -v /data/web-data/docker.sock:/var/run/docker.sock jenkinsci/blueocean
複製代碼

參數解釋:linux

-idt 以交互的方式、新建一個模擬終端運行容器git

--name 容器的別名web

-p 指定容器映射宿主機的端口 -> 宿主機端口:容器端口正則表達式

-v jenkins-data:/var/jenkins_home Jenkins容器在工做的時候,若是要執行Docker的命令(例如 docker ps、docker run等),須要有個途徑能鏈接到宿主機的docker服務,此參數就是用來創建容器和宿主機docker服務的鏈接的spring

-v /data/web-data/docker.sock:/var/run/docker.sock 將該容器的數據保留在宿主機的目錄,這樣即便容器崩潰了,裏面的配置和任務都不會丟失docker

須要注意的是,docker中默認是以jenkins用戶運行的Jenkins,如需以root用戶能夠加參數-u root,本示例未指定root。

訪問Jenkins Docker容器

有時候須要進入Jenkins容器執行一些命令,能夠經過docker exec命令訪問,例如:docker exec -it [containerid] bash

若要手動重啓Jenkins,能夠執行如下命令:docker restart [containerid]

Jenkins基本配置

經過以上步驟,若是正常走到這裏,能夠經過如下地址訪問http://121.41.16.183:9090/,ip地址爲服務器的地址。

解鎖jenkins

輸入一下命令獲取解鎖的token,docker exec kmywjenkins cat /var/jenkins_home/secrets/initialAdminPassword

在瀏覽器中輸入對應的token以解鎖:

Unlock Jenkins page

建立憑據

鏈接git倉庫,ssh鏈接服務器均須要相應的憑據,能夠在憑據管理中先建立好,而後須要使用的地方直接選擇憑據便可。這裏以鏈接git、ssh須要的憑據爲例:

  1. 我司用得版本管理工具是gitte,以gitte爲例,其它版本管理工具配置也同樣

​ 類型選擇Username with password

​ 用戶名密碼爲登陸gitte的帳號密碼

​ ID是憑據的惟一標識,可自定義,後面在JenkinsFile中經過ID去引用憑據

image-20201015110502890

配置後的結果

image-20201015110924897

  1. ssh鏈接服務器時須要密鑰,咱們先在服務器生成一對公私鑰,而後複製私鑰,填入便可

    類型選擇SSH Username with private key

    Username是鏈接服務器的用戶名,如jenkins

    Private Key項選中Enter directly,點擊Add,粘貼剛複製的私鑰

image-20201015111417561

配置後的結果

image-20201015111823495

建立一個多分支流水線

以前的Jenkins任務是FreeStyle的方式建立的,這種方式不夠靈活,界面也不夠清爽,這裏選擇使用聲明式流水線方式(Declarative Pipeline)建立,能夠多分支獨立構建,便於之後的擴展。

咱們這裏使用 BlueOcean 這種方式來完成此處 CI/CD 的工做,BlueOcean 是 Jenkins 團隊從用戶體驗角度出發,專爲 Jenkins Pipeline 從新設計的一套 UI 界面,仍然兼容之前的 fressstyle 類型的 job,BlueOcean 具備如下的一些特性:

  • 連續交付(CD)Pipeline 的複雜可視化,容許快速直觀的瞭解 Pipeline 的狀態
  • 能夠經過 Pipeline 編輯器直觀的建立 Pipeline
  • 須要干預或者出現問題時快速定位,BlueOcean 顯示了 Pipeline 須要注意的地方,便於異常處理和提升生產力
  • 用於分支和拉取請求的本地集成能夠在 GitHub 或者 Bitbucket 中與其餘人進行代碼協做時最大限度提升開發人員的生產力。

若是安裝的是jenkinsci/blueocean鏡像,默認是已經集成了BlueOcean,沒有的可前往插件管理安裝對應的插件。

install BlueOcean

點擊打開Blue Ocean,能夠看到已經建立好的兩個流水線,分別是前端和後臺,須要用到不一樣的工具,在後面會提到,如何建立流水線

image-20201015113510340

點擊建立流水線

image-20201015113846536

我司用的是gitte,因此選擇Git,而後填入要鏈接的倉庫地址,須要鏈接到Git倉庫的憑據,咱們以前已經建立好了,直接選中便可,若是未建立,在下面的表單直接編輯便可,最後點擊建立流水線

image-20201015114115860

到這裏咱們就建立了一個多分支流水線,Jenkins會掃描倉庫,帶有JenkinsFile的分支會被檢測出來,JenkinFile是多分支流水線的配置文件,使用的是Groovy語法,能夠直接點擊建立流水線,Jenkins會自動爲你的項目建立一個JenkinsFile

image-20201015114453409

如今能夠可視化地編輯想要執行的階段及步驟,這裏加了一個打包的階段,裏面有個步驟是提示開始打包,點擊保存

image-20201015115159132

填入提交信息,點擊Save & Run,會講JenkinsFile上傳到git,並根據JenkinsFile執行一個構建任務,目前的構建步驟只有一個,是提示開始打包

image-20201015115411207

我這裏不知道爲何會卡在這個地方不動,因此我在vscode直接建立並編輯JenkinsFile,這種方式更靈活,我更推薦這種方式,下面我會先簡單介紹下JeninsFile的基礎語法,僅包含本項目用到的,對於中小企業的構建需求,基本夠用了。

JenkinsFile基礎語法

只需先了解大體的語法,具體的用法會在後面說明

// 前端項目JenkinsFile配置,後端項目配置稍有不一樣,後面會區分說明
pipeline {
  agent any
  environment {
    HOST_TEST = 'root@121.41.16.183'
    HOST_ONLINE = 'jenkins@39.101.219.110'
    SOURCE_DIR = 'dist/*'
    TARGET_DIR = '/data/www/kuaimen-yunying-front'
  }
  parameters {
    choice(
      description: '你須要選擇哪一個環境進行部署 ?',
      name: 'env',
      choices: ['測試環境', '線上環境']
    )    
    string(name: 'update', defaultValue: '', description: '本次更新內容?')      
  }
  triggers {
    GenericTrigger(
     genericVariables: [
      [key: 'ref', value: '$.ref']
     ],
     causeString: 'Triggered on $ref',
     token: 'runcenter-front-q1w2e3r4t5',
     tokenCredentialId: '',
     printContributedVariables: true,
     printPostContent: true,
     silentResponse: false,
     regexpFilterText: '$ref',
     regexpFilterExpression: 'refs/heads/' + BRANCH_NAME
    )
  } 
  stages {
    stage('獲取git commit message') {
     steps {
       script {
         env.GIT_COMMIT_MSG = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
       }
     }
  }
    
    stage('打包') {
      steps {
        nodejs('nodejs-12.16') {
          echo '開始安裝依賴'
          sh 'yarn'
          echo '開始打包'
          sh 'yarn run build'
        }
      }
    }

    stage('部署') {
      when {
        expression {
          params.env == '測試環境'
        }
      }
      steps {
        sshagent(credentials: ['km-test2']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} uname -a"
          sh "scp -r ${SOURCE_DIR} ${HOST_TEST}:${TARGET_DIR}"
          sh 'echo "部署成功~"'
        }
      }
    }

    stage('發佈') {
      when {
        expression {
          params.env == '線上環境'
        }
      }
      steps {
        sshagent(credentials: ['km-online']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} uname -a"
          sh "scp -r ${SOURCE_DIR} ${HOST_ONLINE}:${TARGET_DIR}"
          sh 'echo "發佈成功~"'
        }
      }
    }
  }

  post {
    success {
      dingtalk (
        robot: '77d4c82d-3794-4583-bc7f-556902fee6b0',
        type: 'MARKDOWN',
        atAll: true,
        title: '你有新的消息,請注意查收',
        text:[
          '# 運營管理系統發佈通知',
          '---',
          '#### **所屬:前端**',
          "#### **構建任務:${env.BUILD_DISPLAY_NAME}**",
          "#### **Git commit:${env.GIT_COMMIT_MSG}**",
          "#### **本次更新內容:${params.update}**",
          "#### **部署環境:${params.env}**",
          '#### **構建結果:成功**'
        ]
      )
    }
  }
}
複製代碼

pipeline 必須在最外層

agent 定義了在哪一個環境裏執行,默認any

stages 階段,標識構建流程的標籤塊,子節點是stage

steps 執行步驟

post 全部階段執行完成後執行一些邏輯

when 能夠控制該階段是否執行

environment 環境變量,在這裏定義的變量,JenkinsFile的任何地方均可以訪問

tools 項目使用到的構建工具,聲明系統配置中已經定義好的工具,如maven

parameters 定義參數,能夠提供用戶輸入或者選擇

post 構建結束後會執行這裏,有successfailuresuccess,本示例將在success(構建成功時)發起釘釘通知

CI/CD流程

因爲我司的技術團隊較小,CI/CD流程就沒那麼複雜,不會包含代碼檢查、自動化測試、Code Review等流程,我將簡要說明我所搭建的前端與後端CI/CD流程以及爲何這麼搭建。

前端

提供兩種構建方式,一種是代碼上傳自動構建,一種是參數化構建,可選擇部署到測試環境仍是線上環境。

自動構建默認部署到測試環境,因爲線上環境很重要,自動化構建會有必定風險,因此須要人工干預選擇參數進行構建。

  1. 提交代碼到master,自動觸發構建 若是是參數化構建,這一步是手動選擇要構建的環境,而後開始構建
  2. 安裝依賴
  3. 打包
  4. 上傳到服務器
  5. 若是成功發起釘釘通知

後端

後端的全部項目都是放在一個git倉庫中,因此就沒有作自動構建

  1. 參數化構建 可選擇要構建的環境、打包的項目、是否須要全量打包
  2. 清除舊數據
  3. 打包
  4. 上傳到服務器
  5. 殺掉相應的進程
  6. 啓動相應的進程
  7. 若是成功發起釘釘通知

接下來就每一步做詳細說明,以及可能遇到的坑

自動觸發構建

什麼是自動觸發構建

當咱們提交新的代碼到git倉庫,Jenkins就會自動開始構建已經配置好的該項目的任務

原理

在git倉庫配置一個Jenkins服務器的webhook地址,當git倉庫有變更時會請求這個地址,Jenkins就能收到通知而後開始構建任務

配置

  1. 咱們須要先安裝一個插件Multibranch Scan Webhook Trigger,可進入插件管理搜索進行安裝

  2. 進入項目的配置界面,勾選Scan by webhook,填入自定義token,須要確保token的惟一性,不會與其它項目的衝突 image-20201015145053900

  3. 過濾分支

這是一個多分支流水線,Jenkins默認會檢出全部包含Jenkinsfile的分支,若是配置了webhook,就會自動觸發相應分支的構建任務;有時候咱們只想master發生變化後纔去構建任務,這時就用到了過濾分支的配置,進入項目配置,在分支源git項找到add按鈕並點擊

image-20201021161744848

選擇根據名稱過濾(支持通配符),或者你能夠選擇根據名稱過濾(支持正則表達式),效果同樣,只是過濾格式不太同樣,我這裏在相應的地方填入master,即只檢索master分支,這樣就達到咱們想要的效果了。

image-20201021161836184

  1. 進入遠端倉庫(我這裏是Gitte),點擊Webhooks,接着點擊添加 WebHook image-20201015145511825

  2. 填入URL,IP地址爲Jenkins部署的服務器,token爲咱們剛設置的,/multibranch-webhook-trigger/invoke 是固定地址,點擊添加 image-20201015145820987

  3. 會自動發起一個請求,即咱們剛填寫的,如相應以下則表示配置成功,相應的構建任務也會自動執行 image-20201015150206085

自動化打包

前端

使用了yarn進行安裝依賴及打包,須要先配置nodejs環境

  1. 進入插件管理搜索nodejs進行安裝 image-20201015152417028
  2. 進入全局工具配置,新增以下配置,別名能夠自定義,建議格式爲nodejs-版本號,該項目用的是yarn,因此在Global npm package to install,加入了配置項,構建的時候會自動安裝yarn,若是是npm能夠忽略該配置 image-20201015152605860
  3. Jenkinsfile配置 前端的比較簡單
pipeline {
   stage('打包') {
      steps {
        // 執行環境,nodejs-12.16是咱們剛配置的別名,還有一種方式是在agent中配置執行環境,在tools中配置使用的包,感興趣的能夠自行研究
        nodejs('nodejs-12.16') {
          echo '開始安裝依賴'
          sh 'yarn'
          echo '開始打包'
          sh 'yarn run build'
        }
      }
    }
}
複製代碼

後端(Java)

pipeline {
  tools {
        maven 'Maven3.6.3'
    }
  parameters {
        // 提供要部署的服務器選項
        choice(
            description: '你須要選擇哪一個環境進行部署 ?',
            name: 'env',
            choices: ['測試環境', '線上環境']
        ) 
        // 提供構建的模塊選項
        choice(
            description: '你須要選擇哪一個模塊進行構建 ?',
            name: 'moduleName',
            choices: ['kuaimen-contract', 'kuaimen-core', 'kuaimen-eureka-server', 'kuaimen-manage', 'kuaimen-member', 'kuaimen-order', 'kuaimen-shop', 'tiemuzhen-manage']
        )   
        booleanParam(name: 'isAll', defaultValue: false, description: '是否須要全量(包含clean && build)')     
        string(name: 'update', defaultValue: '', description: '本次更新內容?')    
    }
  
  stages {
    stage('全量清除舊數據...') {
            when {
                expression {
                    params.isAll == true
                }
            }
            steps {
                echo "開始全量清除"      
                sh "mvn package clean -Dmaven.test.skip=true"
            }
        }
        stage('全量打包應用') {
            when {
                expression {
                    params.isAll == true
                }
            }
            steps {
                echo "開始全量打包"   
                sh "mvn package -Dmaven.test.skip=true"
                echo '打包成功'
            }
        }
        stage('清除舊數據...') {
            when {
                expression {
                    params.isAll == false
                }
            }
            steps {
                echo "開始清除${params.moduleName}模塊"      
                sh "cd ${params.moduleName} && mvn package clean -Dmaven.test.skip=true"
            }
        }
        stage('打包應用') {
            when {
                expression {
                    params.isAll == false
                }
            }
            steps {
                echo "開始打包${params.moduleName}模塊"   
                sh "cd ${params.moduleName} && mvn package -Dmaven.test.skip=true"
                echo '打包成功'
            }
        }
  }
}
複製代碼

parameters

parameters中主要是提供參數化構建的選項,在其它地方能夠經過"${params.isAll}"這種形式拿到用戶的交互信息,配置後效果以下:

image-20201015155415457

when>expression表達式中的參數若是未true,則執行,反之跳過該stage

mvn在系統配置中默認就已經提供了該環境,進入系統全局工具配置,添加以下配置(相似nodejs)

image-20201015160714295

這種方式引用

tools {
  maven 'Maven3.6.3'
}
複製代碼

自動化部署

前端

pipeline {
  agent any
  environment {
    HOST_TEST = 'root@121.41.16.183'
    HOST_ONLINE = 'jenkins@39.101.219.110'
    SOURCE_DIR = 'dist/*'
    TARGET_DIR = '/data/www/kuaimen-yunying-front'
  }
   stage('部署') {
      when {
        expression {
          params.env == '測試環境'
        }
      }
      steps {
        sshagent(credentials: ['km-test2']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} uname -a"
          // 將打包好的文件上傳到服務器
          sh "scp -r ${SOURCE_DIR} ${HOST_TEST}:${TARGET_DIR}"
          sh 'echo "部署成功~"'
        }
      }
    }

    stage('發佈') {
      when {
        expression {
          params.env == '線上環境'
        }
      }
      steps {
        sshagent(credentials: ['km-online']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} uname -a"
          sh "scp -r ${SOURCE_DIR} ${HOST_ONLINE}:${TARGET_DIR}"
          sh 'echo "發佈成功~"'
        }
      }
    }
  }
}
複製代碼

environment定了全局變量,在其它地方可直接引用

sshagent用於鏈接服務器,須要先安裝插件ssh-agentcredentials是鏈接服務器的憑據ID,咱們在一開始已經教你們建立好了

後端

pipeline {
  agent any
  environment {
        HOST_TEST = 'root@121.41.16.183'
        TARGET_DIR = '/data/www/kuaimen-auto'
        HOST_ONLINE = 'jenkins@39.101.219.110'
  }
  
  tools {
        maven 'Maven3.6.3'
  }
  
  stage('部署應用') {
            when {
                expression {
                    params.env == '測試環境'
                }
            }
            steps {
                echo "開始部署${params.moduleName}模塊"   
                sshagent(credentials: ['km-test2']) {
                    sh "ssh -v -o StrictHostKeyChecking=no ${HOST_TEST} uname -a"
                    // 將打包後的文件上傳到服務器
                    sh "cd ${params.moduleName}/target && scp *.jar ${HOST_TEST}:${TARGET_DIR}/${params.moduleName}"
                    // 匹配出該Java進程而後殺掉
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} \"uname;ps -ef | egrep ${params.moduleName}.*.jar | egrep -v grep | awk '{print \\\$2}' | xargs -r sudo kill -9\""
                    // 啓動該進程
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} \"nohup /data/apps/jdk1.8/bin/java -jar ${TARGET_DIR}/${params.moduleName}/${params.moduleName}-0.0.1-SNAPSHOT.jar --spring.profiles.active=test >/dev/null 2>&1 &\""
                    sh 'echo "部署成功~"'
                }
                echo '部署成功'
            }
        }
        stage('發佈應用') {
            when {
                expression {
                    params.env == '線上環境'
                }
            }
            steps {
                echo "開始發佈${params.moduleName}模塊"   
                sshagent(credentials: ['km-online']) {
                    sh "ssh -v -o StrictHostKeyChecking=no ${HOST_ONLINE} uname -a"
                    sh "cd ${params.moduleName}/target && scp *.jar ${HOST_ONLINE}:${TARGET_DIR}/${params.moduleName}"
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} \"uname;ps -ef | egrep ${params.moduleName}.*.jar | egrep -v grep | awk '{print \\\$2}' | xargs -r sudo kill -9\""
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} \"nohup /data/apps/jdk1.8/bin/java -jar ${TARGET_DIR}/${params.moduleName}/${params.moduleName}-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev >/dev/null 2>&1 &\""
                    sh 'echo "發佈成功~"'
                }
                echo '發佈成功'
            }
        }
}
複製代碼

須要注意的是,在匹配進程的那段shell中的awk '{print \\\$2}'$符號須要用三個反斜線進行轉義,否則會沒法執行成功,這裏曾卡了很久,但願大家別踩坑了

部署完成後發起通知

咱們這裏使用釘釘發起通知,主要原理是在釘釘羣建立一個webhook機器人,而後把webhook的地址填入DingTalk插件的配置項,最後在JenkinsFile中進行以下配置便可:

pipeline {
  stage('獲取git commit message') {
     steps {
       script {
         // 將獲取到的git commit賦值給GIT_COMMIT_MSG
         env.GIT_COMMIT_MSG = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
       }
     }
  }
  post {
     success {
            dingtalk (
                robot: '77d4c82d-3794-4583-bc7f-556902fee6b0',
                type: 'MARKDOWN',
                atAll: true,
                title: '你有新的消息,請注意查收',
                text:[
                '# 運營管理系統發佈通知',
                '---',
                '#### **所屬:後端**',
                "#### **構建任務:${env.BUILD_DISPLAY_NAME}**",
                "#### **本次更新內容:${params.update}**",
                "#### **部署環境:${params.env}**",
                '#### **構建結果:成功**'
                ]
            )
        }
  }
}
複製代碼

GIT_COMMIT這個是Jenkins系統全局變量,得到的是git commit ID,而後經過它拿到具體的提交信息,並賦值給env.GIT_COMMIT_MSG,全局變量能夠經過這種方式訪問env.BUILD_DISPLAY_NAME

robot爲機器人ID,在系統配置中添加以下配置項

webhook在建立完機器人的時候可以拿到

image-20201015162449026

如何建立釘釘機器人

點擊羣設置 -> 智能羣助手

image-20201015162819482

選擇自定義機器人,配置完成後就能夠看到webhook的地址了

image-20201015162922118

開始構建

通過上面的配置,咱們已經完成了前端、後臺的自動化構建配置,接下來再說明一下分別是如何觸發構建的

前端

  1. 提交代碼到master,會自動執行構建任務,並部署到測試環境,部署成功後會在釘釘羣發起提醒
  2. 參數化構建 image-20201015163313512 點擊Build with Parameters,選擇相應的參數進行構建,線上環境必須經過這種方式,保證必定的安全性 image-20201015163435799

後端

後端只配置了參數化構建,緣由前面已經說了,選擇要構建的環境、模塊進行構建

image-20201015163643880

使用Blue Ocean構建(推薦)

點擊打開Blue Ocean

image-20201015163808172

選擇要構建的分支

image-20201015163855221

彈出參數選擇,這和Build with Parameters差很少,可是界面更好看,更清爽了,選擇後點擊Run便可開始構建

image-20201015164023218

構建結果,很直觀,根據顏色能夠判斷構建成功了,若是失敗了是紅色

image-20201015164227693

回滾

在Blue Ocean的活動欄能夠看到歷史構建,點擊以下位置的按鈕能夠從新構建該歷史項,即回滾

image-20201015164435230

寫在最後

到這裏終於告一段落了,雖然折騰了很多時間,可是將公司的工程化流程完善了仍是有點小小的成就感的,之後能夠愉快得寫代碼了,自動化的事情就交給Jenkins了。

將這個記錄下來一個是方便之後隨時查閱,還有一個是但願能讓朋友們少踩些坑,完~

附錄

Jenkins官方文檔

BlueOcean實戰多分支pipeline構建(Jenkins)

Complete Jenkins Pipeline Tutorial for Beginners [FREE]

實戰筆記:Jenkins打造強大的前端自動化工做流

Jenkins:添加SSH全局憑證

釘釘通知系列Jenkins發佈後自動通知

使用 Generic Webhook Trigger 觸發 Jenkins 多分支流水線自動化構建

Jenkins pipeline單引號、雙引號和轉義字符

Jenkins Blue Ocean 的使用

How to Execute Linux Commands on Remote System over SSH

相關文章
相關標籤/搜索