Jenkins Pipeline语法

作为一种流行的持续集成和交付工具,Jenkins有多种方式来实现交付流水线。其中, Jenkins Pipeline是一种比较流行的方式,它提供了一个DSL(Domain Specific Language 的缩写,中文翻译为: 领域特定语言)来描述交付流水线。

官方:Pipeline Syntax (jenkins.io)

1、什么是Jenkins Pipeline

Jenkins Pipeline是一种基于Groovy编写的DSL,它可以描述交付流水线。Pipeline支持串行和并行的执行,可以将多个任务组合成一个流水线。Pipeline也支持将上下文传递给不同的阶段,使得阶段之间的数据共享变得更加容易。

Pipeline提供了三种编写Pipeline的方式

  • Declarative Pipeline: 是基于YAML编写的声明式语言,它可以更容易地描述交付流水线。
  • Scripted Pipeline: 是基于Groovy编写的脚本语言,它是一种灵活的方式来描述交付流水线。
  • Jenkinsfile: 是一种将Pipeline脚本保存为Jenkins源代码管理系统中的文件的方式。

2、Declarative Pipeline(声明式)流水线

2.1 特点

  • 最外层必须由pipline{ //do something }来进行包裹
  • 不需要分号作为分隔符,每个语句必须在一行内
  • 不能直接使用groovy语句(例如循环判断等),需要被script {}包裹

下面是一个简单的Pipeline脚本示例

pipeline{ // 最外层必须由pipeline包裹
        agent any // agent表示再哪个节点执行
        stages{
                stage("build"){
                        steps{ // 具体执行步骤
                      echo "Build..."
                  }
            }
            stage("test"){
                steps{
                      echo "Test..."
                        }
                }
            stage("depoly"){
                steps{
                      echo "Deployment..."
                  }
                }
        }
        post{ // 最后执行
                success{ // 测试成功时执行(需要安装 Email Extension 插件)
              emailext body: 'Build succeeded!', subject: 'Build Success', to: 'xx@example.com'
                }
                failure{ // 失败时会执行
                        emailext body: 'Build failed!', subject: 'Build Failure', to: 'xx@example.com'
                }
        }
}

在这个示例中,我们使用了三个阶段:build、test 和 deploy。

每个阶段都是一个stage块。在每个阶段中,我们可以使用Jenkins提供的一些API来执行任务,例如sh命令来执行shell脚本或者Jenkins提供的其他插件。

2.2 声明式核心概念

  • 1、pipeline: 声明其内容为一个声明式的pipeline脚本;
  • 2、agent: 执行节点(job运行的slave或者master节点);
  • 3、stages: 阶段集合,包裹所有的阶段(例如:打包,部署等各个阶段);
  • 4、stage: 阶段,被stages包裹,一个stages可以有多个stage;
  • 5、steps: 步骤,为每个阶段的最小执行单元,被stage包裹;
  • 6、post: 执行构建后的操作,根据构建结果来执行对应的操作;

2.1 pipeline

作用域:应用于全局最外层,表明该脚本为声明式pipeline
是否必须:必须
参数:无

2.2 agent

作用域:可用在全局与stage内
是否必须:是,
参数:any, none, label, node, docker, dockerfile

参考示例:
//运行在任意的可用节点上
agent any

//全局不指定运行节点,由各自stage来决定
agent none

//运行在指定标签的机器上,具体标签名称由agent配置决定
agent { label 'master' }

//node参数可以扩展节点信息
agent {
     node {
         label 'master'
         customWorkspace 'xxx'
    }
}

//使用指定运行的容器
pipeline{
    agent none
    stages{
            stage('build Test'){
                   agent {docker 'maven:3-alpine'}
                  steps{
                    echo "Build Test"
                        }
                }
                stage('Example Test'){
                   agent {docker 'openjdk:8-jre'}
                   steps{
                       echo "Exmaple Test"
                 }
                }
        }
}

2.3 stages

作用域:全局或者stage阶段内,每个作用域内只能使用一次 是否必须:全局必须
参数:无

参考示例:
pipeline{
    agent any
    stages{
        stage("first stage"){
                        stages{ //嵌套在stage里
                                stage("inside"){
                            steps{
                                echo "inside"
                            }
                         }
                         stage("inside_two"){
                            steps{
                                echo "inside_two"
                                        }
                                }
                        }
                }
                stage("stage2"){
                        steps{
                                echo "outside"
                        }
                }
        }
}

2.4 stage

作用域:被stages包裹,作用在自己的stage包裹范围内
是否必须:必须
参数:需要一个string参数,表示此阶段的工作内容
备注:stage内部可以嵌套stages,内部可单独制定运行的agent

2.5 steps

作用域:被stage包裹,作用在stage内部
是否必须:必须
参数:无

2.6 post(可选)

作用域:作用在pipeline结束或者stage结束后
条件:always、changed、failure、success、unstable、aborted

2.7 parameters(可选)

  • 构建时用户需要提供的参数
  • 这些参数可以通过params提供给流水线的steps使用,有 字符串 类型和 boolean 类型 string: 字符串类型,parameters { string(name: 'DEPLOY_ENV', defaultValue: ‘staging’, description: ‘’) } booleanParam: 布尔参数,parameters { booleanParam(name: ‘DEBUG_BUILD’, defaultValue: true, description: ‘’) } text: 文本参数,包含多行parameters { text(name: ‘DEPLOY_TEXT’, defaultValue: ‘One\nTwo\nThree\n’, description: ‘’) } choice: 选择类型的参数,parameters { choice(name: ‘CHOICES’, choices: [‘one’, ‘two’, ‘three’], description: ‘’) } password: password参数,parameters { password(name: ‘PASSWORD’, defaultValue: ‘SECRET’, description: ‘A secret password’) }

示例

pipeline{
    agent any
    parameters {
        string(name: 'P1', defaultValue: 'it is p1', description: 'it is p1')
        booleanParam(name: 'P2', defaultValue: true, description: 'it is p2')
    }
    stages{
        stage("stage1"){
            steps{
                    echo "$P1"
                    echo "$P2"
            }
                }
        }
}

2.8 triggers(可选)

自动化触发运行pipeline的方法

示例:每两分钟触发一次job

pipeline{
    agent any
    triggers{cron("*/2 * * * *")}
    stages{
        stage("Build Test"){
            steps{
                    echo "hello world"
            }
        }
    }
}

2.9 input(可选)

指令允许 暂时中断 pipeline执行,等待用户输入,根据用户输入进行下一步动作

pipeline {
    agent any
    stages {
        stage('Example') {
            input {
                message "Should we continue?"
                ok "Yes, Do it."
                submitter "alice,bob"
                parameters {
                    string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
                                }
                        }
            steps {
                echo "Hello, ${PERSON}, nice to meet you."
                        }
                }
        }
}

2.10 when(可选)

根据when指令的判断结果来决定是否执行后面的阶段

一个when指令至少包含一个条件,当有多个条件时,所有的子条件必须返回true,这个 stage才会运行

  • branch: 当正在构建的分支与模式给定的分支匹配时,执行这个阶段, 例如: when { branch ‘master’ }。注意,这只适用于多分支流水线。
  • environment: 当指定的环境变量是给定的值时,执行这个步骤, 例如:when { environment name: ‘DEPLOY_TO’, value: ‘production’ }**
  • expression: 当指定的Groovy表达式评估为true时,执行这个阶段, 例如:when { expression { return params.DEBUG_BUILD } }
  • not: 当嵌套条件是错误时,执行这个阶段,必须包含一个条件,例如:when { not { branch ‘master’ } }
  • allOf: 当所有的嵌套条件都正确时,执行这个阶段,必须包含至少一个条件,例如:when { allOf { branch ‘master’; environment name: ‘DEPLOY_TO’, value: ‘production’ } }
  • anyOf: 当至少有一个嵌套条件为真时,执行这个阶段,必须包含至少一个条件,例如:when { anyOf { branch ‘master’; branch ‘staging’ } }

单条件判断:

pipeline{
    agent any
    parameters{
        string(name:"deploy_env",defaultValue:"test",description:"")
        }
        stages{
        stage("Build Test"){
                when{
                    environment name:"deploy_env",value:"prod"
            }
                steps{
                    echo "hello wrold"
                        }
                }
        }
}

build结果:

第一次build时,deploy_env的值是 test,stage “Build Test” 被 skipped

第二次build时,参数deploy_env设置为 prod,执行 stage “Build Test”

多条件判断:

pipeline{
    agent any
        parameters{
                string(name:"deploy_env",defaultValue:"test",description:"")    
                string(name:"branch",defaultValue:"test",description:"分支")
        }
        stages{
        stage("Build Test"){
            when{
                environment name:"branch",value:"master"
                environment name:"deploy_env",value:"prod"
            }
            steps{
                echo "Hello world"
                        }
                }
        }
}

需满足 branch:master,deploy_env:prod 才会执行流水线。

3、Scripted Pipeline(脚本式)流水线

脚本管道和声明管道一样,是建立在底层管道子系统之上的。与Declarative不同, Scripted Pipeline实际上是一个使用Groovy构建的通用DSL。

Groovy语言提供的大多数功能都可供Scripted Pipeline的方式使用,这意味着它可以是一个非常有扩展性和灵活性的工具,可以用来编写连续交付管道。

stage('Build&Tag&Push&Deploy'){
        //把选择的项目信息转为数组
        def selectedProjects = "${project_name}".split(',')

        for(int i=0;i<selectedProjects.size();i++){
                //取出每个项目的名称
                def currentProjectName = selectedProjects[i];
                //定义镜像名称
                def imageName = "${currentProjectName}:${tag}"
                //定义newTag
                def newTag = sh(returnStdout: true,script: 'echo `date +"%Y%m%d%H%M%S"_``git describe --tags --always`').trim()
                //编译,构建本地镜像
                sh "sed -i 's#ACTIVEPROFILE#${springProfilesActive}#g' Dockerfile"
                sh "mvn clean package -Dmaven.test.skip=true dockerfile:build"
                container('docker') {
                        //给镜像打标签
            sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"
            //登录Harbor,并上传镜像
            withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')])
            {
                    //登录
                    sh "docker login -u ${username} -p ${password} ${harbor_url}"
                    //上传镜像
                     sh "docker push ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"
            }

            //删除本地镜像
            sh "docker rmi -f ${imageName}"
            sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"
            }
        def deploy_image_name = "${harbor_url}/${harbor_project_name}/${currentProjectName}:${newTag}"
        //基于Helm的方式部署到K8S
        container('helm3') {
                withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
                        sh """
                                helm repo add --username=${username} --password=${password} aliharborrepo http://harbor-ali-test.xxxx.com:8088/chartrepo/sparkx
                        """
                }
                withCredentials([file(credentialsId: 'b8fca5a2-8c91-4456-99aa-071723aae7fe', variable: 'KUBECONFIG')]) {
                        sh """
                                mkdir -p /root/.kube/ && echo $KUBECONFIG >/root/.kube/config
                                echo "Helm应用配置信息确认..."
                                helm upgrade --install --dry-run --debug ${currentProjectName} --namespace devops aliharborrepo/javaAliTest \
                                        --set replicaCount=${replicas} \
                                        --set image.repository=${deploy_image_name} \
                                        --set service.type=ClusterIP \
                                        --set springActive=${springProfilesActive} \
                                        --set ingress.enabled=${isIngress}
                                echo "应用部署..."
                                helm upgrade --install ${currentProjectName} --namespace devops aliharborrepo/javaAliTest \
                                        --set replicaCount=${replicas} \
                                        --set image.repository=${deploy_image_name} \
                                        --set service.type=ClusterIP \
                                        --set springActive=${springProfilesActive} \
                                        --set ingress.enabled=${isIngress}
                        """
                }
        }
    }
}                  

4、Declarative pipeline和Scripted pipeline的比较

共同点:

两者都是pipeline代码的持久实现,都能够使用pipeline内置的插件或者插件提供的 steps,两者都可以利用共享库扩展。

区别:

两者不同之处在于语法和灵活性。

  • Declarative pipeline:对用户来说,语法更严格,有固定的组织结构,容易生成代码段,使其成为用户更理想的选择。
  • Scripted pipeline:更加灵活,因为Groovy本身只能对结构和语法进行限制,对于更复杂的pipeline来说,用户可以根据自己的业务进行灵活的实现和扩展。

5、优化交付流水线性能

随着交付流水线的复杂度越来越高,需要优化交付流水线的性能成为了一个时刻需要关注的问题。
下面是一些常见的优化策略:

1、并行执行

使用并行执行可以大大缩短交付流水线的执行时间。Pipeline可以很容易地支持并行执行

例如,我们可以将测试阶段并行执行:

stage('Test') {
    parallel (
            "test1" : { sh 'mvn test -Dtest=Test1' },
                "test2" : { sh 'mvn test -Dtest=Test2' },
                "test3" : { sh 'mvn test -Dtest=Test3' }
        )
}

在这个示例中,我们使用了 parallel块 来并行执行。

在parallel块内,我们定义了三个分支来执行测试。分支的名称是任意的,它们将被用作日志输出。每个分支都有自己的命令来执行测试。

2、缓存依赖项

使用缓存可以避免在每个阶段中重新下载依赖项。

例如,如果一个项目使用Maven,我们可以在build阶段前缓存Maven仓库:

pipeline {
        agent any
    stages {
        stage('Build') {
            steps {
                script {
                    def mvnHome = tool 'Maven-3.8.2'
                    env.M2_HOME = mvnHome
                    sh "${mvnHome}/bin/mvn -B -Dmaven.repo.local=$HOME/.m2/repository clean package"
                }
            }
        }
    }
    post {
        success {
                cleanWs()
        }
    }
}

在这个示例中,我们使用了Maven插件的tool方法来定义Maven的版本。然后,我们将M2_HOME设置为我们定义的Maven的路径。

最后,我们在Maven命令中使用-Dmaven.repo.local选项来指定Maven仓库的位置。

3、删除不必要的阶段

一些阶段可能不必要并且会大大降低交付流水线的性能。

例如,我们可能只需要在提交代码时执行 build 和 test 阶段,而不是在每次构建时执行这些阶段。

示例:

pipeline {
    agent any
    stages {
        stage('Build') {
            when {
                changeset "src/**"
            }
            steps {
                    sh 'mvn clean install'
            }
        }
        stage('Test') {
            when {
                changeset "src/**"
            }
            steps {
                sh 'mvn test'
                        }
                }
        stage('Deploy') {
            when {
                changeset "src/**"
            }
            steps {
                sh './deploy.sh'
                        }
                }
    }
    post {
        success {
            cleanWs()
        }
    }
}

在这个示例中,我们在build、test和deploy阶段之前添加了when块。当检测到代码库 中的更改时,这些阶段才会被执行。

6、总结

Scripted Pipeline 和 Declarative Pipeline 两种流水线定义的主要区别在于语法和灵活性上。

Declarative Pipeline 语法要求更严,需使用 Jenkins 预定义的DSL 结构,使用简单; Scripted Pipeline 受限很少,限制主要在 Groovy 的结构和语法;

大家可以根据个人或企业的情况选择两种方式,比如如果公司没有 Groovy 技术栈,可以考虑直接使用 Declarative Pipeline, 学习曲线低,可以快速上手;

如果需要对公司的业务场景灵活配置或者对 Groovy 熟悉,那么 Scripted Pipeline 是一个不错的选择;

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
Jenkins云原生

Jenkins基于Helm的应用发布

2025-7-15 2:59:17

EFKStack云原生

EFK日志平台部署管理

2025-7-15 4:19:43

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索
Index
本站支持IPv6访问