将 NetBSD 引入 Zig 的持续集成

2021-08-08 15:43:13

您好,如果这是您第一次阅读本文,我是 Luna,LavaTech 的一半,我们为 Zig 编程语言提供持续集成 (CI) 服务。我们开始这样做是因为 Zig 无法将他们预先存在的 CI 服务用于 FreeBSD 构建,sr.ht,因为编译器变得非常占用 RAM,这达到了他们 CI 的限制。我们介入了,现在我们提供了一个自托管的 Sourcehut 实例,其中 FreeBSD 构建仍然可以在自托管编译器尚未完成的情况下进行(实际上,我不知道一旦内存使用量下降会发生什么,但现在,我很高兴提供服务,并希望继续)。在一个无事可做的晴天,我决定为 Zig 提供更好的 NetBSD 支持,我可以学习的领域之一就是我正在操作的领域:CI。这篇博文概述了我将 NetBSD 引入 Zig CI 必须做的事情。 Sourcehut 是一个“软件伪造”,它是一组用于管理代码库的工具,无论是公开的还是私有的。 sr.ht 是 Sourcehut 的主要实例,由其原始创建者 Drew Devault 运营。 builds.sr.ht 是 Sourcehut 的 CI 服务,它通过提供运行专门制作的操作系统映像的 QEMU/KVM 虚拟机来工作,从中可以构建您的项目,并且可以在虚拟机内测试生成的工件,所有这些都是通过在 VM 中执行 SSH 来实现的。在所有命令都没有错误地运行之后,构建已经“通过”了。主实例上的 builds.sr.ht 的例子可以在这里看到 在 builds.sr.ht 的兼容性页面可以看到可用的 VM 镜像的轮廓,从那里你可以看到 Alpine Linux、Arch Linux、FreeBSD ,其中包括(地狱,甚至 9front!)。

回到我开发对它的支持时,兼容性页面没有提到 NetBSD,感谢 Michael Forney,现在是!它的历史如下: – NetBSD 映像的初始版本是由 Drew Devault 创建的。 ——然而,他们并没有真正完成。 – 我开始努力完成它。 – Michael Forney 也同时开始研究它。他们的补丁已经成功上传! – 即使我们并行工作,我们的更改大多是等效的,因此,不需要将我的更改上传到上游: – 替换 anita(自动 NetBSD 安装程序)以直接下载二进制集并对虚拟磁盘进行分区。这样做是为了删除构建脚本上的 QEMU 要求,因为我正在 NetBSD VM 本身中构建映像,并且我不确定如何将嵌套虚拟化引入它。 – 用通过 pkgin 下载二进制包替换 pkgsrc 构建源包。 Zig CI 不依赖于上游平台的 LLVM 构建。相反,自建 LLVM 是通过 zig-bootstrap 项目创建的。有了它,您可以从 C 编译器转变为适用于任何架构的功能齐全的 Zig 编译器。它通过四个大型编译步骤(我将在本博文中为它们使用相关名称)来实现这一点: – 为主机系统编译 LLVM(“llvm-host”)。 – 为主机系统编译 Zig(“zig-host”)。 – 为目标系统编译 LLVM(“llvm-crosscompiled”)。 – 为目标系统编译 Zig(“zig-crosscompiled”)。例如,使用 aarch64-linux-musl 的 zig-bootstrap 结果,您可以启动 ARM64 Linux CI,其中 zig-crosscompiled 编译 Zig 的新提交。这是可能的,因为 Zig 也是 C 编译器。这是在 CI 脚本 ($CACHE_BASENAME) 中工作的此类工件的 Azure 上 Zig Linux CI 的脚本。对于 NetBSD,过程应该没有什么不同(FreeBSD 也遵循它),所以我们需要做的是让 zig-bootstrap 在其上运行。在andrewrk 和washbear 的帮助下,这个过程花了几天时间,以及必须周期性地“开始构建,找点事情做,精神上切换回修复构建”的漫长而艰苦的时刻。这一切都得到了回报,最后我生成了很棒的快速修复!他们都在这里!。提交的 PR 更像是一个关于我的解决方法可以做些什么的讨论场所,而不是将它们全部放入 zig-bootstrap(因为我很确定其中一些修复会破坏......所有其他目标,哈哈)。 builds.sr.ht 具有为作业构建运行程序的想法,因此您可以根据需要为 CI 动态添加更多容量。每个 runner 可以有 N 个 worker,这些是运行 QEMU VM 的那些。

当我们开始使用 FreeBSD 时,我们了解到单个 worker 需要 16GB RAM(实际编译要求大约为 8GB,但是有很多构建工件,VM 的文件系统由映像和不断增长的临时 ramdisk 组成) .随着时间的推移,我们了解到在 FreeBSD 方面,4 个构建工人可以处理 Zig 所需的吞吐量(截至 2021 年 7 月,这是真的)。要将 NetBSD 支持添加到组合中,我们必须向网络添加 4 个构建工作器,这意味着在我们的基础架构中的某处找到 64GB 的可用 RAM。 LavaTech 在混合云模型中运行:各种云提供商都有 VPS,但我们的大部分服务都运行在我们自己的共置硬件中,与通用编程共享。我们能够这样做是因为 Hurricane Electric 的免费 colo 交易。此外,与我们同行!一般来说,我们的机架有两个电源单元,一台大型妈妈电脑(我们亲切地将其命名为“laserjet”,它接近于“HP-LaserJet-Enterprise-MFP”)和一堆刀片。随着时间的推移,此列表(或本文中提到的基础设施)不会有硬性要求进行更新。这是 NetBSD 工作之前的 build runner 分配: – runner1:Laserjet VM(2 个工人) – runner2:Laserjet VM(2 个工人) 在与我们的通用编程朋友交谈之后,我们能够再分配两个 runner: – runner3:GP Blade(2 个工人) – runner4:GP Blade(2 个工人) 但是,随着时间的推移,它们变得不稳定,我们决定租用一台服务器来处理 FreeBSD 和 NetBSD: – bigrunner:Hetzner 租用的服务器(8 个工人)

runner1 和 runner2 已退役以减少 Laserjet 的负载。 runner3 和runner4 因为不稳定而退役。要记住的一项基础设施说明是,由于电源问题,我们并未使用机架中的所有可用刀片。 PSU 可以处理 20A 的最大电流,我们同时拥有它们,这样我们就可以保持冗余。即使我们有 40A 的理论最大电流可供使用,我们也只能使用 20A 的最大值,而且我们已经达到了这个限制。预兆:数据中心最近发生故障,导致其中一个 PSU 宕机,而我们的路由器并未同时插入这两个电源,从而导致中断。启动 NetBSD CI 时在构建网络中发生的一系列操作问题 该问题首先通过看到作业成功关闭并显示“Connection to localhost closed by remote host”来确定。这很糟糕(它应该失败,真的),但我们知道它与 OOM 杀手有关,就像我们之前遇到的那样。我们注意到受影响的跑步者的 RAM 使用率为 50%,即使没有 CI 作业在他们身上运行。在决定重新启动之前,我认为这是一些被诅咒的 I/O 缓存占用了 RAM(在检查空闲输出之后不是),我检查了 df,发现 rootfs 是一个 tmpfs,而不是像 ext4 这样的东西。原来我不小心将这些运行程序安装在 alpine livecd ramdisk 上。我没有运行 setup-alpine。当您进入 shell 时,motd 会告诉您运行它。我不知道我是怎么错过的。备份重要数据(构建日志)后,我能够正确地重新创建它们。因为我知道未来一定会创造更多的跑步者,所以我写了一个pyinfra脚本来预先设置一切,然后成为它的粉丝。哦,它发生了。

有时,与构建服务的连接只是通过 curl 超时:(7) 无法连接到 builds.hut.lavatech.top 端口 443:主机无法访问。丢包率很高,可能是因为我们的网络上游之一,但我无法深入研究这个问题。希望不是那么频繁。泊坞窗:来自守护程序的错误响应:冲突。容器名称“/builds_job_unknown_1626160482”已被容器“90c8a6c874840755e9b75db5b3d9b223fbc00ba0a540b60f43d78378efc4376a”使用。您必须删除(或重命名)该容器才能重用该名称。运行 QEMU 的 Docker 容器的名称在控制脚本中决定,这是相关的代码片段: 实际启动包含 CI 作业的 VM 的脚本是控制脚本,通常在 /var/lib/images/control (至少在使用 Alpine Linux Sourcehut 软件包时)。 Sourcehut 文档说,运行该脚本的用户应该被锁定为只能运行它,这样工作进程上的漏洞就不会导致权限提升。这是通过 doas 实用程序完成的,它是 sudo 的替代品。 doas.conf 文件中缺少参数使控制脚本在没有任何环境变量的情况下运行,因为缺少 BUILD_JOB_ID,它使用日期 +"%s"。这个问题直到 NetBSD 构建被合并到 Zig 后才出现。在此之前,我们只为每个 PR 生成一个作业:FreeBSD 作业。然而,在添加 NetBSD 之后,我们开始为每个 PR 生成两个作业。由于它们在时间上很接近,容器名称将相同,其中一个可以工作,另一个会因上述错误而崩溃。

一些虚拟机在它们的稳定时超时,但仅在机器负载不足时(例如在 Zig 项目中的合并狂潮之后)。运行 CI 作业时,会创建一个运行特定操作系统映像的新 VM,然后为其运行“解决”任务。该任务尝试通过 SSH 连接到虚拟机,运行 echo "hello world",如果可行,则发送其余命令(克隆存储库、运行 YAML 清单中声明的​​脚本等)。我们总是错过时间,所以当我连接到跑步者时,它很好而且稳定。但是到了副标题中提到的日期,我们能够实时捕捉到它,并且看到负载数接近 16,即使 runner 总共有 8 个内核。结果表明 CI VM 分配了 2 个内核,这是在控制脚本中硬编码的。所有这些都应该有一个状态页面和日志分析,以查找由操作错误引起的 CI 失败,而不是由于有人在正在进行的 PR 中编写不完整的代码而导致误报。