跳到正文
原文
Hacker News· tosh·· 25 天前精选AI 评分62

Oxide 推出 Virtual Oxide Labs(Voxel):在单台机器上模拟整机架

Virtual Oxide Labs: Emulating a Full Oxide Rack

AI 摘要

Oxide 发布 Virtual Oxide Labs(Voxel),用纯软件模拟栈在单台机器上复现完整 Oxide 机架,无需特殊测试版本。

正文 · AI 翻译

测试和为机架规模硬件开发带来的麻烦在于,机架规模并不容易获得。我的意思是,别提什么把 DDR5 当作货币的事情了;即使所有部件都大量供应,任何完成的机架都会迅速被前线(也就是我们客户的数据中心)抢购一空。

作为 Oxide 产品保障团队的成员,我发现这完全不公平,但也完全合理。所以我们必须有点创造力。我们必须能够:

  • 复制多样的客户网络拓扑结构

  • 运行我们的实际控制平面

  • 测试我们软件的最新版本

  • 代表硬件组件(毕竟我们生产机架)包括我们的交换机和微控制器

而且最关键的是:我们不需要发布特殊的“测试版本”发布。因为我们想保持我们的 CI 简洁,也因为那样做是不公平的。

现在,别误会——我们也不是没有窍门。去年我加入 Oxide 后,介绍给我的第一件事之一就是我们的 racklettes;也就是说,每个由四个滑轨和两个交换机构成的、比平均尺寸小的机架。这些 racklettes 可以像真正的机架一样初始化和运行,正是因为它们 是 真正的机架。只是它们不会以任何经济上可行的形式出售。但对于测试来说,它们效果极佳。我们今天有四个,很快将增加到八个,我们可以在那里测试任何硬件/软件组合的要求。

我们还拥有运行 Omicron 的各种模拟方法。您可以在 如何运行模拟 和完整的 运行指南 中了解更多信息。我不会在这里过多地详细说明,但足以说我们的控制平面(以及更多)的各个部分可以独立运行、协同运行,并以各种程度的模拟运行。

什么是体素?

你还记得 Q*bert 游戏吗?它是一款早期非常受欢迎的等距游戏,看起来有点像这样:

Q*bert, a bit of nostalgia in isometric gaming

我为什么要给你看一个荒谬的小游戏截图?嗯,图像中的每个立方体代表一个“体素”,即三维空间中的一个点。它实际上是像素的三维版本。体积元素 + 像素 = 体素。酷吧?

无论如何,对于 Oxide 来说,虚拟 OXidE 实验室(或 Voxel)意味着将极高的保真度集成到单个硬件中。它是我们先前虚拟实验室(称为 a4x2)的精神续作,后者使我们能够制作出在虚拟机中运行的不错机架。

请注意那个词“保真度”。因为,正如 Bryan Cantrill 所说,这是最高指令:实验室应该使用未经修改的原始 Oxide 软件。

这就是 Voxel 给我们的。一个基于 Rust 的、在 illumos 上构建的完整软件模拟堆栈,我们可以在其中构建和测试各种逼真的配置:

  • 由 Propolis 提供的滑轨虚拟化

  • 使用 Debian 和 FRR 的 BGP(编号和非编号)或静态网络

  • 通过 Oxide 自己的 SoftNPU,一个软件 P4 交换机 运行的 P4 交换机

  • 我们服务处理器(SP)和可信根(RoT)的微控制器模拟和仿真

而且,它还以相当小的占用空间提供了所有这些功能。在家里,我使用Minisforum MS-A2微型PC运行Voxel labs,配备32线程的AMD Ryzen 9 9955HX和96GB RAM。在那台电脑上,我可以轻松地启动6个sled,形式为一个机架或一个多机架配置(两个独立的机架,一个管理平面)。所有它需要的是Oxide Helios和一个速度不错的NVMe驱动器。启动后,大约10分钟你就有了一个完整的Oxide机架,每个“sled”都在自己的Propolis VM中——旁边还有交换机(也在Propolis中)和其他基础设施

图像创建

还记得我说的我们必须使用原版Oxide软件吗?实际上这让事情变得更容易。我们首先做的事情(遵循虚拟机工具中的常见做法)是创建一个可重复使用的快照

Oxide sleds永远只有两种东西:一个正常的sled(第一代的“gimlet”,当前的“cosmo”)或一个“scrimlet”,这是一个连接到边车交换机并运行交换机区域的sled。在它的复杂性中,一切都令人惊叹地优雅,你应该在我们的网络文档中了解更多

为了为Voxel创建快照,我们只需要创建一个“sled”(虚拟机),在它上面安装我们的控制平面(Omicron),在配置点之前快照它,然后:一个准备就绪的sled。控制平面可以通过两种方式安装:

从源代码

Voxel在GitHub上克隆Omicron仓库(或使用本地定义的源进行快速开发),并使用cargo来编译

从TUF仓库

我们使用Tufaceous使用The Update Framework(TUF)和我们的CI工具Buildomat来打包Omicron发布

以第二种选项为例,这里是过程的开始,voxel image create提取 sled 配置的关键部分:

$ pfexec ./target/debug/voxel image create --from-tuf ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip 5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c
[voxel] TUF source ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip: system version 23.0.0-0.ci+git5d176e0be7e, omicron 5d176e0be7e
[voxel] extracted 12 zones from ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip
[voxel] extracted 3 measurement corpus artifacts
[voxel] extracting host artifacts -> /export/home/helios/voxel-rawfix/target/debug/../../voxel-image/.tuf-host
[voxel] host boot image 1258295296 bytes, phase 1 rom 33554432 bytes
[voxel] extracted SP/RoT firmware -> /export/home/helios/voxel-rawfix/target/debug/../../voxel-image/.tuf-fw/ca9184058bd7

经过大量的解压和准备后,它将启动一个VM,所有内容都写入/安装到其中。最后:

[voxel] ready: voxel-cp version=unknown unpacked_artifacts=18 built=2026-09-06T21:37:58
[voxel] clearing device-instance map, halting
[voxel] flushing, up to 25s
[voxel] stopping hypervisor
[voxel] writing voxel image voxel-cp-5d176e0be7e-tuf

该图像是一个zfs快照,可用于并重复用于创建我们的机架

准备就绪,可以启动

启动机架使用类似的过程,只是控制平面已经安装。在这个阶段,我们只需要:

  1. 一个“frr”图像(用于网络的Debian VM)

  2. 一个控制平面图像(就像我们刚刚创建的那个)

  3. voxel-init代理,它为每个虚拟sled分配任务

日志输出相当长,但如果你愿意,你可以滚动查看。我会总结一下

就像我说的,很多步骤。这里发生了什么:

  1. 信任材料以sprockets PKI的形式生成

  2. 模拟的STM32H753和LPC55微控制器被Hubris固件烧录

  3. 我们的自动化网络拓扑套件(称为falcon)使用simnet和每个链路的VNIC对来连接拓扑,并准备sleds(本例中的g0、g1和g2)

  4. Propolis VM启动;本例中有6个,其中3个驱动网络,3个作为Oxide sleds

  5. voxel-init运行:每个节点上的轻量级Rust代理,

    • 设置NAT和上行地址

    • 在每个sled上配置底层设置

    • 在模拟启动磁盘上写入M.2分区布局,并标记五个U.2数据磁盘

    • 播种主机阶段2图像并将数据集安装到M.2上

    • 分配scrimlets并运行交换机强制执行程序,以便它们在重新启动时“记住”它们的身份

    • 激活sled-agent,控制平面的每个sled配置层

  6. 连接到wicketd的委员会API,这使机架通过其初始化阶段

这是一个复杂的步骤组合,而且我在整个“模拟微控制器”方面确实埋下了伏笔。但足以说,这些是任何氧化 racks 的完整且正确的步骤,只是硬件表示为不同的代码片段。例如,需要在两个虚拟 racks 之间添加一个新的互连?在 rack.rs 中添加几行代码即可。

有一些东西(目前)不遵循最高指令。例如,构成顶部 sprockets PKI 的那些证书不是在制造时设置的,也不是像我们的生产 racks 那样来自 RoT。但这是我们现在可以忍受的一种东西,因为它不影响实际的 rack 运行。

模拟服务处理器

这就是我刚才提到的那部分。我们 racks 的独特之处很大程度上在于每个 sled 中的嵌入式 RoT 和 SP。我们长期以来一直有一个 SpSim 结构,它作为服务处理器的桩,并在正确的时间返回正确的信息。但那只是模拟,不是模拟。它总是快乐的路径,而这对于真实的测试用例来说并不够精确。

因此,我们为 illumos 和 Linux(x86 和 ARM)构建了实际的模拟 Cortex-M7 和 Cortex-M33 设备。这个工具(sp-emu)的 仓库 是开源的。每个设备都在一个线程中运行,并且(基本上)忠实地模拟了三个主要要求:指令、时间和中断。

从那里开始,你可以直接将我们的 Hubris 固件刷新到这些模拟设备上,然后通过普通套接字进行通信。Voxel 实验室可以通过管理网关服务(MGS)和处理器间通信通道(IPCC)与这些虚拟设备通信。你可以通过 管理网关服务仓库 和 Hubris 文档 了解你想知道的一切以及更多关于这些概念的信息。

真的没有“用这个巧妙技巧将真实微控制器作为虚拟设备运行!”的东西。但我可以想到。但 sp-emu 的工作真正挑战是时间。记住我们那里运行的所有代码都是为硅片设计的。Hubris 按照每 1ms 的 SysTick 进行调度,而模拟器不使用墙上时间;因此,我们必须发明一个时钟。话说回来,模拟硬件也比真实的东西慢。所以……嗯,每单位时间的时间更少。因此,我们没有试图让时间变得复杂,时间变成了指令。例如,我给你 maybe_tick:

if self.systick == 0 {
self.systick = self.syst_rvr;
}
self.systick -= 1;
if self.systick == 0 {
self.systick = self.syst_rvr;
self.tick_events = self.tick_events.wrapping_add(1);
if self.syst_csr & 2 != 0
&& self.mode == Mode::Thread
&& !self.primask
{
self.exception_entry(15, bus); // SysTick exception
}
}

在这种情况下,我们根据内核自己的重载值来计算指令,并在单优先级设计使其安全的地方触发异常——在线程模式下,中断未屏蔽。同样地,外设访问消耗我们时钟的一个滴答(一条指令),而在硅片上,它可能会在总线上停滞几十个周期。

你可能注意到了 self.exception_entry,它在代码的其他地方定义:

fn exception_entry(&mut self, vecnum: u32, bus: &mut Bus)

简而言之,这个函数模拟了硬件在 Cortex-M 核心上触发异常时所做的所有事情,从“异常变为活动”到第一个处理程序指令。这里的 vecnum 是一个 ARMv7-M 异常编号(也是该异常处理程序向量表的索引)。bus 是用于实际推送框架和读取表的内存系统。

Hubris 使用非抢占式内核,尽管它会抢占式地调度任务。SVCall、PendSV、SysTick 和所有 IRQ 都运行在相同的优先级级别,而异常则保持更高的优先级。因此,模拟器不需要嵌套处理程序。它根据 PRIMASK、FAULTMASK 和 BASEPRI 的设置来处理挂起的 IRQ。如果其中任何一个挂起了 PendSV,它将执行内核的延迟上下文切换。有关其内部工作原理的更多详细信息,您可以查看 这份可爱的文档。

if let Some(irq) = bus.next_irq() {
if self.basepri == 0 || (bus.irq_prio(irq) as u32) < self.basepri {
bus.clear_pending(irq);
self.exception_entry(16 + irq as u32, bus);
return;
}
}
if bus.take_pendsv() {
self.exception_entry(14, bus);
}

这里还有很多可以讨论的内容,但总而言之,您可以看一下这个伪代码循环:

loop {
cpu.step(&mut bus, host.as_mut())?; // one Thumb-2 instruction
cpu.maybe_tick(&mut bus); // SysTick: time is instructions
cpu.maybe_interrupt(&mut bus); // NVIC and PendSV delivery
if cpu.cycles % 4096 == 0 {
bus.pump_eth(host.as_mut()); // MGS packets in and out
bus.pump_uart(host.as_mut());
}
}

注意:是的,我知道我说没有周期,但有名为 cycles 的字段。这是模拟的“先假装再实现”部分。

这是 sp-emu 的整体心跳,它无限循环运行。其他所有内容都是为了使这些调用看起来真实。

还有一件事

在模拟的 sled 和微控制器实际工作原理之外,一切都是老式的用户体验。当您在单个系统上运行如此多的组件时,试图弄清楚哪些组件在哪里、如何连接到一个机架上的 sled 与另一个 sled 等,可能会非常令人困惑。因此,我们用来启动和销毁虚拟机架的 voxel 命令也用作连接工具。例如:

\( pfexec ./target/debug/voxel host login g0
[voxel] ssh root@192.168.68.167 (g0 global zone)
The illumos Project helios-3.0.23976 May 2026
root@g0:~# zoneadm list
global
oxz_switch
oxz_internal_dns_5b07f3e7-444c-462c-9633-6967e52d6a3b
oxz_ntp_b308987c-3408-4077-b9a0-d803ed1e420a
oxz_cockroachdb_daa29fcd-fff0-4b3d-a722-b969e03398d9
oxz_cockroachdb_117986ee-8b2d-4567-8c6f-e01f3e63447e
oxz_crucible_310a91b5-1b67-46cb-b08d-ad6534702a3d
oxz_crucible_627c130b-97d6-4e8f-b13e-eb03b671c2a3
oxz_crucible_pantry_65e1b527-26fc-46fd-8584-d9792771926c
oxz_crucible_2692a74a-3a22-420c-857a-a3c786e4b600
oxz_external_dns_baa049eb-7bdd-4d8e-ace3-c0ef054845bd
oxz_crucible_2ffc1456-b0e1-4f8e-8b6d-6ab01377bd25
oxz_crucible_c5f7d004-9d9b-4741-9246-c4793b9c7976
oxz_nexus_c1a31871-bdb5-422c-b5b1-2de62aa31e30

该命令将用户直接登录到全局区域的 g0 sled。或者:

\) pfexec ./target/debug/voxel tp login switch0
[voxel] ssh root@192.168.68.167 -> zlogin oxz_switch (g0 switch0)
[Connected to zone 'oxz_switch' pts/3]
Last login: Sun Sep 6 22:17:52 from fdb0:a840:2500:
The illumos Project helios-3.0.23976 May 2026
root@oxz_switch:~#

在这种情况下,tp 像是我们的 技术端口,允许用户直接连接到交换机区域以进行机架范围的命令和网络。还有许多命令用于管理图像(导出/导入、删除)、配置(网络拓扑、sled 尺寸、多机架等)等,更多功能将在我们开发过程中陆续推出。

Voxel 可在 MPL 2.0 许可证下下载。您需要以 Helios 作为操作系统来运行 Voxel。我建议使用 16 个线程以上的线程和 64GB 以上的 RAM,再加上一个带有板载 DRAM 的高性能 NVMe。

来源:Hacker News · oxide.computer

00