架构师_程序员_码农网

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 256|回复: 1

可重现构建(Reproducible Build)简单介绍

[复制链接]
发表于 2025-4-30 10:09:27 | 显示全部楼层 |阅读模式
什么是可重复构建?

Deterministic Build(确定性构建)或 Reproducible Build(可重现构建),两者稍有差异,但是从本文来讲可以理解成同一个意思。

可重现构建(Reproducible Builds)是指在相同的输入和构建环境下,多次执行构建过程能够产生完全相同的结果。这种技术对于软件开发、分发和安全验证具有重要意义。

如果构建无论在何时何地运行都能提供完全相同的输出,则该构建即为可复现构建。无论您在哪台计算机上运行,​​在什么时间运行,以及通过网络访问哪些外部服务,可复现构建都会产生相同的逐字节输出。这对于开发(因为可复现构建易于在不同的开发者设备之间共享)和生产(因为可以轻松确保可复现构建的成果未被篡改——只需在您自己的机器上重新运行构建,并检查结果是否一致!)都非常有用。

QQ截图20250430100333.jpg

可重复构建的三大支柱

支柱 1:可重复构建

构建可重复性指的是构建机器本身发生的事情。假设我们的构建输入可用,并且我们周围的世界没有任何变化,那么我们的构建在重复时是否会产生相同的输出?

确定性安装计划

可重复构建中第一个、最简单、最明显的要求是确定性依赖安装计划。

在大多数语言中,这就像签入锁定文件一样简单。现代构建工具通常允许项目将直接依赖项要求表达为约束,然后解决这些约束以生成安装计划(要安装的依赖项名称和版本对的列表)。许多此类工具还会生成序列化安装计划的锁定文件。开发人员可以将这些锁定文件提交到版本控制中,以便将来的构建使用相同的依赖项名称和版本。

请注意,我们还需要依赖项构建本身(而不仅仅是版本选择)具有确定性,而确定性的安装计划并不能让我们实现这一目标!

确定性构建

一旦我们知道要构建什么,我们的构建本身(包括我们自己的代码和依赖项代码的构建)实际上必须是确定性的。

对于没有编译步骤的项目来说,这实际上可能不是什么问题!例如,一个依赖项全部是纯 JavaScript 的 Node 项目,无需额外工作即可实现有效确定性。

对于确实包含编译或转译(源到源编译)步骤的项目,确保确定性是建立可复现构建过程中迄今为止最困难的部分。编译过程可能会通过多种方式暗中引入非确定性,包括:

  • 图灵完备的程序构建脚本可以任意改变编译输出。
  • 依赖可执行文件系统查找或网络调用的安装后脚本。
  • C 绑定到系统安装的包,其中具有不同库头的不同系统上的绑定可能会产生不同的输出。
  • 构建读取版本控制之外的文件的步骤。
  • 构建使用系统时间生成时间戳的步骤。
  • 构建从网络下载安装计划中未表达的依赖项的步骤(例如,从 GitHub 下载其 C 绑定的缓存二进制构建的 NPM 依赖项)。
  • 根据当前设置的环境变量改变行为但不提交环境变量配置的构建。


并非所有这些行为在正确设置的情况下都必然会引入不确定性,但正确配置构建过程可能非常复杂且困难。例如,您可以阅读这篇关于 Chromium 构建中不确定性的博客文章。许多此类问题可以通过控制本地构建环境来缓解,我们将在下一节中讨论。

支柱二:不可变环境

即使可重复构建,我们也需要确保构建输入不会改变。通常,这意味着我们要确保构建时所依赖的是我们周围环境的不可变快照。

不可变的本地环境

我们上面讨论过,构建不确定性的一个常见来源是依赖于构建工具未捕获的“依赖项”。C 语言绑定的系统库是最常见的例子,但其他本地环境因素(例如环境变量设置和版本控制范围之外的文件)也会影响构建。

缓解此问题的一个简单方法是将构建运行在已知的、不可变的容器中。例如,像 Docker 这样的容器运行时有助于确保每个人都使用相同的系统依赖项、相同的环境变量,并在相同的文件系统上运行。此外,还可以轻松验证容器的内容是否与已知良好的构建容器匹配,并且如果需要,可以轻松地从已知良好的镜像中完全删除并重新创建容器。

请注意,我们非常明确地指出了已知容器或已知容器镜像。仅仅提交 Dockerfile 是不够的!为什么?因为 Dockerfile 本身并没有描述 Docker 镜像完全可重现的构建过程,因为它们不是在不可变的全局环境中运行的。

不可变的全局环境

构建系统经常与外部服务交互,以完成诸如版本解析和依赖项下载之类的任务。但外部服务经常发生变化。

今天运行apt install nodejs会得到与去年不同的结果,明年可能也会得到不同的结果。这就是为什么 Dockerfile 本身无法描述可重现的构建——在不同的时间点运行相同的 Dockerfile 会产生不同的构建输出!

此处简单的缓解措施是尽可能配置构建,指定精确的版本(理想情况下,还要指定精确的内容哈希值),以便将来的构建使用与当前构建相同的版本。但外部服务也可能意外地改变其行为——真正悲观的可复现构建会尽可能多地利用其网络资源运行内部镜像。

支柱3:资源可用性

假设我们的构建是可重复的,并且我们脚下的世界没有改变。我们现在需要的只是访问构建输入。这看起来很简单,对吧?嗯……

注册表有时会出现故障

大多数 Node 开发者都经历过至少一次NPM 中断,在此期间,没有缓存或镜像 NPM 包的构建管道会中断。许多 Node 开发者还经历了left-pad和 faker 的删除,这严重破坏了 NPM 生态系统,实际上相当于中断。

缓解此类构建中断的唯一可靠方法是运行您自己的软件包注册表镜像。当外部服务不可用时,镜像可以保持在线;当官方注册表删除旧软件包时,镜像可以继续提供服务。同样的原则也适用于其他远程服务:除非您运行自己的镜像,否则构建管道的可用性仅与其服务的可用性相当。

选择运行服务镜像始终是一个微妙的权衡。一方面,像 NPM 这样的注册中心拥有专门的工程和运维团队,他们拥有维护这些系统在线的专业知识。另一方面,为一小部分依赖项运行小型镜像比运行所有 NPM 镜像要容易得多。您应该根据每个服务的具体情况来制定镜像决策,同时考虑历史外部服务的可靠性以及您团队的构建可用性和人员需求。

供应商确保最大可用性

确保项目依赖项最大可用性的一个简单方法是将其添加到 vendor 中。大多数包管理器都支持某种形式的“vendoring”,这意味着我们不再依赖外部服务的下载,而是将依赖项源代码存储在版本控制中,与我们的源代码并存。例如,在 Node 中,这可能看起来像是将 node_modules 提交到源代码控制中。

虽然这个解决方案并不完美(取决于您的供应商和项目的设置方式,这可能会给您的版本控制带来很大的负担),但它通常是获得最大可用性的最简单、最容易的解决方案。

参考:

超链接登录可见。
超链接登录可见。




上一篇:.NET/C# 使用 UnsafeAccessor 修改只读字段内容
下一篇:Angular 18 系列(三十二)ControlValueAccessor 自定义表单控件
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2025-4-30 10:10:23 | 显示全部楼层
关于 C# 构建 NuGet 包时使用可重复构建:

超链接登录可见。
超链接登录可见。
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

免责声明:
码农网所发布的一切软件、编程资料或者文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:help@itsvse.com

QQ|手机版|小黑屋|架构师 ( 鲁ICP备14021824号-2 )|网站地图

GMT+8, 2025-6-15 22:10

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表