编写 pipeline-as-code 和使用 Jenkins 2 实现 DevOps 简介

了解如何在 Jenkins 2 中创建 pipeline as code。
255 位读者喜欢这篇文章。

DevOps 的关键思想之一是基础设施即代码——让您的交付/部署 pipeline 的基础设施以代码形式表达——就像流经它的产品一样。

Jenkins 工作流工具是用于创建许多持续交付/部署 pipeline 的主要应用程序之一。这通常通过为各种 pipeline 任务定义一系列单独的作业来完成。每个作业都通过 Web 表单进行配置——填写文本框、从下拉列表中选择条目等。然后将一系列作业串在一起,每个作业触发下一个作业,形成一个 pipeline。

尽管这种创建和连接 Jenkins 作业以形成 pipeline 的方式被广泛使用,但它仍然具有挑战性。它不符合基础设施即代码的定义。作业配置仅作为 XML 文件存储在 Jenkins 配置区域中。这意味着这些文件不易读取或直接修改。Jenkins 应用程序本身提供了用户查看和访问它们的主要方式。

DevOps 的发展导致了更多可以更大程度地实施基础设施即代码范例的工具。Jenkins 在这方面有些滞后,直到 Jenkins 2 发布。(这里Jenkins 2是我们通常应用于支持pipeline-as-code 功能以及其他功能的新版本的名称。)在本文中,我们将学习如何在 Jenkins 2 中创建 pipeline as code。我们将使用两种不同的 pipeline 语法风格——脚本式和声明式。我们还将探讨如何将 pipeline 代码存储在 Jenkins 之外,但仍然让 Jenkins 在代码更改时运行它,以实现实施 DevOps 的目标之一。

让我们从讨论基础开始。Jenkins 及其插件通过其自身的编程步骤——Jenkins 领域特定语言 (DSL)——为您的 pipeline 任务提供构建块。

DSL 步骤

当 Jenkins 开始向 pipeline-as-code 模型演进时,最早的一些变化以插件的形式出现——workflow 插件。这包括创建一组初始 DSL 步骤,允许在 Jenkins 中编写简单的作业代码,并由此扩展编写简单的 pipeline 代码。

现在,Jenkins 中可用的不同 DSL 步骤由许多不同的插件提供。例如,我们有一个 git 步骤,它由 Git 插件提供,用于从源代码控制系统检索源代码。清单 1 显示了它的用法示例

node ('worker_node1') {
     stage('Source') {
         // Get some code from our Git repository
         git 'http://github.com/brentlaster/roarv2'
      }
}

清单 1:使用 Git 步骤的示例 pipeline 代码行。

实际上,为了与 Jenkins 2 兼容,当前的插件应该提供 DSL 步骤,以便在 pipeline 中使用。Pipeline 整合了 DSL 步骤来执行传统上通过 Web 表单完成的操作。但是,为了组成一个完整的 pipeline,仍然需要其他支持部分和结构。Jenkins 2 允许使用两种结构和语法风格来构建 pipeline。这些被称为脚本式声明式

脚本式 Pipeline

在 Jenkins 2 中创建 pipeline 的原始方法现在被称为脚本式。在最高级别,脚本式 Pipeline 包含在 node 代码块中。这里的 node 指的是包含 Jenkins agent 组件并可以运行作业的系统(以前称为 slave 实例)。node 通过使用标签映射到系统。标签只是在 Jenkins 中配置一个或多个节点时添加的标识符(通过全局管理节点部分完成)。图 1 显示了一个示例。

Figure 1. Node configuration with labels

图 1. 带有标签的节点配置

node 代码块是来自 Groovy 编程语言 的构造,称为闭包(用左右大括号表示)。实际上,脚本式 Pipeline 可以包含和使用任何有效的 Groovy 代码。例如,清单 2 显示了执行多项操作的脚本式 Pipeline 代码

  • 检索源代码(通过 git pipeline 步骤);
  • 获取全局定义的 Gradle 安装的值(通过 tool pipeline 步骤)并将其放入 Groovy 变量中;以及
  • 调用 shell 来执行它(通过 sh pipeline 步骤)
    // Scripted Pipeline
    node ('worker_node1') {
      // get code from our Git repository
      git 'http://github.com/brentlaster/roarv2'
       // get Gradle HOME value
      def gradleHome = tool 'gradle4'
       // run Gradle to execute compile and unit testing
      sh "'${gradleHome}/bin/gradle' clean compileJava test"
    }

清单 2:使用 tool 和 sh 步骤的脚本式语法。

这里 gradleHome 是用于支持 DSL 步骤的 Groovy 变量。可以通过 Jenkins 定义这样的 pipeline:创建一个新的 Pipeline 项目,并在新项目的页面底部的 Pipeline 编辑器部分输入代码,如图 2 所示

Figure 2: Defining a new Pipeline project

图 2:定义新的 Pipeline 项目

虽然这个简单的节点代码块在技术上是有效的语法,但 Jenkins pipeline 通常具有更细的粒度级别——阶段。阶段是将 pipeline 划分为逻辑功能单元的一种方式。它还用于将 DSL 步骤和 Groovy 代码组合在一起,以执行有针对性的功能。清单 3 显示了我们的脚本式 Pipeline,其中包含用于构建组件的阶段定义和一个用于源代码管理组件的阶段定义

// Scripted Pipeline with stages
node ('worker_node1') {
  stage('Source') { // Get code
    // get code from our Git repository
    git 'http://github.com/brentlaster/roarv2'
  }
  stage('Compile') { // Compile and do unit testing
    // get Gradle HOME value
    def gradleHome = tool 'gradle4'
    // run Gradle to execute compile and unit testing
    sh "'${gradleHome}/bin/gradle' clean compileJava test"
  }
}

清单 3:带有阶段的脚本式语法

pipeline 中的每个阶段还在新的默认 Jenkins 输出屏幕——阶段视图中获得自己的输出区域。如图 3 所示,阶段视图输出组织成矩阵,每行代表作业的一次运行,每列映射到 pipeline 中定义的阶段

Figure 3: Defined stages and Stage View output

图 3:定义的阶段和阶段视图输出

矩阵中的每个单元格(行和列的交集)还显示时间信息,并使用颜色指示成功或失败。表 1 显示了每种颜色的含义

表 1:阶段执行的颜色代码

颜色

含义
白色 阶段尚未运行
蓝色条纹 正在处理中
绿色 阶段成功
红色 阶段成功,但下游阶段失败
红色条纹 阶段失败

此外,通过将鼠标悬停在单元格上,弹出窗口将提供有关该阶段执行的更多信息,并提供指向日志的链接,如图 4 所示

Figure 4: Drilling down into the logs in stage output

图 4:深入查看阶段输出中的日志

虽然脚本式 Pipeline 提供了很大的灵活性,并且对于程序员来说可能感到舒适和熟悉,但它们对于某些用户来说也存在缺点。特别是

  • 感觉好像需要了解 Groovy 才能创建和修改它们。
  • 对于传统的 Jenkins 用户来说,学习使用它们可能具有挑战性。
  • 它们提供 Java 样式的回溯以显示错误。
  • 没有内置的构建后处理。

如果您习惯于能够通过 Freestyle 作业中的构建后处理来执行发送电子邮件通知或存档结果等任务,那么列表中的最后一项可能是一个大问题。如果您的脚本式 Pipeline 代码失败并抛出异常,那么您的 pipeline 的末尾可能永远无法到达;但是,您可以使用 Java 样式的 try-catch 代码块来处理这种情况,如图 5 所示

Figure 5: try-catch block to emulate post-build notifications

图 5:try-catch 代码块,用于模拟构建后通知

这里我们有一个名为 Notify最终阶段。无论我们 pipeline 的早期部分成功还是失败,此阶段都将始终执行。这是因为 try-catch 结构将捕获由失败引起的任何异常,并允许控制权在之后继续。(Jenkins 2 还有一个 catchError 代码块,它是 try-catch 构造的简化、有限版本。)

这种解决方法增加了这样一种印象,即您需要了解一些编程才能在脚本式 Pipeline 中完成以前使用 Freestyle 免费获得的功能。为了简化那些来自传统 Jenkins 的用户的事情,并消除混合中的一些 Java/Groovy 主义,CloudBees 和 Jenkins 社区创建了第二种编写 pipeline 的语法风格——声明式。

声明式 Pipeline

顾名思义,声明式语法更多地是关于声明您在 pipeline 中想要什么,而不是编写逻辑来执行它。它仍然使用 DSL 步骤作为其基础,但在步骤周围包含一个定义良好的结构。此结构包括许多指令和部分,用于指定您在 pipeline 中想要的项目。清单 4 显示了一个示例声明式 Pipeline

pipeline {
  agent { label 'worker_node1' }
  stages {
    stage('Source') { // Get code
      steps {
        // get code from our Git repository
        git 'https://github.com/brentlaster/roarv2'
      }
    }
    stage('Compile') { // Compile and do unit testing
      tools {
        gradle 'gradle4'
      }
      steps {
        // run Gradle to execute compile and unit testing
        sh 'gradle clean compileJava test'
      }
    }
  }
}

清单 4:示例声明式 Pipeline

您可能首先注意到的事情之一是,我们有一个 agent 代码块包围着我们的代码,而不是 node 代码块。您可以将 agent 和 node 视为相同的事物——指定要在其上运行的系统。(但是,agent 还包括创建系统的其他方法,例如通过 Dockerfile。)

除此之外,我们在 pipeline 中还有其他指令代码块,例如 tools 代码块,我们在其中声明要使用的 Gradle 安装(而不是像在脚本式语法中那样将其分配给变量)。在声明式语法中,您不能使用 Groovy 代码,例如赋值或循环。您仅限于结构化部分/代码块和 DSL 步骤。

此外,在声明式 Pipeline 中,阶段内的 DSL 步骤必须包含在 steps 指令中。这是因为您还可以在阶段中声明除步骤之外的其他内容。

图 6 显示了 Jenkins 2: Up and Running 书籍中声明式 Pipeline 中可以包含的所有部分的图表。读取此图表的方式是,具有实线边框的项目是必需的,而具有虚线边框的项目是可选的

Figure 6: Diagram of Declarative Pipeline sections

图 6:声明式 Pipeline 部分的图表

您可能可以从不同部分的名称中了解它们的作用。例如,environment 设置环境变量,而 tools 声明我们要使用的工具应用程序。我们不会在这里详细介绍每个部分,但这本书有一整章专门介绍声明式 Pipeline。

请注意,这里有一个 post 部分。这模拟了 Freestyle 作业中构建后处理的功能,并且还取代了对我们在脚本式 Pipeline 中使用的 try-catch 构造之类的语法的需求。另请注意,post 代码块也可以出现在阶段的末尾。

这种定义良好的结构为在 Jenkins 中使用声明式 Pipeline 提供了几个优势。它们包括

  • 从 Freestyle 到 pipeline-as-code 的更容易的过渡,因为您正在声明您需要什么(类似于您在传统 Jenkins 中填写表单的方式);
  • 更严格和更清晰的语法/需求检查;以及
  • 与新的 Blue Ocean 图形界面的更紧密集成(pipeline 中结构良好的部分更容易映射到各个图形对象)。

您可能想知道声明式 Pipeline 的缺点是什么。如果您需要超出声明式语法允许的范围,则必须采用其他方法。例如,如果您想将变量分配给一个值并在 pipeline 中使用它,则不能在声明式语法中执行此操作。某些插件也可能需要这种操作才能在 pipeline 中使用它们。因此,它们与声明式语法不直接兼容。

在这些情况下您会怎么做?您可以在阶段中的少量非声明式代码周围添加 script 代码块。声明式 Pipeline 中 script 代码块内的代码可以是任何有效的脚本式语法。

对于更大量的非声明式代码,推荐的方法是将其放入外部库(在 Jenkins 2 中称为共享 pipeline 库)。还有一些其他方法,这些方法不太认可——这意味着未来可能不支持它们。(如果您有兴趣了解更多信息,Jenkins 2: Up and Running 这本书更详细地介绍了共享库和其他替代方法。)

希望这能让您很好地了解 Jenkins 2 中脚本式 Pipeline 和声明式 Pipeline 之间的差异。这里要考虑的最后一个维度是,使用 Jenkins 2,您不需要将 pipeline 代码存储在 Jenkins 应用程序本身中。您可以将其创建为(或复制到)一个名为 Jenkinsfile 的外部文本文件。然后,该文件可以与源代码一起存储在同一存储库中的所需分支中。

Jenkinsfile

虽然 Jenkins 应用程序是开发和运行我们 pipeline 的主要环境,但 pipeline 代码本身也可以放置到一个名为 Jenkinsfile 的外部文件中。这样做使我们能够像处理任何其他文本文件一样处理我们的 pipeline 代码——这意味着它可以存储在源代码控制中并进行跟踪,并经历常规流程,例如代码审查。

此外,pipeline 代码可以与产品源代码一起存储并与其共存,从而使我们达到 pipeline 成为基础设施即代码的期望目标。

图 7 显示了存储在源代码控制存储库中的 Jenkinsfile

Figure 7: Jenkinsfile in source control

图 7:源代码控制中的 Jenkinsfile

源代码控制中 Jenkinsfile 的内容如图 8 所示

Figure 8: An example Jenkinsfile

图 8:示例 Jenkinsfile

您可能会认为这是我们之前 pipeline 的精确副本,但存在一些细微的差异。首先,我们在顶部添加了 groovy 指示符。但更重要的是,请注意从源代码控制系统获取代码的步骤。我们没有使用之前使用的 git 步骤,而是使用了 scm checkout 步骤。这是您可以在 Jenkinsfile 中采用的快捷方式。由于 Jenkinsfile 与它将要操作的代码位于同一存储库(和分支)中,因此它已经假定这是它应该从中获取源代码的位置。这意味着我们不需要显式地告诉它该信息。

使用 Jenkinsfile 的优势在于,您的 pipeline 定义与通过 pipeline 的产品的源代码一起存在。在 Jenkins 中,您可以创建一个 Multibranch Pipeline 类型的项目,并通过作业配置的 Branch Sources 部分将其指向包含 Jenkinsfile 的源代码存储库。图 9 显示了两个步骤

Figure 9: Creating and configuring a Multibranch Pipeline

Figure 9: Creating and configuring a Multibranch Pipeline

图 9:创建和配置多分支 Pipeline

然后,Jenkins 将扫描存储库中的每个分支,并检查该分支是否具有 Jenkinsfile。如果它有,它将在多分支 Pipeline 项目中自动创建和配置一个子项目,以运行该分支的 pipeline。图 10 显示了扫描正在运行并查找 Jenkinsfile 以在多分支 Pipeline 中创建作业的示例

Figure 10: Jenkins checking for Jenkinsfiles to automatically setup jobs in a Multibranch Pipeline

图 10:Jenkins 检查 Jenkinsfile 以自动设置多分支 Pipeline 中的作业

图 11 显示了针对 Jenkinsfile 和源代码存储库执行后,多分支 Pipeline 中的作业

Figure 11: Automatically created Multibranch Pipeline jobs based on Jenkinsfiles in source code branches

图 11:基于源代码分支中 Jenkinsfile 自动创建的多分支 Pipeline 作业

借助 Jenkinsfile,我们可以实现 DevOps 的目标,即将我们的基础设施(或至少 pipeline 定义)视为代码。如果 pipeline 需要更改,我们可以像处理任何其他文件一样,在源代码控制中拉取、编辑和推送 Jenkinsfile。我们还可以对 pipeline 脚本执行代码审查等操作。

使用 Jenkinsfile 存在一个潜在的缺点:当您在外部文件而不是 Jenkins 应用程序的环境中工作时,预先发现问题可能更具挑战性。处理此问题的一种方法是首先在 Jenkins 应用程序中将代码开发为 Pipeline 项目。然后,您可以稍后将其转换为 Jenkinsfile。此外,还有一个声明式 Pipeline linter 应用程序,您可以在 Jenkins 之外针对 Jenkinsfile 运行它,以尽早检测问题。

结论

希望本文能让您很好地了解脚本式和声明式 Pipeline 的区别和用途,以及如何将它们作为 Jenkinsfile 包含在源代码中。如果您想了解有关这些主题或 Jenkins 2 中相关主题的更多信息,您可以搜索 Web 或查看 Jenkins 2: Up and Running 这本书,其中包含大量有关使用此技术在您的组织中增强 DevOps 的示例和说明。

User profile image.
Brent Laster 是一位全球培训师、演讲者,并且是《Professional Git》和

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.