本文对比Docker与Nix在软件复现性上的差异。Docker虽普及,但难保完全复现。Nix通过隔离构建环境实现高复现性,Flox则降低Nix门槛,使其更适于商业应用。
译自:Docker versus Nix: The quest for true reproducibility
作者:B. Cameron Gain
在进行性能基准测试时,最终目标是进行真正的“同类比较”。Docker 被广泛认为是过去15年中最杰出的发明之一,它提供了工程师在测试平台上共享应用程序所需的可复现性。因此,容器已成为复制测试的实用标准。
然而,仍然存在重大问题。当为测试生成不同的应用程序时,容器通常仍然依赖于主机操作系统或正在使用的内核更新。这种现象通常被称为“配置漂移”,意味着运行时并非总能达到准确比较基准所需的复现性。这在分析像 ScyllaDB 或 Cassandra 这样的复杂应用程序时尤为重要。虽然 Nix 尚无可用的 ScyllaDB 包,但通过 Nix 审视 Cassandra 为我们如何实现比当前标准在数学上更准确的基准测试提供了见解。
可重用性 vs. 可复现性
尽管许多行业专业人士依赖 Docker 的可移植性,但必须区分仅仅是可重用的容器和真正可复现的容器。Michael Stahnke 表示,标准的 Docker 构建过程通常会引入非确定性元素,从而损害严格的基准测试。
“我认为Docker从一开始就不能被称为可复现的。它是可重用的,因为你可以重用镜像并从中构建多个容器。但要复现该镜像实际上相当困难。大多数镜像的Dockerfile中都有诸如 apt-get upgrade 或 yum upgrade 或类似操作的行,”Stahnke 说。
“那是非确定性的,因为如果你是两周前做的,和今天做的,你最终会得到镜像中不同的内容。有一些方法可以固定所有东西,但即使你固定了某个东西,例如使用apt,你固定了树的叶节点,但你没有固定它下面的每个依赖项。因此,如果你安装一个 Python 应用程序,它可能仍然会升级其下的libc,所以除非你非常非常努力地固定每个包,否则无法保证可复现性。”
从基准测试的角度来看,Stahnke 说工程师可能会尝试通过为所有测试创建一个单一的、庞大的容器来缓解这个问题。
“从基准测试的角度来看,你可能会做的是拥有一个包含所有你可能需要进行基准测试内容的容器。这样,你就可以为每组基准测试使用完全相同的镜像,”Stahnke 说。“你只需关闭你不需要使用的数据库,或者类似的东西。我不确定这是否是同类比较,但大多数基准测试,如果你实际深入研究它们,从这个角度来看效果并不那么好。”
构建“洁净室”
NixOS 在 Docker 容器方面取得的进展主要集中在包的处理方式上。自诞生以来,可用性和可复现性一直是 Nix 的默认机制,这与标准容器化相比,代表了设计理念的根本转变。Stahnke 说,真正的可复现性需要一个严格、隔离的构建环境,以防止外部变量——如时间戳或互联网访问——改变最终的产物。
“当你从头开始设计一个具有可复现性设计的系统时,你必须做出一些非常不同的选择。例如,使用 Nix 构建的包是在洁净室环境或沙盒中构建的,”Stahnke 告诉 The New Stack。“这意味着没有网络连接,无法访问精确构建环境之外的文件,这意味着你基本上有一个纯粹的产物,它是可构建和可复现的,因为没有外部副作用或无法保证的事情,例如联系语言包管理器提供商或连接到互联网获取信息或文档或其他任何东西。例如,你构建的输入的加密哈希值是保证可复现的。”
Stahnke 说,这种严格的控制延伸到元数据,确保即使是基于时间的变量也不会导致漂移。
“进一步阐述这一点,即使像时间戳这样的东西也会被适当设置,使其始终设置为纪元时间。这样你就可以保证可复现性,因为具有不同时间戳的东西会有不同的哈希值等等,”Stahnke 说。“大约十年前,我相信 Debian 非常热衷于做可复现性,他们最终复制了 Nix 许多底层设施。即使是 Debian 的可复现性努力,我认为它是一个特别兴趣小组,并没有真正普及到整个 Debian 生态系统。”
Stahnke 说,常见的 Docker 实践,特别是使用可变标签,经常给操作员带来问题。
“就 Docker 而言,还有一个概念经常会损害操作员:使用 latest 作为你正在使用的版本。所以如果你有一个 AWS ELB,后端有节点,你启动一个新节点,它只是拉取你正在使用的 latest 镜像,这与该 ELB 集群中其他节点上运行的版本相同吗?这些是很难知道的事情,”Stahnke 说。“其中一些不一定是 Docker 本身的问题,更多的是实现和使用 latest 标签等常见模式的问题,latest 标签是完全可变的,并且在你发布新镜像时会发生变化。”
用 Flox 弥合可用性差距
尽管 Nix 在可复现性方面具有技术优势,但历史上一直被认为学习曲线陡峭。然而,Nix 之上运行的新层,如 Flox,正在提高其可访问性。这些工具允许商业环境利用 Nix 的原则而无需学术上的摩擦,促进从“在我的机器上能用”到“在你的机器上也能用”的转变,而不管底层操作系统如何。Stahnke 说,虽然 Nix 功能强大,但其学术根源需要额外的工具层才能使其适用于快节奏的商业环境。
“我认为基于 Nix 原则和流程构建的工具很棒。Nix 在构建时显然是一个学术设置,它有一些我认为对于日常用户来说是粗糙的地方,特别是在快速学习和商业环境中,技术不是为了技术而存在;它是为了使业务能够产生不同的结果,”Stahnke 说。“Nix 不太容易使用,而且有很多可变性,其中包含很多力量。所以你必须弄清楚哪些工作流程是用户需要的常见工作流程,Nix 生态系统的哪些部分和原则是重要的,然后你可以在它之上构建什么来使其更容易使用,更容易理解,更容易阅读,更容易与同事分享,更容易教学?”
对于 Flox 而言,Stahnke 特别指出,该工具使用环境原语来确保软件在不同的硬件架构中行为相同。
“对于 Flox 而言,具体来说就是:我们有一个称为 Flox 环境的原语,它包含你所有的包,包含服务管理指令,包含环境变量和配置等等。你只需执行 flox activate。这被计算为跨平台工作,所以如果我在 Linux x86 上,但我的同事在带有 Apple silicon 芯片组的 Mac 上,我每次在我的环境中进行更改时,它也会计算这些其他目标的更改,”Stahnke 告诉 The New Stack。
“因此,当我的同事将此环境拉到他们的笔记本电脑上时,他们运行一个简单的命令:flox activate,并且他们拥有与我完全相同的内容。完全相同的软件版本,来自完全相同的构建,来自完全相同的输入,显然构建到他们正在运行的目标上,但这些是我们为每个环境事务在底层执行的计算。主要目标是将‘在我的机器上能用’转变为‘在你的机器上也能用’。”
Stahnke 说,这种可复现性超出了开发范围,通过消除冗余测试,在生产环境中提供了显著优势。
“这也意味着有一些设施允许它在开发生命周期结束后在生产中更好地工作。在生产中,你可以说,‘好的,我们在 Mac 上开发,但在 Linux 上运行。’好吧,我们已经计算了所有这些事务,”Stahnke 说。“如果你已经将测试作为构建的一部分运行,那些测试就不需要重新运行,因为从数学上可以证明你正在测试相同的产物,或者你之前已经测试过完全相同的产物,所以你不需要像那样重新运行测试。”
Stahnke 说,Nix 生态系统固有的记账功能提供了自动的软件来源,无需外部扫描工具。
“此外,因为我们了解构建包或构建 Flox 环境的所有输入和输出,这意味着你可以默认获取软件构建材料。无需在之后运行任何附加组件,无需进行任何扫描或特殊工具。它只是生态系统的一部分,”Stahnke 说。“记账是 Nix 之所以成为 Nix 的一部分,也是 Flox 之所以成为 Flox 的一部分。因此,你将获得记账和完整的软件来源、软件构建材料以及对每个包内部内容的理解。”