首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >译 | .NET Core 基础架构进化之路(二)

译 | .NET Core 基础架构进化之路(二)

作者头像
Edi Wang
发布2019-07-09 10:21:28
1.4K0
发布2019-07-09 10:21:28
举报

原文:Matt Mitchell

翻译:Edi Wang

(接上篇 译 | .NET Core 基础架构进化之路(一)

Maestro 及依赖流

.NET Core 3.0 基础结构难题的最后一部分就是我们所说的依赖项流。这不是 .NET Core 的唯一概念。除非它们是完全独立的,否则大多数软件项目都包含某种对其他软件的版本化引用。在 .NET Core 中,这些通常表示为 NuGet 包。当我们想要库提供的新功能或修补程序时,我们会通过更新项目中引用的版本号来提取这些新更新。当然,这些包也可能具有对其他包的版本化引用,这些其他包可能具有更多的引用,依此类推。这将创建一个图(graph)。当每个仓库都拉取其输入依赖项的新版本时,更改会流过此图。

一个复杂图

大多数软件项目的主要开发生命周期(开发人员经常处理的)通常涉及少量相互关联的仓库。输入依赖项通常稳定,更新是稀疏的。当他们确实需要更改时,它通常是手动操作。开发人员评估输入包的可用版本,选择适当的版本,并提交更新。.NET Core 中不是这样。组件需要独立,以不同的节奏提供,并且具有高效的内循环开发经验,这导致了大量具有大量相互依赖的存储库。相互依赖性还形成了一个相当深的图:

dotnet/core-sdk 仓库充当所有子组件的聚合点。我们提供一个特定的 dotnet/core-sdk 编译版本,它描述了所有其他引用的组件。

我们还期望新的输出能快速通过此图,以便尽可能频繁地验证最终产品。例如,我们期望ASP.NET Core 或 .NET Core 运行时的最新版本尽可能经常在 SDK 中表示自己。这实质上意味着以常规的快速节奏更新每个仓库中的依赖项。在足够大的图(如 .NET Core)中,这很快成为手动执行的不可能完成的任务。这种大小的软件项目可能会通过多种方式来解决:

自动浮动输入版本

在此模型中,dotnet/core-sdk 可能引用 Microsoft.NETCore.App,这是 dotnet/core-setup 生成的,允许 NuGet 浮动到最新的预发行版本。虽然这行得通,但它也有重大的缺点。编译变得非确定性。签出较旧的 git SHA 和编译不一定使用相同的输入或生成相同的输出。重现错误变得困难。在 dotnet/core-setup 中,一个糟糕的提交可能会破坏任何在 PR 和 CI 检查之外拉取其输出的仓库。编译的编排成为一项主要任务,因为生成中的独立计算机可能会在不同的时间还原包,从而产生不同的输入。所有这些问题都是"可以解决的",但需要巨大的投资和不必要的基础设施复杂性。

"组合"编译

在此模型中,使用每个输入存储库中的最新 git SHA,以依赖项顺序同时生成整个图。生成每个阶段的输出将用于下一阶段。仓库有效地将其输入依赖项版本号覆盖其输入阶段。在成功编译结束时,将发布输出,并且所有仓库都更新其输入依赖项,以匹配刚刚编译的内容。与自动浮动版本号相比,这稍有改进,因为单个存储库版本不会因其他存储库中的不良签入而被爆,但它仍然有主要缺点。突发更改几乎不可能在仓库之间有效地流动,并且重现失败仍然是有问题的,因为存储库中的源通常与实际构建的内容不匹配(因为输入版本被覆盖在源代码管理)。

自动依赖项流

在此模型中,外部基础结构用于在存储库之间以确定性、验证方式自动更新依赖项。存储库在源中显式声明其输入依赖项和相关版本,并"订阅"来自其他仓库的更新。新的编译完成时,系统将查找匹配的订阅,更新任何声明的输入依赖项,并打开具有更改的 PR。此方法提高了可重复性、对重大更改进行流式操作的能力,并允许存储库所有者控制更新的完成方式。缺点是,它比其他两种方法中的任何一个都慢得多。更改只能以沿流路径每个存储库中的 PR 和官方 CI 时间总和的速度从栈底部流向顶部。

.NET Core 已尝试所有 3 种方法。我们在 1.x 的早期用了浮动版本,在 2.0 中进行了某种程度的自动依赖项流,并用在了 2.1 和 2.2 的组成版本。有了3.0,我们决定在自动化依赖项流上投入大量资金,并放弃其他方法。我们希望通过一些重要的方式改进以前的 2.0 基础架构:

简化产品实际内容的可追溯性

在任何给定的仓库中,通常可以确定哪些组件的版本用作输入,但几乎总是很难确定这些组件的构建位置、这些组件来自哪些 git SHA、它们之间的输入依赖关系等等。

减少所需的人工操作

大多数依赖项更新都是普通的。在更新 PR 通过验证以加快流程时自动合并它们。

使依赖项流信息与仓库状态分开

仓库应仅包含有关其节点在依赖关系图中的当前状态的信息。它们不应包含有关转换的信息,例如何时应进行更新、从中提取哪些来源等。

基于"意图"而不是分支的流依赖项

因为 .NET Core 由相当多的半自治团队组成,具有不同的分支理念、不同的组件发货节奏等,因此不使用分支作为意图的代理。团队应该根据这些输入的用途(而不是它们来自何处)定义他们拉入存储库的新依赖项。此外,这些投入的目的应由这些投入的小组宣布。

"意图"应从编译时推迟

为了提高灵活性,请避免在生成完成之前分配生成的意图,从而允许声明多个意图。在生成时,输出只是一个在一些 git SHA 上构建的位桶。就像在 Azure DevOps 生成的输出上运行发布管道一样,它实质上为输出分配了目的,在依赖项流系统中分配生成意图开始基于意图的流动依赖项过程。

考虑到这些目标,我们创建了一个名为 Maestro++ 的服务和一种称为"darc"的工具来处理我们的依赖项流。Maestro++ 处理数据以及依赖项的自动移动,而 darc 为 Maestro++ 提供了人机界面以及了解整个产品依赖状态的窗口。依赖项流基于 4 个主要概念:依赖项信息、编译、通道和订阅。

编译、通道和订阅

依赖项信息

在每个仓库中,都有仓库的输入依赖项的声明,以及eng/Version.Details中有关这些输入依赖项的源信息。读取此文件,然后传递每个输入依赖项的仓库+sha 组合生成产品依赖关系图。

编译

编译只是 Azure DevOps 内部构建中的 Maestro+ 视图。生成标识仓库+sha、总版本号以及从编译生成的完整资源集及其位置(例如 NuGet 包、zip 文件、安装程序等)。

通道

通道表示意图。将通道视为跨仓库分支可能很有用。可以将生成分配给一个或多个通道,以将意图分配给输出。通道可以与一个或多个释放管道关联。将生成分配给通道将激活发布管道并导致发布发生。根据发布发布活动更新生成的资源位置。

订阅

订阅表示转换。它将放置在特定通道上的编译的输出映射到另一个仓库的分支上,并提供有关何时进行这些转换的其他信息。

这些概念的设计使仓库所有者不需要栈或其他团队进度的全局知识,以便参与依赖项流。他们基本上只需要知道三件事:

  • 它们所做的编译的意图(如果有),以便可以分配通道。
  • 它们的输入依赖项及其产生的仓库。
  • 他们希望从哪些渠道更新这些依赖项。

例如,假设我拥有 dotnet/core-setup 存储库。我知道我的主分支为日常 .NET Core 3.0 开发编译二进制文件。我想将新编译分配给预先声明的".NET Core 3.0 开发"通道。我也知道,我有几个 dotnet/coreclr 和 dotnet/corefx 包输入。我不需要知道他们是如何编译的,也不是从什么分支编译的。我需要知道的是,我希望每天从'.NET Core 3.0开发'通道的最新dotnet/coreclr 输入,以及来自'.NET Core 3.0开发'通道的最新dotnet/corefx 输入,每当它们出现时。

首先,我添加一个 eng/Version.Details 文件。然后,我使用"darc"工具确保主分支上仓库的每个新生成默认分配给".NET Core 3.0 开发"通道。接下来,我将订阅设置为从 .NET Core 3.0 开发中提取输入,用于网dotnet/corefx, dotnet/coreclr, dotnet/standard 等的编译。这些订阅具有节奏和自动合并策略(例如,每周或每个生成)。

激活每个订阅的触发器时,Maestro++ 会根据与新生成的输出相交声明的依赖项更新核心设置回购中的文件(eng/version.Details.xml、eng/version.props 和其他一些文件)。它将打开 PR,一旦满足配置的检查,将自动合并 PR。

这反过来在主分支上生成新的核心设置编译。完成后,将自动将编译分配给".NET Core 3.0 开发"通道。".NET Core 3.0 开发"通道具有关联的发布管道,用于将构建的输出伪影(例如包和符号文件)推送到一组目标位置。由于此通道适用于日常公共开发编译,因此包和符号将推送到不同的公共位置。发布管道完成后,将完成通道分配,并触发在此事件上激活的任何订阅。随着更多组件的添加,我们构建了一个完整流图,表示仓库之间的所有自动流。

.NET Core 3 开发通道的流图,包括有助于 .NET Core 3 开发流的其他通道(例如 Arcade 的".NET 最新工具")。

一致性和不协调性

.NET Core 依赖关系图状态的可见性增加,这突出说明了一个现有问题:当在图中的各个节点引用同一组件的多个版本时,会发生什么情况?.NET Core 依赖关系图中的每个节点可能会将依赖项流到多个其他节点。例如,由dotnet/core-setup 产生的 Microsoft.NETCore.App 依赖项流向 dotnet/toolset, dotnet/core-sdk, aspnet/extensions 和许多其他位置。由于拉取请求验证时间的变化、需要对重大更改做出反应以及所需的订阅更新频率,此依赖项的更新将在每个位置以不同的速率提交。当这些仓库流向其他位置并最终在 dotnet/core-sdk 下合并时,可能有许多不同的 Microsoft.NETCore.App 版本在整个图形中被反向引用。这称为"不协调"。当在整个依赖关系图中仅引用每个产品依赖项的单个版本时,该图是符合逻辑的。如果可能的话,我们总是努力提供一个连贯的产品。

不协调会导致哪些问题?

不协调表示可能的错误状态。例如,我们来看看 Microsoft.NETCore.App。此包表示特定的 API 层面。虽然可以在仓库依赖关系图中引用多个版本的 Microsoft.NETCore.App,但 SDK 只附带一个版本。此运行时必须满足可能在此运行时上执行的传递引用组件(例如 WinForms 和 WPF)的所有要求。如果运行时不能满足这些要求(例如,爆破式 API 更改),则可能会发生故障。在不连贯的图中,由于所有存储库均未引入同一版本的 Microsoft.NETCore.App,因此有可能错过重大更改。

这是否意味着不协调总是错误状态?

不。例如,假设图中的 Microsoft.NETCore.App 的不协调性仅表示 coreclr 中的单个更改,即单个不会爆的 JIT Bug 修复。从技术上讲,在图表中的每个点都不需要引入新的 Microsoft.NETCore.App。简单地将相同的组件与新的运行时进行发布就足够了。

如果不协调只是偶尔重要,为什么我们努力提供一个连贯的产品?

因为确定何时不协调并不重要是很难的。简单地将一致性作为所需状态的运来比尝试理解不相干组件之间对已完成产品的任何语义影响差异更容易。它可以完成,但在构建的基础上,它是耗时密集型的,容易出错。将一致性强制为默认状态更安全。

依赖流的干货

所有这些自动化和跟踪都有大量的优势,随着仓库图的增大,这些优势变得显而易见。它为解决我们每天的实际问题开辟了许多可能性。虽然我们刚刚开始探索这一领域,但系统可以开始回答有趣的问题并处理以下情况:

  • dotnet/core-sdk 的 git SHA A 和 SHA B 之间发生了哪些"真正的"变化? 通过 Version.Details.xml 文件来构建完整的依赖关系图,我可以识别图中发生的非依赖项更改。
  • 修复需要多长时间才能在产品中出现? 通过组合存储库流图和每个存储库遥测数据,我们可以估计在图中将修复程序从存储库 A 移动到存储库 B 需要多长时间。这在发布后期特别有价值,因为它有助于我们在查看是否进行特定更改时做出更准确的成本/收益估计。例如:我们是否有足够的时间来进行此修复并完成方案测试?
  • core-sdk 及其所有输入编译生成的所有文件的位置是什么?
  • 在服务版本中,我们希望采取特定的修复,但暂缓其他。通道可以放置在允许特定修复程序自动流经图的模式下,但其他修复程序被阻止或需要批准。

下一步是什么?

随着 .NET Core 3.0 逐渐落地,我们正在寻找需要改进的新领域。虽然规划仍处于(非常)早期阶段,但我们预计在一些关键领域进行投资:

  • 缩短将修复程序转换为可发布、连贯产品的时间 – 依赖关系图中的跃点数量非常重要。这允许存储库在其进程中具有很大的自治性,但会增加我们的端到端"构建"时间,因为每个跃点都需要提交和正式编译。我们希望显著缩短端到端时间。
  • 改进我们的基础架构遥测 — 如果我们能够更好地跟踪失败的位置、资源使用情况、依赖状态的表现等,我们可以更好地确定我们的投资需要哪些地方才能提供更好的产品。在 .NET Core 3.0 中,我们朝这个方向迈出了一小步,但我们还有很长的路要走。

多年来,我们不断发展我们的基础设施。从 Jenkins 到 Azure DevOps,从手动依赖项流到 Maestro++,从许多工具实现到一个工具,我们对提供 .NET Core 3.0 所做的更改是向前迈出的一大步。我们准备开发并出一种比以往更加可靠的更令人兴奋的产品。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 汪宇杰博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
CODING DevOps
CODING DevOps 一站式研发管理平台,包括代码托管、项目管理、测试管理、持续集成、制品库等多款产品和服务,涵盖软件开发从构想到交付的一切所需,使研发团队在云端高效协同,实践敏捷开发与 DevOps,提升软件交付质量与速度。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档