如何使用 Jenkins WorkFlow 创建具有多个管道的复杂价值流

How to create complex value stream with multiple pipelines with Jenkins WorkFlow

如何在 Jenkins WorkFlow 中实现具有多个管道的复杂价值流?类似于你可以用 Go CD 做的:How do I do CD with Go?: Part 2: Pipelines and Value Streams.

对于分布式系统,我希望每个开发团队和运营团队都从他们自己的交付管道开始。一项更改只需要触发进行更改的团队的管道。它需要触发一个新的管道,该管道需要从每个团队的管道中获取最新的成功工件并从那里继续前进。这意味着来自其他团队的工件没有被重建或重新测试,因为它们没有被改变。在 Fan In 之后,我们可以 运行 一组自动化测试来验证分布式系统随着更改的正确行为。

在文档中,我只发现您可以从多个 VCS 中提取,但我假设所有内容都会在每次更改时构建和测试。这是我想避免的事情。

如果每个交付管道都在它自己的 Jenkins 作业中。我如何可视化完整的管道以及从其他管道中提取最后成功的工件或版本的最佳方法是什么?

假设您的每个开发团队都在项目的不同模块上工作,并且“一个更改只需要触发进行更改的团队的管道“我' d 使用 Git Submodules:

Submodules allow you to keep a Git repository as a subdirectory of another Git repository.

有了一个 repo,它就成为每个团队的 main 模块 repo 的子模块。这对团队来说是透明的,因为他们只处理指定的存储库。

main 模块也是构建工具方面模块项目的聚合器项目。所以,你有以下选择:

  • 单独构建每个 repo/pipeline 或
  • 一次构建整个 (main) 项目。

包含一个或多个构建作业的构建管道关联到每个 team/repo/module。

main 管道只是下游作业的集合,代表 team/repo/module 管道的起点。

构建触发器可以是手动、定时或源更改中的任何一种。

还要做出决定:

  • 是否单独对模块进行版本控制,以便其他模块仅依赖于发布版本。
    • 优点:
      • 其他依赖发布,通常是更稳定的版本。
      • 模块可以决定他们想要使用哪个版本的依赖项。
    • 缺点:
      • 必须为每个模块准备发布。
      • 其他人可以使用最新的更改可能需要更长的时间。
      • 模块必须决定他们想要使用哪个版本的依赖项。每次他们需要在新版本中添加功能时,他们都必须对其进行调整。
  • 或者是否对整个项目使用一个版本(然后由模块继承):...-SNAPSHOT在开发周期中,发布项目时使用一个发布版本。

    在这种情况下,如果有其他模块必不可少的模块,例如一个 core 模块,它的成功构建也应该触发依赖模块的构建,以便尽早识别不兼容。

    • 优点:
      • 其他人可以立即获得最新更改。
      • 只有在交付时才会为整个项目准备发布。
    • 缺点:
      • 其他人立即可用的最新更改可能会引入不太稳定的(快照)代码。

Re „如何可视化完整的管道

目前我不知道有任何插件可以使用 Workflows 执行此操作。

最初是为 Build Flows 创建的 Build Graph View Plugin,但现在已有两年多了:

Downstream builds are identified by DownStreamRunDeclarer extension point.

  • Default one is using Jenkins dependencyGraph and UpstreamCause and as such can detect common build chain.
  • build-flow plugin is contributing one to render flow execution as a graph
  • some Jenkins plugins may later contribute dedicated solutions.

(你知道,“可能”和“以后”经常变成不会永远不会 开发中。;)

Build Pipeline Plugin 但它显然也不适合 Workflows:

This plugin provides a Build Pipeline View of upstream and downstream connected jobs [...]


Re „拉入最后成功工件的方法

显然 Gradle 没有那么顺利:

By default, Gradle does not define any repositories.

我正在使用 Maven 并且存在 local and remote repositories 其中后者也可以是:

[...] internal repositories set up on a file or HTTP server within your company, used to share private artifacts between development teams and for releases.

您是否考虑过使用像 Artifactory or Nexus 这样的二进制存储库管理器?

据我所知,人们正在转向更小的、独立的代码交付,而不是单一的部署。但显然,不同组件之间仍然会存在依赖关系。至少,例如,如果您有一个脚本用于配置您的基础架构,而另一个脚本用于构建和部署您的应用程序,您需要确保您的基础架构更新脚本在应用程序部署之前是 运行。另一方面,您的基础架构不依赖于部署您的应用程序代码 - 它可以按照自己的节奏进行更新,只要它理想地通过一些测试即可。

如另一个 post 中所述,您确实有两种选择来实现此依赖关系:

  1. 有一个管道(工作流脚本)可以从两个存储库中检出代码并将它们同时通过同一个管道。对一个的任何更改都需要完整的船管道。
  2. 有两条管道,这将允许每条管道独立于另一条管道以自己的步调运行。这对基础设施代码来说不是问题,但对应用程序代码来说很可能是个问题。如果您在没有先进行基础设施更新的情况下将您的应用程序代码推向生产环境,结果可能不会令人满意。

我开始使用 Jenkins Workflow 做的是在我的流程之间建立依赖关系。基本上,我声明一个流程依赖于特定版本(在本例中,只是 BUILD_NUM),因此在我进行生产部署之前,我验证另一个管道的最后一次成功构建是否首先完成。我可以使用 Jenkins API 作为我的流程脚本的一部分来完成此操作,等待该构建或更高版本成功,就像这样

import hudson.EnvVars
import hudson.model.*

int indepdentBuildNum = 16

waitUntil{
    verifyDependentPipelineCompletion("FLDR_CM/WorkflowDepedencyTester2", indepdentBuildNum)
}

boolean verifyDependentPipelineCompletion(String jobName, int buildNum){
    def hi = jenkins.model.Jenkins.instance
    Item dep2 = hi.getItemByFullName(jobName)
    hi = null
    def jobs = dep2.getAllJobs().toArray()
    def onlyJob = jobs[0]   //always 1 job...I think?
    def targetedBuild = onlyJob.getLastSuccessfulBuild()
    EnvVars me = targetedBuild.getCharacteristicEnvVars()
    def es = me.entrySet()
    int targetBuildNum = 0;
    def vars = es.iterator()
    while(vars.hasNext()){
        def envVar = vars.next()
        if(envVar.getKey().equals("BUILD_ID")){
            targetBuildNum = Integer.parseInt(envVar.getValue())
        }
    }
    if (buildNum > targetBuildNum) {
        return false
    }
    return true

}

免责声明,我刚刚开始这个过程,所以我还没有太多的实际经验,但如果我有更多相关信息,我会更新这个线程。欢迎任何反馈。

在 Jenkins 中没有直接等同于价值流,并且工作流作业在这方面的行为没有任何不同:您可以让上游作业和下游作业与触发器相关联(在这种情况下 build 步骤,或核心 ReverseBuildTrigger),并使用(例如)Copy Artifact 插件将工件传输到下游构建。同样,您可以使用外部存储库管理器作为“真实来源”,并根据推送到存储库的快照定义作业触发器。

也就是说,Workflow 的部分目的是避免在大多数情况下需要复杂的作业链¹,因为通常更容易推理、调试和自定义带有标准控制流运算符和本地的单个脚本变量而不是管理一组相互依赖的工作。如果单个流程的主要问题是您需要避免重建未修改的部分,一种解决方案是使用 JENKINS-30412 之类的东西来检查特定存储库检出的变更日志,如果为空则跳过构建步骤。我认为在工作空间被其他构建破坏或丢弃的一般情况下,需要更多功能才能使这样的系统正常工作。

¹您肯定需要单独工作的一种情况是,出于安全原因,为不同项目做出贡献的团队不能看到彼此的源代码或构建日志。