0
点赞
收藏
分享

微信扫一扫

一文搞定Jenkins Pipeline语法

Jenkins Pipeline语法

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

官方:Pipeline Syntax (jenkins.io)

一、什么是Jenkins Pipeline

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

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

**Declarative Pipeline:**是基于YAML编写的声明式语言,它可以更容易地描述交付流水线。

**Scripted Pipeline:**是基于Groovy编写的脚本语言,它是一种灵活的方式来描述交付流水线。

**Jenkinsfile:**是一种将Pipeline脚本保存为Jenkins源代码管理系统中的文件的方式。

二、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'
        }
    }
}

在这个示例中,我们使用了三个阶段:buildtestdeploy

每个阶段都是一个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.2.1、pipeline
作用域:应用于全局最外层,表明该脚本为声明式pipeline
是否必须:必须
参数:无
2.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.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.2.4、stage
作用域:被stages包裹,作用在自己的stage包裹范围内
是否必须:必须
参数:需要一个string参数,表示此阶段的工作内容
备注:stage内部可以嵌套stages,内部可单独制定运行的agent
2.2.5、steps
作用域:被stage包裹,作用在stage内部
是否必须:必须
参数:无
2.2.6、post(可选)
作用域:作用在pipeline结束或者stage结束后
条件:always、changed、failure、success、unstable、aborted
2.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.2.8、triggers(可选)

自动化触发运行pipeline的方法

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

pipeline{
    agent any
    triggers{cron("*/2 * * * *")}
    stages{
        stage("Build Test"){
            steps{
                echo "hello world"
            }
        }
    }
}
2.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.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 才会执行流水线。

三、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-test.xxxx.com:/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}
                """
            }
        }
    }
}

四、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块内,我们定义了三个分支来执行测试。分支的名称是任意的,它们将被用作日志输出。每个分支都有自己的命令来执行测试。

5.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仓库的位置。

5.3、删除不必要的阶段

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

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

示例:

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块。当检测到代码库中的更改时,这些阶段才会被执行。

六、总结

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

Declarative Pipeline 语法要求更严,需使用 Jenkins 预定义的DSL 结构,使用简单;

Scripted Pipeline 受限很少,限制主要在 Groovy 的结构和语法;

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

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

举报

相关推荐

0 条评论