Jenkins Pipeline 脚本优化实践:从繁琐到简洁

news2024/12/23 20:10:27

引言

在持续集成的过程中,Jenkins Pipeline 是非常关键的一环。它定义了如何自动编译、测试和部署代码。随着项目的不断发展,Pipeline 的复杂性也在不断上升,这就需要我们持续优化 Pipeline 脚本,以提高代码的可读性和维护性。本文将介绍一次从繁琐Pipeline脚本到精简Pipeline脚本的转化过程,以及这种转化所带来的好处。

原始的 Pipeline 脚本:

image.png
注: loader其实已经废弃了

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: 'xxxxx',
                        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"
                    }
                }                                  
            }
        }
    }
}

在优化之前,我们的 Jenkins Pipeline 脚本中包含了多个独立定义的 stage,每个 stage 中都有重复的结构和指令:

  1. 明确指定了 agent。
  2. 在每个 stage 的 steps 中,都使用了类似的脚本来操作 git、构建 docker 镜像和部署到 Kubernetes。
  3. 使用了冗长的 shell 脚本来绑定变量和执行部署。

这种写法虽然直观,但存在以下问题:

  • 代码冗余:相同的任务(例如构建与部署)重复编写了多次。
  • 低效的修改:一旦需要调整构建或部署流程,需要同时修改多个类似的代码段。
  • 可读性差:过于细碎的脚本内容使得新成员阅读和理解这个 Pipeline 变得困难。

转化的原因与过程

简化 agent 的声明

pipeline {
    agent none // Use none at the top level, each stage will define its own agent.
    }

在原始脚本中,每个 stage 都重复指定相同的 agent,这是不必要的。转化后,我们在 pipeline 的顶层使用 agent none 声明,表示不在这一层级指定执行者,这样各个 stage 就可以根据需求独立地声明自己的 agent。

将环境变量统一管理

我们创建一个 environment 部分来集中定义环境变量,简化了变量的管理,并且当需要修改时只要在一个地方进行调整即可。

    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.
    }

使用共用方法减少代码重复

对于 Docker 镜像的构建和推送操作,每个应用几乎执行相同的步骤。我们提取了一个方法 buildAndPushDockerImage 来代替在每个 stage 里重复定义的步骤,这样不但减小了脚本体积,也提升了代码的复用性。

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 方法中,进一步去重。

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"
}

结构的并行化

将 Docker 构建步骤组合为一个并行执行的 stage,这样不仅可以缩短整个流水线的执行时间,还可以使得结构更加清晰。

        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)
                    }
                }
            }
        }

转化后的好处

转换后的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" // 定义一个基础工作空间路径
    }

    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)'" // 使用基础工作空间变量
    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"
}
  • 代码结构清晰:每个阶段的职责更加明确,整个 Pipeline 结构变得简单易懂。
  • 维护成本降低:通用操作被封装成方法,避免了重复代码,当流程需要修改时,只需在一个地方更新。
  • 执行效率提升:使用 parallel 来并行构建 Docker 镜像降低了整体构建时间。
  • 可扩展性增加:如果将来要增加新的镜像构建或者 Kubernetes 部署,只需很少的修改即可。

总结来说,通过优化 Jenkins Pipeline 脚本,我们不仅提高了流程的效率,还大大减小了维护成本。在持续集成和持续部署的实践中,拥有一个简洁和高效的 Pipeline 是至关重要的。

特意强调的

environment 添加了BASE_WORKSPACE,why?
在Jenkins中,当你在一个Pipeline中使用并行步骤执行作业时,默认情况下,每个并行的分支都会创建一个新的工作空间目录,格式一般为workspace@2workspace@3等,这样做是为了避免各个分支之间产生文件系统上的冲突。
这样带来的后果就是:
image.png
构建过程中就会出现No such file or directory这样的报错:
image.png
所以这里引入了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" // 定义一个基础工作空间路径
    }

build过程中引入了:

sh "cd ${workspacePath} && echo 'Current directory: \$(pwd)'"

确保并行步骤中引用到正确的工作空间目录!

注: 以上脚本代码转换后的pipeline使用chatgpt生成,并多次修订!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1314991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最好的猫粮排行榜前十名有哪些牌子?盘点好的主食冻干猫粮前五名牌子

现在很多猫咪因为吃了不好的猫粮,出现了各种问题,甚至有的还发生了悲剧,让猫主人心疼又无奈。要解决这个问题,选择一款健康又安全的主食冻干猫粮是非常关键的。优质的主食冻干猫粮不仅在配方上要健康,营养配比也要科学…

【深度学习】注意力机制(二)

本文介绍一些注意力机制的实现,包括EA/MHSA/SK/DA/EPSA。 【深度学习】注意力机制(一) 【深度学习】注意力机制(三) 目录 一、EA(External Attention) 二、Multi Head Self Attention 三、…

26 redis 中 replication/cluster 集群中的主从复制

前言 我们这里首先来看 redis 这边实现比较复杂的 replication集群模式 我们这里主要关注的是 redis 这边的主从同步的相关实现 这边相对比较简单, 我们直接基于 cluster集群模式 进行调试 主从命令同步复制 比如这里 master 是 redis_7002, slave 是 redis_7005 然后 这…

打开软木塞,我们来谈谈葡萄酒泡泡吧

香槟是任何庆祝场合的最佳搭配。从婚礼和生日到单身派对和典型的周五晚上,这款气泡饮料是生活中特别聚会的受欢迎伴侣。 来自云仓酒庄品牌雷盛红酒分享你知道吗,你喜欢喝的那瓶香槟酒可能根本不是香槟,而是汽酒?你不是唯一一个认…

6个超好用的小众图片素材网站,高清、免费,值得收藏~

推荐几个超好用的图片素材网站,免费下载,还可以商用,建议收藏哦~ 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 我推荐过很多次的设计素材网站,除了设计类素材,还有很多自媒体可以用到的高清图片、背景…

最好的猫粮排行榜前十名有哪些品牌?质量好的主食冻干猫粮分享

为什么越来越多人推荐冻干猫粮喂养呢?主食冻干猫粮究竟是最适应猫饮食习惯的喂养方式还是消费陷阱? 作为一个6年的宠物营养师,我以前接触过很多不同品种的猫咪,一只健康又漂亮的猫咪从表面上就能看出来!体型匀称刚好、…

大模型落地,向量数据库到底能做什么?

▼最近直播超级多,预约保你有收获 今晚直播:《AI编程向量数据库架构设计案例实践》 —1— 大模型的“数据局限性” 数据局限对企业做 LLM 大模型带来的影响,可归结为以下三点: 第一点:对数据的管理和运维。随着文本、…

LeetCode(63)旋转链表【链表】【中等】

目录 1.题目2.答案3.提交结果截图 链接: 旋转链表 1.题目 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。 示例 1: 输入:head [1,2,3,4,5], k 2 输出:[4,5,1,2,3]示例 2&…

深入理解LightGBM

1. LightGBM简介 GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。GBDT不仅在工业界应用广泛&#…

python初试二

连接数据库 Django为多种数据库后台提供了统一的调用API。根据需求不同,Django可以选择不同的数据库后台。MySQL算是最常用的数据库。我们这里将Django和MySQL连接。 在Linux终端下启动mysql: $mysql -u root -p 在MySQL中创立Django项目的数据库: …

【数据结构和算法】判断子序列

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一:双指针 三、代码 3.1 方法一:双指针 3.1.1 Java易懂版:…

Cosmopolitan Libc:让 C 语言一次构建、随处运行 | 开源日报 No.109

jart/cosmopolitan Stars: 12.9k License: ISC Cosmopolitan Libc 使 C 成为一种构建一次运行在任何地方的语言,就像 Java 一样,但它不需要解释器或虚拟机。相反,它重新配置了标准 GCC 和 Clang 以输出符合 POSIX 标准的多语言格式&#xff…

VS Code连接远程Linux服务器调试C程序

1.在 VS Code 上安装扩展 C/C 2.通过 VS Code 连接远程 Linux 服务器 3.通过 VS Code 在远程 Linux 服务器上安装扩展 C/C 4.打开远程 Linux 服务器上的文件夹 【注】本文以 /root/ 为例。 5.创建项目文件夹,并在项目文件夹下创建C程序 6.按 F5,选…

数据挖掘任务一般流程

数据挖掘是从大量数据中提取有价值信息的过程。它涉及多个步骤,每一步都对整个数据挖掘过程至关重要。以下是数据挖掘任务的一般流程: 业务理解: 确定业务目标。评估当前情况。定义数据挖掘问题。制定一个初步计划来达到这些目标。 数据理…

JVM的类的生命周期

目录 前言 1. 加载(Loading): 2. 验证(Verification): 3. 准备(Preparation): 4. 解析(Resolution): 5. 初始化(Ini…

解决ES伪慢查询

一、问题现象 服务现象 服务接口的TP99性能降低 ES现象 YGC:耗时极其不正常, 峰值200次,耗时7sFULL GC:不正常,次数为1但是频繁,STW 5s慢查询:存在慢查询5 二 解决过程 1、去除干扰因素 从现象上看应用是由于某种…

从零开始:前端架构师的基础建设和架构设计之路

文章目录 一、引言二、前端架构师的职责三、基础建设四、架构设计思想五、总结《前端架构师:基础建设与架构设计思想》编辑推荐内容简介作者简介目录获取方式 一、引言 在现代软件开发中,前端开发已经成为了一个不可或缺的部分。随着互联网的普及和移动…

算法通关第十九关-青铜挑战理解动态规划

大家好我是苏麟 , 今天聊聊动态规划 . 动态规划是最热门、最重要的算法思想之一,在面试中大量出现,而且题目整体都偏难一些对于大部人来说,最大的问题是不知道动态规划到底是怎么回事。很多人看教程等,都被里面的状态子问题、状态…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑电力-交通交互的配电网故障下电动汽车充电演化特性》

这个标题涉及到电力系统、交通系统和电动汽车充电的复杂主题。让我们逐步解读: 考虑电力-交通交互的配电网故障: 电力-交通交互: 指的是电力系统和交通系统之间相互影响、相互关联的关系。这可能涉及到电力需求对交通流量的影响,反…

windows wsl2 ubuntu上部署 redroid云手机

Redroid WSL2部署文档 下载wsl内核源码 #文档注明 5.15和5.10 版本内核可以部署成功,这里我当前最新的发布版本 #下载wsl 源码 wget --progressbar:force --output-documentlinux-msft-wsl-5.15.133.1.tar.gz https://codeload.github.com/microsoft/WSL2-Linux-Ker…