從繁瑣到簡(jiǎn)潔的Jenkins?Pipeline腳本優(yōu)化實(shí)踐
引言
在持續(xù)集成的過(guò)程中,Jenkins Pipeline 是非常關(guān)鍵的一環(huán)。它定義了如何自動(dòng)編譯、測(cè)試和部署代碼。隨著項(xiàng)目的不斷發(fā)展,Pipeline 的復(fù)雜性也在不斷上升,這就需要我們持續(xù)優(yōu)化 Pipeline 腳本,以提高代碼的可讀性和維護(hù)性。本文將介紹一次從繁瑣Pipeline腳本到精簡(jiǎn)Pipeline腳本的轉(zhuǎn)化過(guò)程,以及這種轉(zhuǎn)化所帶來(lái)的好處。
原始的 Pipeline 腳本:

注: loader其實(shí)已經(jīng)廢棄了
pipeline {
agent { label "build01" }
stages {
stage("GetCode"){
agent { label "build01" }
steps{
script{
println("下載代碼 --> 分支: ${env.branchName}")
checkout([$class: 'GitSCM', branches: [[name: "${env.branchName}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true]],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: 'xxxx',
url: "${env.gitHttpURL}"]]])
}
}
}
stage('docker build dataloader-game-ucenter') {
agent { label "build01" }
when {
environment name: 'dataloader', value: 'true'
}
steps {
sh ''' cd dataloader
docker build --build-arg NODE_ENV=game-ucenter -t swr.cn-north-4.myhuaweicloud.com/master-metaspace/dataloader-game-ucenter:$data .'''
withCredentials([usernamePassword(credentialsId: 'hw-registry', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
sh "docker login -u ${dockerUser} -p ${dockerPassword} swr.cn-north-4.myhuaweicloud.com"
sh "docker push swr.cn-north-4.myhuaweicloud.com/master-metaspace/dataloader-game-ucenter:$data"
}
}
}
stage('docker build datawriter-game-ucenter') {
agent { label "build01" }
when {
environment name: 'datawriter', value: 'true'
}
steps {
sh ''' cd datawriter-game-ucenter
docker build --build-arg NODE_ENV=game-ucenter -t swr.cn-north-4.myhuaweicloud.com/master-metaspace/datawriter-game-ucenter:$data .'''
withCredentials([usernamePassword(credentialsId: 'hw-registry', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
sh "docker login -u ${dockerUser} -p ${dockerPassword} swr.cn-north-4.myhuaweicloud.com"
sh "docker push swr.cn-north-4.myhuaweicloud.com/master-metaspace/datawriter-game-ucenter:$data"
}
}
}
stage('docker build game-ucenter') {
agent { label "build01" }
when {
environment name: 'game-ucenter', value: 'true'
}
steps {
sh ''' cd game-ucenter
docker build --build-arg NODE_ENV=game-ucenter -t swr.cn-north-4.myhuaweicloud.com/master-metaspace/game-ucenter:$data .'''
withCredentials([usernamePassword(credentialsId: 'hw-registry', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
sh "docker login -u ${dockerUser} -p ${dockerPassword} swr.cn-north-4.myhuaweicloud.com"
sh "docker push swr.cn-north-4.myhuaweicloud.com/master-metaspace/game-ucenter:$data"
}
}
}
stage('develop') {
parallel {
stage("develop datawriter-game-ucenter") {
when {
environment name: 'datawriter-game-ucenter', value: 'true'
}
agent { label "huaweiyun-xx" }
steps {
sh "sed -e 's/{data}/$data/g' /home/jenkins/workspace/yaml/master-metaspace/datawriter-game-ucenter.tpl > /home/jenkins/workspace/yaml/master-metaspace/datawriter-game-ucenter.yaml"
sh "sudo kubectl apply -f /home/jenkins/workspace/yaml/master-metaspace/datawriter-game-ucenter.yaml --namespace=master-metaspace --context=master"
}
}
stage("develop dataloader-game-ucenter") {
when {
environment name: 'dataloader', value: 'true'
}
agent { label "huaweiyun-xx" }
steps {
sh "sed -e 's/{data}/$data/g' /home/jenkins/workspace/yaml/master-metaspace/dataloader-game-ucenter.tpl > /home/jenkins/workspace/yaml/master-metaspace/dataloader-game-ucenter.yaml"
sh "sudo kubectl apply -f /home/jenkins/workspace/yaml/master-metaspace/dataloader-game-ucenter.yaml --namespace=master-metaspace --context=master"
}
}
stage("develop game-ucenter") {
when {
environment name: 'game-ucenter', value: 'true'
}
agent { label "huaweiyun-xx" }
steps {
sh "sed -e 's/{data}/$data/g' /home/jenkins/workspace/yaml/master-metaspace/game-ucenter.tpl > /home/jenkins/workspace/yaml/master-metaspace/game-ucenter.yaml"
sh "sudo kubectl apply -f /home/jenkins/workspace/yaml/master-metaspace/game-ucenter.yaml --namespace=master-metaspace --context=master"
}
}
}
}
}
}在優(yōu)化之前,我們的 Jenkins Pipeline 腳本中包含了多個(gè)獨(dú)立定義的 stage,每個(gè) stage 中都有重復(fù)的結(jié)構(gòu)和指令:
- 明確指定了 agent。
- 在每個(gè) stage 的 steps 中,都使用了類似的腳本來(lái)操作 git、構(gòu)建 docker 鏡像和部署到 Kubernetes。
- 使用了冗長(zhǎng)的 shell 腳本來(lái)綁定變量和執(zhí)行部署。
這種寫(xiě)法雖然直觀,但存在以下問(wèn)題:
- 代碼冗余:相同的任務(wù)(例如構(gòu)建與部署)重復(fù)編寫(xiě)了多次。
- 低效的修改:一旦需要調(diào)整構(gòu)建或部署流程,需要同時(shí)修改多個(gè)類似的代碼段。
- 可讀性差:過(guò)于細(xì)碎的腳本內(nèi)容使得新成員閱讀和理解這個(gè) Pipeline 變得困難。
轉(zhuǎn)化的原因與過(guò)程
簡(jiǎn)化 agent 的聲明
pipeline {
agent none // Use none at the top level, each stage will define its own agent.
}在原始腳本中,每個(gè) stage 都重復(fù)指定相同的 agent,這是不必要的。轉(zhuǎn)化后,我們?cè)?pipeline 的頂層使用 agent none 聲明,表示不在這一層級(jí)指定執(zhí)行者,這樣各個(gè) stage 就可以根據(jù)需求獨(dú)立地聲明自己的 agent。
將環(huán)境變量統(tǒng)一管理
我們創(chuàng)建一個(gè) environment 部分來(lái)集中定義環(huán)境變量,簡(jiǎn)化了變量的管理,并且當(dāng)需要修改時(shí)只要在一個(gè)地方進(jìn)行調(diào)整即可。
environment {
REGISTRY = "swr.cn-north-4.myhuaweicloud.com/master-metaspace"
KUBE_CONFIG = "--namespace=master-metaspace --context=master"
KUBE_YAML_PATH = "/home/jenkins/workspace/yaml/master-metaspace"
// Assume that 'data' is defined elsewhere or injected as a parameter.
}使用共用方法減少代碼重復(fù)
對(duì)于 Docker 鏡像的構(gòu)建和推送操作,每個(gè)應(yīng)用幾乎執(zhí)行相同的步驟。我們提取了一個(gè)方法 buildAndPushDockerImage 來(lái)代替在每個(gè) stage 里重復(fù)定義的步驟,這樣不但減小了腳本體積,也提升了代碼的復(fù)用性。
def buildAndPushDockerImage(String imageName, String tag) {
sh "cd $imageName && docker build --build-arg NODE_ENV=$imageName -t $REGISTRY/$imageName:$tag ."
withCredentials([usernamePassword(credentialsId: 'hw-registry', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
sh "docker login -u $dockerUser -p $dockerPassword $REGISTRY"
sh "docker push $REGISTRY/$imageName:$tag"
}
}同理,部署到 Kubernetes 的指令也被提取到了 deployToKubernetes 方法中,進(jìn)一步去重。
def deployToKubernetes(String kubernetesComponent) {
String templateFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.tpl"
String outputFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.yaml"
sh "sed -e 's/{data}/$data/g' $templateFile > $outputFile"
sh "sudo kubectl apply -f $outputFile $KUBE_CONFIG"
}結(jié)構(gòu)的并行化
將 Docker 構(gòu)建步驟組合為一個(gè)并行執(zhí)行的 stage,這樣不僅可以縮短整個(gè)流水線的執(zhí)行時(shí)間,還可以使得結(jié)構(gòu)更加清晰。
stage("Docker Builds") {
parallel {
stage('Build dataloader-game-ucenter') {
agent { label "build01" }
when { environment name: 'dataloader', value: 'true' }
steps {
buildAndPushDockerImage("dataloader-game-ucenter", env.data, env.BASE_WORKSPACE)
}
}
stage('Build datawriter-game-ucenter') {
agent { label "build01" }
when { environment name: 'datawriter', value: 'true' }
steps {
buildAndPushDockerImage("datawriter-game-ucenter", env.data, env.BASE_WORKSPACE)
}
}
stage('Build game-ucenter') {
agent { label "build01" }
when { environment name: 'game-ucenter', value: 'true' }
steps {
buildAndPushDockerImage("game-ucenter", env.data, env.BASE_WORKSPACE)
}
}
}
}轉(zhuǎn)化后的好處
轉(zhuǎn)換后的pipeline如下:
pipeline {
agent none // Use none at the top level, each stage will define its own agent.
environment {
REGISTRY = "swr.cn-north-4.myhuaweicloud.com/master-metaspace"
KUBE_CONFIG = "--namespace=master-metaspace --context=master"
KUBE_YAML_PATH = "/home/jenkins/workspace/yaml/master-metaspace"
// Assume that 'data' is defined elsewhere or injected as a parameter.
BASE_WORKSPACE = "xxxxxxx" // 定義一個(gè)基礎(chǔ)工作空間路徑
}
stages {
stage("GetCode") {
agent { label "build01" }
steps {
script {
checkout scm: [
$class: 'GitSCM',
branches: [[name: env.branchName]],
extensions: [[$class: 'CloneOption', depth: 1, noTags: false, shallow: true]],
userRemoteConfigs: [[credentialsId: 'xxxx', url: env.gitHttpURL]]
]
}
}
}
stage("Docker Builds") {
parallel {
stage('Build dataloader-game-ucenter') {
agent { label "build01" }
when { environment name: 'dataloader', value: 'true' }
steps {
buildAndPushDockerImage("dataloader-game-ucenter", env.data, env.BASE_WORKSPACE)
}
}
stage('Build datawriter-game-ucenter') {
agent { label "build01" }
when { environment name: 'datawriter', value: 'true' }
steps {
buildAndPushDockerImage("datawriter-game-ucenter", env.data, env.BASE_WORKSPACE)
}
}
stage('Build game-ucenter') {
agent { label "build01" }
when { environment name: 'game-ucenter', value: 'true' }
steps {
buildAndPushDockerImage("game-ucenter", env.data, env.BASE_WORKSPACE)
}
}
}
}
stage('Development Deployment') {
parallel {
stage("Deploy datawriter-game-ucenter") {
when { environment name: 'datawriter-game-ucenter', value: 'true' }
agent { label "huaweiyun-xx" }
steps {
deployToKubernetes("datawriter-game-ucenter")
}
}
stage("Deploy dataloader-game-ucenter") {
when { environment name: 'dataloader', value: 'true' }
agent { label "huaweiyun-xx" }
steps {
deployToKubernetes("dataloader-game-ucenter")
}
}
stage("Deploy game-ucenter") {
when { environment name: 'game-ucenter', value: 'true' }
agent { label "huaweiyun-xx" }
steps {
deployToKubernetes("game-ucenter")
}
}
}
}
}
}
// Define methods outside pipeline to avoid repetition
def buildAndPushDockerImage(String imageName, String tag, String workspacePath) {
sh "cd ${workspacePath} && echo 'Current directory: \$(pwd)'" // 使用基礎(chǔ)工作空間變量
sh "cd ${workspacePath}/${imageName}&& docker build --build-arg NODE_ENV=$imageName -t $REGISTRY/$imageName:$tag ."
withCredentials([usernamePassword(credentialsId: 'hw-registry', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
sh "docker login -u $dockerUser -p $dockerPassword $REGISTRY"
sh "docker push $REGISTRY/$imageName:$tag"
}
}
def deployToKubernetes(String kubernetesComponent) {
String templateFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.tpl"
String outputFile = "${KUBE_YAML_PATH}/${kubernetesComponent}.yaml"
sh "sed -e 's/{data}/$data/g' $templateFile > $outputFile"
sh "sudo kubectl apply -f $outputFile $KUBE_CONFIG"
}- 代碼結(jié)構(gòu)清晰:每個(gè)階段的職責(zé)更加明確,整個(gè) Pipeline 結(jié)構(gòu)變得簡(jiǎn)單易懂。
- 維護(hù)成本降低:通用操作被封裝成方法,避免了重復(fù)代碼,當(dāng)流程需要修改時(shí),只需在一個(gè)地方更新。
- 執(zhí)行效率提升:使用 parallel 來(lái)并行構(gòu)建 Docker 鏡像降低了整體構(gòu)建時(shí)間。
- 可擴(kuò)展性增加:如果將來(lái)要增加新的鏡像構(gòu)建或者 Kubernetes 部署,只需很少的修改即可。
總結(jié)來(lái)說(shuō),通過(guò)優(yōu)化 Jenkins Pipeline 腳本,我們不僅提高了流程的效率,還大大減小了維護(hù)成本。在持續(xù)集成和持續(xù)部署的實(shí)踐中,擁有一個(gè)簡(jiǎn)潔和高效的 Pipeline 是至關(guān)重要的。
特意強(qiáng)調(diào)的
environment 添加了BASE_WORKSPACE,why?
在Jenkins中,當(dāng)你在一個(gè)Pipeline中使用并行步驟執(zhí)行作業(yè)時(shí),默認(rèn)情況下,每個(gè)并行的分支都會(huì)創(chuàng)建一個(gè)新的工作空間目錄,格式一般為workspace@2、workspace@3等,這樣做是為了避免各個(gè)分支之間產(chǎn)生文件系統(tǒng)上的沖突。
這樣帶來(lái)的后果就是:

構(gòu)建過(guò)程中就會(huì)出現(xiàn)No such file or directory這樣的報(bào)錯(cuò):

所以這里引入了BASE_WORKSPACE
environment {
REGISTRY = "swr.cn-north-4.myhuaweicloud.com/master-metaspace"
KUBE_CONFIG = "--namespace=master-metaspace --context=master"
KUBE_YAML_PATH = "/home/jenkins/workspace/yaml/master-metaspace"
// Assume that 'data' is defined elsewhere or injected as a parameter.
BASE_WORKSPACE = "/home/jenkins2/workspace/test6" // 定義一個(gè)基礎(chǔ)工作空間路徑
}build過(guò)程中引入了:
sh "cd ${workspacePath} && echo 'Current directory: \$(pwd)'"確保并行步驟中引用到正確的工作空間目錄!
注: 以上腳本代碼轉(zhuǎn)換后的pipeline使用chatgpt生成,并多次修訂!
以上就是從繁瑣到簡(jiǎn)潔的Jenkins Pipeline腳本優(yōu)化實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Jenkins Pipeline腳本優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mybatis-plus之如何實(shí)現(xiàn)in嵌套sql
這篇文章主要介紹了mybatis-plus之如何實(shí)現(xiàn)in嵌套sql問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Java實(shí)現(xiàn)京東聯(lián)盟API數(shù)據(jù)獲取功能
這篇文章介紹了Java獲取京東聯(lián)盟API數(shù)據(jù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
Springcloud Config配置中心使用與相關(guān)介紹
springcloud config是一個(gè)解決分布式系統(tǒng)的配置管理方案。它包含了 client和server兩個(gè)部分,server端提供配置文件的存儲(chǔ)、以接口的形式將配置文件的內(nèi)容提供出去,client端通過(guò)接口獲取數(shù)據(jù)、并依據(jù)此數(shù)據(jù)初始化自己的應(yīng)用2022-09-09
Spring中配置Transaction與不配置的區(qū)別及說(shuō)明
這篇文章主要介紹了Spring中配置Transaction與不配置的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
根據(jù)URL下載圖片至客戶端、服務(wù)器的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇根據(jù)URL下載圖片至客戶端、服務(wù)器的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12

