自从 Jenkins 2.0 版本升级之后,支持了通过代码(Groovy DSL)来描述一个构建流水线,灵活方便地实现持续交付,大大提升 Jenkins Job 维护的效率,实现从 CI  到 CD 到转变。而在2016 Jenkins World 大会上,Jenkins 发布了1.0版本的声明式流水线 - Declarative Pipeline,目前已经到发布了1.2版本,它是一种新的结构化方式定义一个流水线。今天我们一起对比这两种定义流水线的方式以及特性。


Pipeline 特性 - Pipeline As Code


Jenkins 从根本上讲是一种支持多种自动化模式的自动化引擎。Pipeline 为其添加了一套强大的自动化工具,支持从简单的持续集成到全面的持续交付。Jenkins Pipeline 特性如下:


  • 代码:Pipeline 以代码的形式描述,通常存储于源代码控制系统,如 Git,使团队能够编辑,审查和迭代其流程定义。

  • 持久性:Pipeline 可以在计划和计划外重新启动 Jenkins Master 管理时不被影响。

  • 可暂停:Pipeline 可以选择停止并等待人工输入或批准,然后再继续 Pipeline 运行。

  • 多功能:Pipeline 支持复杂的项目持续交付要求,包括并行分支/连接,循环和执行 Job 的能力。

  • 可扩展:Pipeline 插件支持其 DSL 的自定义扩展以及与其他插件集成。

 

基于 Jenkins Pipeline,用户可以在一个  JenkinsFile 中快速实现一个项目的从构建、测试以到发布的完整流程,并且可以保存这个流水线的定义。

 

下面的流程图是在 Jenkins Pipeline 中建模的一个持续交付方案的示例:


用代码描述流水线 - Jenkins Pipeline 详解_java


Pipeline 基本概念


  • Node: 一个 Node 就是一个 Jenkins 节点,或者是 Master,或者是 Agent,是执行 Step 的具体运行环境,Pipeline 执行中的大部分工作都是在一个或多个声明 Node 步骤的上下文中完成的。

  • Stage: 一个 Pipeline 可以从逻辑上划分为若干个 Stage,每个 Stage 代表一组操作,如:Build、Test、Deploy。注意,Stage 是一个逻辑分组的概念,可以跨多个 Node。

  • Step:  Step 是最基本的操作单元,小到执行一个 Shell 脚本,大到构建一个 Docker 镜像,由各类 Jenkins Plugin 提供,当插件扩展Pipeline DSL 时,通常意味着插件已经实现了一个新的步骤。

 

另外在 Jenkins Pipeline 中定义的 Stage(各个阶段的逻辑划分),Jenkins 提供了 Stage View 插件,按照 Stage 逻辑划分任务,对用户透明化、可视化展示流水线的执行,如下图:


用代码描述流水线 - Jenkins Pipeline 详解_java_02


Scripted Pipeline


用代码描述流水线 - Jenkins Pipeline 详解_java_03


上图通过 Jenkinsfile 定义了流水线的各个阶段(不仅限这几个阶段):构建,测试以及发布,在每个阶段可以执行相应的任务,而在 Scripted Pipeline 中,用户可以使用 Groovy 语法脚本来自定义流程控制,如下:


用代码描述流水线 - Jenkins Pipeline 详解_java_04


CurrentBuild 可以获取档次执行的结果,可以用于判读后续流程走向,Jenkins 还提供了更多内置环境变量以及 DSL 对象,方便我们操作流水线任务,如:BUILD_ID、JOB_NAME、BRANCH_NAME、CHANGE_ID 等等,可参考Global Variable。这种方式受 Jenkins 的限制较少,我们可以灵活控制和定义一个流水线,甚至我们可以在 JenkinsFile 中定义多个 Groovy 函数来扩展 Jenkins Pipeline 的能力。


Declarative Pipeline


用代码描述流水线 - Jenkins Pipeline 详解_java_05


同样的,上图定义了流水线的各个阶段,Declarative Pipeline 这种方式受 Jenkins 限制较多,需使用预定义的结构。Jenkins 已经预置了很多描述流水线的结构,可以在没有 Groovy 基础上快速建立流水线,当然在 Declarative Pipeline 同样支持写 Groovy 脚本,但官方不推荐把脚本直接写在流水线中,应该把这些逻辑定义在 Shared Libraries 中。


如下:


用代码描述流水线 - Jenkins Pipeline 详解_java_06


Declarative Pipeline 语法


下面介绍几个重点 Declarative Pipeline 语法:


定义执行环境


通过 Agent 来定义 Pipeline 的执行环境,在每个 Pipeline,Agent 是必需存在的。Agent 也可以用 Label 指定具体的 Jenkins Slave Node,并且每个 Stage 可以单独指定 Agent,灵活调度资源以及运行环境。


用代码描述流水线 - Jenkins Pipeline 详解_java_07


环境变量


可以定义为全局的,也可以为 Stage 来定义。


用代码描述流水线 - Jenkins Pipeline 详解_java_08


已定义的环境变量,可以通过 Env 来访问,与 Scripted Pipeline 一样,可以访问 Jenkins 预置的环境变量。


用代码描述流水线 - Jenkins Pipeline 详解_java_09


参数


可以通过 Params 对象来访问构建时的参数。如:


用代码描述流水线 - Jenkins Pipeline 详解_java_10


流程控制


可以通过 When 语法控制流程走向,判断环境变量或自定义表达式,是否执行某一个 Stage,同时 Anyof, Allof 进行逻辑运算,Anyof 对应或运算,Allof 对应与运算。


用代码描述流水线 - Jenkins Pipeline 详解_java_11


超时、重试机制


用代码描述流水线 - Jenkins Pipeline 详解_java_12


资源的清理工作 & 结束后操作


使用 Post 来完成一些资源的清理工作。其和 Stages 平级:


用代码描述流水线 - Jenkins Pipeline 详解_java_13


清理结束阶段也可以执行一个邮件通知:


用代码描述流水线 - Jenkins Pipeline 详解_java_14


并发 Stages


Declarative Pipeline 1.2 版本对并发语法做了升级:


用代码描述流水线 - Jenkins Pipeline 详解_java_15


其中 FailFast 可以控制如果并发的任务的其中一个失败,立即结束流水线,执行清理结束任务。


总结


Scripted Pipeline 和 Declarative Pipeline 两种流水线定义的主要区别在于语法和灵活性上, Declarative Pipeline 语法要求更严,需使用 Jenkins 预定义的DSL 结构,使用简单; Scripted Pipeline 受限很少,限制主要在 Groovy 的结构和语法,大家可以根据个人或企业的情况选择两种方式,比如如果公司没有 Groovy 技术栈,可以考虑直接使用 Declarative Pipeline, 学习曲线低,可以快速上手,并且新的 Blue Ocean Editor 插件可以帮助我们可视化的创建一个流水线,如果对 Groovy 非常熟悉,那 Scripted Pipeline 是一个不错的选择。