nix是终极devops toolkit

2021-04-09 19:00:07

在通道,我们使用NIX构建和部署我们的服务并管理我们的开发环境。这种情况并非总是如此:过去,我们使用了一种生态系统的工具和自定义脚本的组合来将它们粘在一起。巩固与NIX的所有内容都帮助我们标准化了开发和部署工作流程,消除了“我的机器上的工作” - 问题,并避免不必要的重建。在这篇文章中,我们希望在采用NIX之前分享我们遇到的哪些问题,NIX如何解决这些问题,以及我们如何逐步将NIX介绍到我们的工作流程中。

大约1.5年前,我们决定逐步采用尼克斯。首先仅适用于开发环境和CI,后面也用于部署和生产使用。直到那时我们使用了各种工具来构建,包和部署我们的软件:

对于构建APT包,我们使用了各种制作,Docker和一个名为Chranebuild的家庭成长构建工具。这已经很好地工作了一段时间,但我们的构建要求清单不断增长,我们很快发现我们没有时间建立和维护高质量的构建系统。 1这就是为什么我们决定尝试给予nix。

我们开始在水中浸入一个小脚趾。有一天,我们的CI构建破坏了,因为哈尔克尔的构建工具堆栈的新版本倒退不兼容,而CI始终安装了最新版本。 NIX使别捕所有程序,包括堆栈,所以这对公司中的几个NIX倡导者来说这是一个很好的机会,以便在一个存储库中尝试一下。

在下一步中,NIX用于构建具有合适库的Python环境,用于某些基准脚本。这些小型步骤竟然是非常成功的,在此之后的一个月,正在制作计划迁移我们最大的Haskell存储库以使用NIX建造。随着时间的推移,越来越多的项目被迁移到NIX。首先通过通过本地开发设置移动,然后是连续集成设置,最后一个部署到生产的软件包。

拥有几个尼克斯倡导者对于在公司其余的尼克斯踢球时至关重要。他们的方法开始逐渐迁移,结果原来是成功的。我们很快意识到NIX解决了我们许多我们所拥有的问题,并在长期运行中拯救了一些解决问题的问题。

我们的主要动机来切换到NIX是它承诺解决我们的构建和我们的部署过程中的一些棘手问题。值得注意的是详细研究这些问题,因为许多其他组织编写了软件面临类似的问题,我们希望这一经验报告可以帮助您提出更明智的决定如果您正在考虑在您的组织中采用NIX。

让我们看看我们如何在我们的构建中解决三个具体问题。

对于我们最大的Python项目,我们使用了PIP,VirtualenV和APT的组合。我们将在VirtualEnv中使用PIP安装依赖项,并将其与我们的代码一起作为APT包安装在生产中。虽然它在很多年份供应我们的时候,它的痛点:

在保持其他固定(包括传递依赖项)的同时很难更新单个依赖。最后,我们创建了一些围绕pip freeze的脚本来生成两个要求.txt文件:一个引脚直接依赖,一个到引脚传递依赖项。

这并没有留下任何空间来区分从运行时依赖性的开发依赖关系,因此我们要么在生产中安装开发依赖关系,或者将开发人员询问到PIP手动安装剩余的依赖项。

在某人更改要求之后,所有其他开发人员都需要运行pip安装,但他们如何知道? “你有没有运行pip安装?”曾经是我们在开发聊天室中最常见的故障排除响应。

使用APT安装包不是原子:APTS将文件逐一更新。因为我们每秒开始Python处理很多次,因为在部署期间开始时,它可能导入旧代码的混合。这经常导致令人费解的错误。

安装APT包可能需要几分钟,因为除了它包含几个大资源的代码之外。即使这些文件不经常更改,必须为每个版本进行打包,下载并提取,并提取。

我们在一些较小的项目中简要介绍了Pipenv,但很快发现它没有解决这些问题的任何问题,它创造了自己的一些问题,所以我们继续前进。

装载在通道工具中的产品可以导出到各种市场。每个Marketplace都有自己的一组类别来对产品进行分类。要支持此产品,我们会定期从MarketPlaces导入数据模型,并以标准化的基于文本的格式将其保存在文件中。这些文件很容易生成和审查,但在运行时使用不高效,因此我们将它们转换为在构建时的SQLite数据库中。为所有市场的所有市场生成这些数据库,我们支持大约需要30分钟。这影响了我们的CI次和当地开发人员体验。

我们尝试才能智能地在输入输入时重新生成数据库,但始终存在导致结果不正确的细微案例,并且每个开发人员仍然必须生成所有工件以获得运行的本地开发环境。另一个痛点是切换Git分支:如果切换到一个输入的一个输入的分支,则需要重新生成相应的数据库......但是一旦切换回来,您需要再次执行此操作!

在CI上构建类别数据库,因此没有开发人员必须等待构建的数据库

我们将此作为尝试使用NIX构建类别数据库的机会,而不是使我们自己的缓存基础架构更高级。 NIX存储将自动保留可用的类别数据库的多个版本。当我们为构建输出设置缓存时,我们可以轻松地共享计算机之间的数据库。在CI上构建类别数据库并将其推向缓存成为可能性。

我们在依赖于使用APT的系统包装工作流程时遇到的另一个问题是系统打包库中的倒退 - 不兼容。例如,Libicu在不同的Ubuntu版本上具有不兼容的Sonames。这意味着为了在我们的服务器上升级Ubuntu版本,我们必须单独构建和包装我们的应用程序,我们打算使用它的每个Ubuntu版本。这也意味着我们有时必须对自己的机器进行不同版本的第三方图书馆而不是我们在生产中遇到了什么。

很像Haskell,NIX是基于许多革命性的想法,即在汇集时,提升我们如何建立我们的软件的最先进。与Haskell不同,它的抛光得多,并且具有大量锋利的边缘。虽然近20岁了,该项目目前仍处于快速(并加速!)节奏,似乎已达到逃避速度。然而,在其核心,它还达到了一定程度的成熟和稳定性,可以推荐用于生产使用。

为了充分了解NIX提供的优势,有必要对NIX生态系统中的不同项目进行练习型理解。我们将在此处提供非常简短的概述。

我们喜欢将NIX视为终极Devops Toolkit。它配备了许多电动工具:

NIX编程语言 - NIX的核心思想是构建指令和依赖管理最好用完全成熟的声明性编程语言完成。

NIX CLI工具 - 评估NIX程序以构建或安装包,调试NIX代码和管理已安装的软件包的程序。

nixpkgs,nix包存档 - 世界上最大的包存储库之一,是包含nix代码的git存储库的形式。还包含nix编程语言的标准库。

nixos - 一个完整的Linux发行版,完全通过nix构建和管理。我们将在本文中忽略它,因为我们当时没有使用它。

NIX语言允许我们为我们的服务定义软件包。对于我们宣布的每个包装:

任何其他包它取决于我们自己或来自nixpkgs的包。这些可以是运行时依赖性或构建时间依赖性。

要运行的命令将源代码转换为二进制或其他构建输出。

nix存储从包声明及其输入派生的目录中的构建输出。这意味着如果任何输入改变,则输出将存储在不同的路径中,这允许多个构建结果共存。

此外,NIX可以在开始建立任何内容之前计算输出目录的路径。二进制缓存可以缓存这些目录。如果在二进制缓存中存在路径,则NIX可以从那里下载它,而不是在本地运行命令以生成构建输出。

使用nixpkgs时,可以针对nixpkgs修订的特定修订。从某种意义上说,这就像整个软件生态系统的全球锁密文件一样。通常nixpkgs提供单个版本的包,但在复杂的情况下,单个nixpkgs修订版可以并行提供多个版本,如python 3.8和3.9。来自nixpkgs的包从默认的nix二进制缓存中获得。

由于任何人都必须生成10,000线的Makefile,或者模板巨大的山脉,将证明有一个全面的编程语言来指定您的构建是一个令人讨厌的好主意。拥有完全陈述的构建是一个很好的理想,但它是一个嵌合体。在某些情况下,任何具有足够复杂的构建都需要一些无法再声明的构建步骤。这是开发人员通常会诉诸BASH脚本的黑客的点,以规避构建系统的局限性。

此外,对于所有外部封装具有单个开源单次仓库,具有许多主要优点。它使得在任何外部依赖项中都非常容易,而不仅仅是来自您的编程语言的依赖性。例如,您希望在CI上使用Postgres和Redis来运行一些集成测试。使用大多数其他系统,您必须使用一些Bash和Dut-Tape来解决这个问题。使用NIX,您可以获得一个统一的构建环境,您可以在其中设置您需要的任何东西(并将其引导到正确的版本,以便您的测试将来仍然通过)。

最后,为所有包装进行单声头仓库也是一种社交技术,可以从各处提供贡献,同时提供测试和质量控制的中心。 nixpkgs包含超过55000个项目,只有拱门的AUR可提供更多项目。

让我们再次覆盖我们的包装和发展问题,并了解NIX如何解决这些问题。

更新单个依赖项。使用nix我们引用nixpkgs版本,这又引脚了我们从那里使用的包,但在那之上,nix使得覆盖包装覆盖包只是我们需要的版本,所以我们可以直接依赖的包裹覆盖这有更多的控制。

分离运行时和开发依赖项。使用PKGS.BuildEnv,很容易构建一个开发环境,包括所有运行时依赖性加上开发包,在生产中我们不引用此环境。

避免手动安装步骤。使用nix,我们要么为我们使用nix运行-c运行的命令,或者我们使用nix运行-c $ shell输入开发shell。然后,NIX扩展了路径以使我们需要的包裹提供。 nix构建default.nix运行时,它始终使用最新版本。如果default.nix由于git pull而更改,下一个nix运行将拉动新的依赖项。

原子安装。 NIX在依赖于所有构建输入的路径上安装包,因此可以与旧版本一起安装一个新版本的包。我们首先确保存在所有所需的文件,然后我们可以原始替换一个系统文件文件,或符号链接/ usr / bin,并使其指向新路径。

即时回滚。由于旧版本未在安装新版本的程序包时未被删除,因此回滚就像将Symlink或SystemD单位再次指向上一条路径一样简单。旧版本确实会在某些时候收集垃圾,但我们明确地保持过去的3个版本。

在版本之间共享大资源。通过将大资源包装为单独的NIX软件包,可以在我们的多个版本之间共享它们。由于多个版本的资源可以与彼此一起安装,因此我们的服务引用了所需的确切版本,原子型部署仍然可以使用,这将无法使用APT包。

切换分支时避免重建。 nix将构建输出放在/ nix / store的子目录中,在依赖于构建描述的路径中。如果该路径已存在,因为它以前构建,则无需再次构建它,因此在分支机构之间来回切换之后的构建是NO-OP。

在机器之间共享构建输出。由于NIX计算要在构建之前存储构建输出的路径,因此它还可以查询二进制缓存以从那里获取输出,而不是构建它。 Cachix 3可以轻松设置私人使用的缓存。在第一个构建之后,我们推向此缓存,然后没有其他人需要再次构建输出。

从CI重用构建输出。二进制缓存可以由任何可信用户填充,任何可信用户可以是CI。所以现在我们只构建一次每个目标,而且开发人员也可以重用缓存中的输出。

破坏系统依赖性的变化。使用NIX构建的软件包仅依赖于NIX管理的其他包,而不是在系统库上,因此我们的软件包现在独立于主机操作系统;我们可以将相同的包部署到Ubuntu 18.04和20.04。

每当一个大型项目在一种做事的方式落户时,它就开始变得舒适。这么多,以至于这种做事方式的特殊性被交织在项目的本质上。在我们的情况下,所有代码,工作流和系统都是基于Haskell的堆栈或Python的virtualenv,Ansible,Apt软件包和运行Ubuntu的服务器的顶部。 NIX是一种不同的方法,可以提供所有这些工具的提供,首先浏览一眼似乎与现有工具不兼容。第一个挑战是发现我们如何在建立的工作流程中开始使用NIX。

幸运的是,NIX可以接管上述工作流程的部分。在Haskell项目的第一步中,我们在一个Python脚本中的代码替换了一个名为Compling的Python脚本,以及用于创建APT包的DPKG-DEC,其中一些NIX代码编译了Haskell项目,然后将其放在APT包中。随着输出仍然是APT包,此更改对其其余的包装和部署过程是看不见的。此外,使用NIX构建APT包已经给了我们建立再现性。在交换构建/部署进程的更多组件之后,我们发现每个单独的步骤都给了我们更多的利益,尽管剩下的过程仍然是旧的方式。这提高了我们继续的动力。

除了在生产上运行的碎片外,一次交换一件就会提供相对的安心。通过途径,中断可能会挫败客户。更重要的是,更有可能对所涉及的开发人员造成强烈的压力。我们不喜欢压力,所以我们确保将开关作为安全性和经过良好的测试,这是合理可能的。这是通过首先切换暂存服务器来完成的。对于在多个服务器上负载平衡的服务,我们可以一次切换一个服务器。某些软件包也可以同时安装APT版本和NIX版本。最后,大多数包裹都在没有问题的情况下切换。对于那些造成麻烦的人来说,损害非常有限。

有了一点创造力,可以继续交换位和碎片,慢慢地为新的方式建立舒适感。重要的是,这种新的舒适感也是在系统的不同部分工作的同事中建立。为了防止惊喜,他们需要了解最重要的变化,为什么他们正在发生以及如何与他们合作。为实现这一目标,我们向开发团队提供了演示,向NIX介绍。后来,这是一个深入的研讨会。每次直接更改工作流程都伴随着开发公告邮件来解释更改。我们发现热情在我们的同事中,他们中的许多人都成为了自己的爱斯斯特。

在通道,我们使用NIX构建和部署我们的服务并管理我们的开发环境。在一年多的过程中,我们逐渐采用NIX,替换特定语言的包管理器(用于Python),或者使用nix引脚(对于haskell),因此我们有一个统一的方式来管理跨存储器的开发环境,操作系统版本和执行环境。我们使用NIX将我们的服务包装并将我们的服务分发给我们的生产服务器,它带来了快速和原子部署和即时回滚。拥有域特定的编程语言,以及由充满活力的社区维护的包裹收集,使我们能够这样做。

特别感谢Laurens Duijvesteijn,Wesley Bowman,Sayyed Naqi,整个Devops团队,以及其他加入这项工作的人。

1:由于任何写过构建系统的人都将证明,很容易开始,但很快就会变得复杂。依赖处理?当然,让我们添加一个DAG执行系统。隔离?当然,让我们使用Chroot或Linux命名空间。缓存?让我们只是哈希所有的输入并将其放在GCS桶中。增量构建?让我们只重新计算改变的构建步骤。等等

2:原则上,这当然是所有构建系统。但是,NIX通过在巨型单次repo中拥有所有外部包的所有外部包来保持所有构建的所有构建输入,更容易保持所有构建输入。 NIX中的重要部分是针对您依赖的NIXPKG的确切版本,另外要注意NIXPKGS之外的所有构建输入都是您自己的回购的一部分。 ↩︎

3:喊出来给凯乔的惊人服务,他们的服务已经节省了很多麻烦,而几次出错的时间出了一些问题,它经常已经修复了我们注意到的时间。 ↩︎