什么是 Pipeline

在进一步讨论 Pipeline 两种语法模型的区别前先来了解下什么是 pipeline,为什么要到它。

Pipeline 是 Jenkins 中的一套自动化流程框架,代表一系列工作流或活动流,作用是允许将单个Jenkins节点或者多个节点的任务组合连接起来,从而实现单个任务难以完成的复杂构建工作。

它有以下优点:

  • Code(代码): Pipeline 的任务是通过代码来实现的,可以通过git来进行版本化控制,团队成员可以编辑迭代Pipeline 代码
  • Durable(持久化): 无论 Jenkins master 是在计划内或者非计划内重启,pipeline 任务都不会收到影响
  • Pausable(可暂定性) pipeline基于groovy可以实现job的暂停和等待用户的输入或批准然后继续执行。
  • Versatile(多功能) 支持fork/join、循环执行,并行执行任务
  • Extensible(可扩展性) 支持其DSL的自定义扩展 ,以及与其他插件集成的多个选项

Pipeline 语法

Pipeline 包括声明式语法和脚本式语法

声明式
pipeline {
    agent any // 
    stages {
        stage('Build') { 
            steps {
                // 
            }
        }
        stage('Test') { 
            steps {
                // 
            }
        }
        stage('Deploy') { 
            steps {
                // 
            }
        }
    }
}
  • agent any: 任意可用的agent都可以执行
  • stages:代表整个流水线的所有执行阶段。通常stages只有1个,里面包含多个stage
  • stage:代表流水线中的某个阶段,可能出现n个。一般分为拉取代码,编译构建,部署等阶段。
  • steps:代表一个阶段内需要执行的逻辑。steps里面是shell脚本,git拉取代码,ssh远程发布等任意内容。
脚本式
node {  
    stage('Build') { 
        // 
    }
    stage('Test') { 
        // 
    }
    stage('Deploy') { 
        // 
    }
}
  • node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念。

声明式和脚本式的主要区别

1. Pipeline 代码校验

声明式 Pipeline 会在执行前就会校验 Pipeline 语法是否正确,而脚本式不会。

观察下面声明式例子,stage Test 里面的 ‘echo 1’ 有语法错误,echo 只可以接受字符串,尝试执行该 pipeline时,会立即报错

pipeline {
    agent any

    stages {
        stage("Build") {
            steps {
                echo "Some code compilation here..."
            }
        }

        stage("Test") {
            steps {
                echo "Some tests execution here..."
                echo 1
            }
        }
    }
}

在这里插入图片描述

同样脚本式的例子如下,会直到执行到 stage Test 才会报错

node {
    stage("Build") {
        echo "Some code compilation here..."
    }

    stage("Test") {
        echo "Some tests execution here..."
        echo 1
    }
}

在这里插入图片描述

想象一下如果有多个 stage,前面的stage 执行都没有问题而最后一个stage 出问题,这将会浪费一定的时间

2. 重启 stage

如果某个 stage 执行失败,修复后声明式 Pipeline 可以直接跳到该 stage 重新执行,而脚本式要从头来过。

观察下面两个图,声明式可以 restart stage,脚本式不能
声明式

脚本式

3. Option 指令

option指令用于配置整个jenkins pipeline本身的选项,根据具体的选项不同,可以将其放在pipeline块或者stage块中。

虽然声明式pipeline 和脚本式 pipeline 都支持 options 选项,但声明式 options 和 pipeline 代码逻辑是分开的,而脚本式 options 和代码逻辑是嵌套在一起的,如果有多个options需要设置代码可读性差。观察下面例子:

声明式

pipeline {
    agent any

    options {
        timestamps()
        ansiColor("xterm")
    }

    stages {
        stage("Build") {
            options {
                timeout(time: 1, unit: "MINUTES")
            }
            steps {
                sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
            }
        }

        stage("Test") {
            options {
                timeout(time: 2, unit: "MINUTES")
            }
            steps {
                sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
            }
        }
    }
}

脚本式

node {
    timestamps {
        ansiColor("xterm") {
            stage("Build") {
                timeout(time: 1, unit: "MINUTES") {
                    sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
                }
            }
            stage("Test") {
                timeout(time: 2, unit: "MINUTES") {
                    sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
                }
            }
        }
    }
}
4. 条件执行 stage 的处理方式

观察下面声明式例子,添加条件当 env.FOO 等于 bar 时才执行 test stage

声明式

pipeline {
    agent any

    options {
        timestamps()
        ansiColor("xterm")
    }

    stages {
        stage("Build") {
            options {
                timeout(time: 1, unit: "MINUTES")
            }
            steps {
                sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
            }
        }

        stage("Test") {
            when {
                environment name: "FOO", value: "bar"
            }
            options {
                timeout(time: 2, unit: "MINUTES")
            }
            steps {
                sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
            }
        }
    }
}

在这里插入图片描述

脚本式

node {
    timestamps {
        ansiColor("xterm") {
            stage("Build") {
                timeout(time: 1, unit: "MINUTES") {
                    sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
                }
            }
            if (env.FOO == "bar") {
                stage("Test") {
                    timeout(time: 2, unit: "MINUTES") {
                        sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
                    }
                }
            }
        }
    }
}

在这里插入图片描述

从结果来看,声明式会正常跳过 Test stage,而脚本式需要if 判断才会添加 test stage,所以 test stage 根本没有被渲染出来,这会一定程序上造成困扰。

5. 语法逻辑

声明式支持 stages,steps 更细致的阶段性任务划分,脚本式只支持 stage, 编写逻辑时,声明式要在 script{} 标记里面写,脚本式不用,观察下面例子

声明式

 pipeline {  
   agent {    
     // executes on an executor with the label 'some-label' or 'docker'
     label "some-label || docker"    
   }   

   stages {   
     stage("foo") {     
       steps {       
         // variable assignment (other than environment variables) can only be done in a script block
         // complex global variables (with properties or methods) can only be run in a script block
         // env variables can also be set within a script block
         script {          
           foo = docker.image('ubuntu')
           env.bar = "${foo.imageName()}"
           echo "foo: ${foo.imageName()}"          
         }        
       }      
     }
     stage("bar") {
       steps{  
         echo "bar: ${env.bar}"
         echo "foo: ${foo.imageName()}" 
       }      
     }
   } 
 }

脚本式

 node {

   git url: 'https://github.com/jfrogdev/project-examples.git'

   // Get Artifactory server instance, defined in the Artifactory Plugin administration page.
   def server = Artifactory.server "SERVER_ID"

   // Read the upload spec and upload files to Artifactory.
   def downloadSpec =
           '''{
       "files": [     
         {
            "pattern": "libs-snapshot-local/*.zip",
            "target": "dependencies/",
            "props": "p1=v1;p2=v2"
         }      
       ]    
   }'''

   def buildInfo1 = server.download spec: downloadSpec

   // Read the upload spec which was downloaded from github.
   def uploadSpec =
           '''{
     "files": [
       {
          "pattern": "resources/Kermit.*",
          "target": "libs-snapshot-local",
          "props": "p1=v1;p2=v2"
       },
       {
          "pattern": "resources/Frogger.*",
          "target": "libs-snapshot-local"
       }
      ]
   }'''


   // Upload to Artifactory.
   def buildInfo2 = server.upload spec: uploadSpec

   // Merge the upload and download build-info objects.
   buildInfo1.append buildInfo2

   // Publish the build to Artifactory
   server.publishBuildInfo buildInfo1
}

总结

  • 声明式语法是 Jenkins 官方推荐的写法,它的特点是语法简单,无需专业的编程知识也能快速上手,同时语法也更为严谨,有许多限制,失去了一定的灵活性。
  • 脚本式语法可以让你灵活的运用代码,就像一张白纸,你可以更灵活地对任务逻辑进行编排,适合有大量特定要求、逻辑复杂的任务。同样灵活的背后是没有形成统一的逻辑结构或流程,可能造成代码的不稳定,难以维护以及可读性差。

参考链接

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐