使用 Jenkins 構建 CI-CD 之多分支流水線指北(實戰)
手把手帶你一起從 0 到 1 搭建一個企業級的自動化構建流程 網上完整的關於多分支流水線的配置很少,希望這篇不短的文章能給你帶來幫助
簡介
- 從 0 到 1 打造前後端自動化工作流
- 企業級的實踐筆記
- 最佳實踐(可在此基礎逐步完善)
緣起
由於公司的 Jenkins 配置沒有部署成功的通知,在我學了幾天的 Jenkins 後終於是對公司的 Jenkins 配置下手了,結果我剛裝完dingtalk
插件自動重啓後,發現之前主管配置的構建項目數據都丟失了,害,正好給了我練手的機會,於是就有了以下從 0 到 1 的辛酸歷程。
在 Docker 中安裝並運行 Jenkins
這裏假設你的服務器已經裝好了 docker
使用的鏡像是jenkinsci/blueocean
,這是一個 jenkins 的穩定及持續維護的鏡像源,本身就集成了 Blue Ocean 等使用插件,非常方便。
拉取鏡像
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
複製代碼
參數解釋:
-idt
以交互的方式、新建一個模擬終端運行容器
--name
容器的別名
-p
指定容器映射宿主機的端口 -> 宿主機端口: 容器端口
-v jenkins-data:/var/jenkins_home
Jenkins 容器在工作的時候,如果要執行 Docker 的命令(例如 docker ps、docker run 等),需要有個途徑能連接到宿主機的 docker 服務,此參數就是用來建立容器和宿主機 docker 服務的連接的
-v /data/web-data/docker.sock:/var/run/docker.sock
將該容器的數據保留在宿主機的目錄,這樣即使容器崩潰了,裏面的配置和任務都不會丟失
需要注意的是,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 以解鎖:
創建憑據
連接 git 倉庫,ssh 連接服務器均需要相應的憑據,可以在憑據管理中先創建好,然後需要使用的地方直接選擇憑據即可。這裏以連接 git、ssh 需要的憑據爲例:
- 我司用得版本管理工具是 gitte,以 gitte 爲例,其它版本管理工具配置也一樣
類型選擇Username with password
用戶名密碼爲登錄 gitte 的賬號密碼
ID 是憑據的唯一標識,可自定義,後面在 JenkinsFile 中通過 ID 去引用憑據
配置後的結果
-
ssh 連接服務器時需要密鑰,我們先在服務器生成一對公私鑰,然後複製私鑰,填入即可
類型選擇
SSH Username with private key
Username
是連接服務器的用戶名,如jenkins
在
Private Key
項選中Enter directly
,點擊 Add,粘貼剛複製的私鑰
配置後的結果
創建一個多分支流水線
之前的 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,沒有的可前往插件管理安裝對應的插件。
點擊打開 Blue Ocean,可以看到已經創建好的兩個流水線,分別是前端和後臺,需要用到不同的工具,在後面會提到,如何創建流水線
點擊創建流水線
我司用的是 gitte,所以選擇 Git,然後填入要連接的倉庫地址,需要連接到 Git 倉庫的憑據,我們之前已經創建好了,直接選中即可,如果未創建,在下面的表單直接編輯即可,最後點擊創建流水線
到這裏我們就創建了一個多分支流水線,Jenkins 會掃描倉庫,帶有 JenkinsFile 的分支會被檢測出來,JenkinFile 是多分支流水線的配置文件,使用的是 Groovy 語法,可以直接點擊創建流水線,Jenkins 會自動爲你的項目創建一個 JenkinsFile
現在可以可視化地編輯想要執行的階段及步驟,這裏加了一個打包的階段,裏面有個步驟是提示開始打包
,點擊保存
填入提交信息,點擊Save & Run
,會講 JenkinsFile 上傳到 git,並根據 JenkinsFile 執行一個構建任務,目前的構建步驟只有一個,是提示開始打包
我這裏不知道爲什麼會卡在這個地方不動,所以我在 vscode 直接創建並編輯 JenkinsFile,這種方式更靈活,我更推薦這種方式,下面我會先簡單介紹下 JeninsFile 的基礎語法,僅包含本項目用到的,對於中小企業的構建需求,基本夠用了。
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
構建結束後會執行這裏,有success
、failure
、success
,本示例將在success
(構建成功時)發起釘釘通知
CI/CD 流程
由於我司的技術團隊較小,CI/CD 流程就沒那麼複雜,不會包含代碼檢查、自動化測試、Code Review 等流程,我將簡要說明我所搭建的前端與後端 CI/CD 流程以及爲什麼這麼搭建。
前端
提供兩種構建方式,一種是代碼上傳自動構建,一種是參數化構建,可選擇部署到測試環境還是線上環境。
自動構建默認部署到測試環境,由於線上環境很重要,自動化構建會有一定風險,所以需要人工干預選擇參數進行構建。
- 提交代碼到 master,自動觸發構建 如果是參數化構建,這一步是手動選擇要構建的環境,然後開始構建
- 安裝依賴
- 打包
- 上傳到服務器
- 如果成功發起釘釘通知
後端
後端的所有項目都是放在一個 git 倉庫中,所以就沒有做自動構建
- 參數化構建 可選擇要構建的環境、打包的項目、是否需要全量打包
- 清除舊數據
- 打包
- 上傳到服務器
- 殺掉相應的進程
- 啓動相應的進程
- 如果成功發起釘釘通知
接下來就每一步作詳細說明,以及可能遇到的坑
自動觸發構建
什麼是自動觸發構建
當我們提交新的代碼到 git 倉庫,Jenkins 就會自動開始構建已經配置好的該項目的任務
原理
在 git 倉庫配置一個 Jenkins 服務器的 webhook 地址,當 git 倉庫有變動時會請求這個地址,Jenkins 就能收到通知然後開始構建任務
配置
-
我們需要先安裝一個插件 Multibranch Scan Webhook Trigger,可進入插件管理搜索進行安裝
-
進入項目的配置界面,勾選 Scan by webhook,填入自定義 token,需要確保 token 的唯一性,不會與其它項目的衝突
-
過濾分支
這是一個多分支流水線,Jenkins 默認會檢出所有包含 Jenkinsfile 的分支,如果配置了 webhook,就會自動觸發相應分支的構建任務;有時候我們只想 master 發生變化後纔去構建任務,這時就用到了過濾分支的配置,進入項目配置,在分支源git
項找到add
按鈕並點擊
選擇根據名稱過濾(支持通配符)
,或者你可以選擇根據名稱過濾(支持正則表達式)
,效果一樣,只是過濾格式不太一樣,我這裏在相應的地方填入master
,即只檢索master
分支,這樣就達到我們想要的效果了。
-
進入遠端倉庫(我這裏是 Gitte),點擊 Webhooks,接着點擊添加 WebHook
-
填入 URL,IP 地址爲 Jenkins 部署的服務器,token 爲我們剛設置的,
/multibranch-webhook-trigger/invoke
是固定地址,點擊添加 -
會自動發起一個請求,即我們剛填寫的,如相應如下則表示配置成功,相應的構建任務也會自動執行
自動化打包
前端
使用了yarn
進行安裝依賴及打包,需要先配置nodejs環境
- 進入插件管理搜索
nodejs
進行安裝 - 進入全局工具配置,新增如下配置,別名可以自定義,建議格式爲
nodejs-版本號
,該項目用的是yarn
,所以在Global npm package to install
,加入了配置項,構建的時候會自動安裝yarn
,如果是 npm 可以忽略該配置 - Jenkinsfile 配置 前端的比較簡單
pipeline {
stage('打包') {
steps {
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}"
這種形式拿到用戶的交互信息,配置後效果如下:
when>expression
表達式中的參數如果未true
,則執行,反之跳過該stage
mvn
在系統配置中默認就已經提供了該環境,進入系統全局工具配置,添加如下配置(類似 nodejs)
這種方式引用
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-agent,credentials
是連接服務器的憑據 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}"
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 {
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 在創建完機器人的時候能夠拿到
如何創建釘釘機器人
點擊羣設置 -> 智能羣助手
選擇自定義機器人,配置完成後就可以看到 webhook 的地址了
開始構建
經過上面的配置,我們已經完成了前端、後臺的自動化構建配置,接下來再說明一下分別是如何觸發構建的
前端
- 提交代碼到 master,會自動執行構建任務,並部署到測試環境,部署成功後會在釘釘羣發起提醒
- 參數化構建
點擊 Build with Parameters
,選擇相應的參數進行構建,線上環境必須通過這種方式,保證一定的安全性
後端
後端只配置了參數化構建,原因前面已經說了,選擇要構建的環境、模塊進行構建
使用 Blue Ocean 構建(推薦)
點擊打開 Blue Ocean
選擇要構建的分支
彈出參數選擇,這和Build with Parameters
差不多,但是界面更好看,更清爽了,選擇後點擊Run
即可開始構建
構建結果,很直觀,根據顏色可以判斷構建成功了,如果失敗了是紅色
回滾
在 Blue Ocean 的活動欄可以看到歷史構建,點擊如下位置的按鈕可以重新構建該歷史項,即回滾
寫在最後
到這裏終於告一段落了,雖然折騰了不少時間,但是將公司的工程化流程完善了還是有點小小的成就感的,以後可以愉快得寫代碼了,自動化的事情就交給 Jenkins 了。
將這個記錄下來一個是方便以後隨時查閱,還有一個是希望能讓朋友們少踩些坑,完~
附錄
BlueOcean 實戰多分支 pipeline 構建 (Jenkins)
Complete Jenkins Pipeline Tutorial for Beginners [FREE]
使用 Generic Webhook Trigger 觸發 Jenkins 多分支流水線自動化構建
How to Execute Linux Commands on Remote System over SSH
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://juejin.cn/post/6883769774564884488