Hacker News 中文摘要

RSS订阅

Virtual Oxide Labs: Emulating a Full Oxide Rack

机架级硬件不好测,因为整机柜设备难得。且不说DDR5被当货币,即便零件充足,组装好的机架也会立刻送进客户数据中心。作为Oxide(Oxide Computer Company)产品保障团队一员,我觉得这不公平却合理,必须另想办法:复现网络拓扑,运行控制平面,测试最新版软件并还原硬件,且不发测试版。我们有小型机架(racklettes),每台四个 sled 和两个交换机,能像真实机架一样运行,团队共有四台。

正文

测试和开发面向整机柜规模的硬件,麻烦在于整机柜本身并不那么容易弄到。先不说 DDR5 像货币一样难搞这件事;就算所有部件都供应充足,任何组装完成的机柜也会迅速被送往前线——也就是我们客户的数据中心。

作为 Oxide 产品保障团队的一员,我觉得这不合理但完全能理解。于是我们得有点创意。我们必须能够:

  • 复现客户多样的网络拓扑
  • 运行我们真实的控制平面
  • 测试最新版本的软件
  • 呈现硬件组件(毕竟我们是做机柜的),包括我们的交换机和微控制器

最锦上添花的是:我们还得在不切出特殊“测试版”发布的前提下做到这些。因为我们想让 CI 保持精简,也因为那样做不公平。

别误会,我们并非没有办法。我去年加入 Oxide 时,最早接触到的东西之一就是我们的 racklettes——也就是比标准更小的机柜,每个有四个 sled 和两个交换机。这些 racklette 可以像真实机柜一样初始化和运行,正因为它们本身就是真实机柜,只是形态上不适合拿来卖。但用于测试,它们非常好用。我们现在有四个,很快会增加到八个,可以在上面测试软硬件需求的任意组合。

我们也有各种运行 Omicron 的模拟方法。更多内容可以看 How to Run Simulated 和更完整的 How to Run Guide。这里不展开太多,只需知道:控制平面的大部分组件(以及更多东西)都可以独立运行、协同运行,并以不同程度的模拟来运行。

什么是 Voxel?

还记得游戏 Q*bert 吗?它是最早风靡一时的等距视角游戏之一,看起来有点像这样:

Q*bert,等距游戏里的一点怀旧

为什么给你看一张小得离谱的游戏截图?因为那张图里的每个立方体都代表一个 “voxel”,也就是三维空间中的一个点。它其实是像素的三维版本:Volume Element + Pixel = Voxel。是不是很巧?

对 Oxide 来说,Virtual OXidE Labs(简称 Voxel)意味着把极高的保真度塞进一台硬件里。它是我们此前虚拟实验室(代号 a4x2)的精神续作,后者让我们能在虚拟机里拼出像样的机柜。

顺便留意一下“保真度”这个词。正如 Bryan Cantrill 所说,这是首要原则:实验室应该使用未经修改的原生 Oxide 软件。

Voxel 给我们的正是这个。它是一套构建在 illumos 上、用 Rust 写成的完整软件仿真栈,我们可以在其中构建和测试各种真实配置:

  • 由 Propolis 提供的 sled 虚拟化
  • 使用 Debian 和 FRR 的 BGP(编号和未编号)或静态网络
  • 通过 Oxide 自己的 SoftNPU,一款软件 P4 交换机 实现的 P4 驱动交换机
  • 针对我们的服务处理器(SP)和信任根(RoT)的微控制器模拟与仿真

而且,它在相当小的体积里提供了所有这些能力。在家里,我用一台 Minisforum MS-A2 迷你主机跑 Voxel 实验室,配置是 32 线程的 AMD Ryzen 9 9955HX 和 96GB 内存。在这台机器上,我可以轻松启动 6 个 sled,既可以组成一个机柜,也可以组成多机柜形态(两个独立机柜,一个管理平面)。它只需要 Oxide Helios 和一块足够快的 NVMe 硬盘。启动后大约 10 分钟,你就拥有了一个完整的 Oxide 机柜:每个 “sled” 都在自己的 Propolis VM 里,旁边还有交换机(同样在 Propolis 中)和其他基础设施。

镜像创建

前面说过我们得用原生 Oxide 软件,这其实让事情更简单。我们做的第一件事(沿用了虚拟机管理程序工具里的常见做法)是创建一个可复用的快照。

Oxide 的 sled 只有两种:一种是普通 sled(第一代叫 “gimlet”,当前代叫 “cosmo”),另一种是 “scrimlet”,也就是连接到 sidecar 交换机、运行 switch zone 的 sled。这套设计复杂得惊人却又极其优雅,更多内容可以看我们的网络文档。

为 Voxel 创建快照时,我们要做的就是先创建一个 “sled”(虚拟机),在上面安装控制平面(Omicron),在配置点之前做快照,然后就好了:一个随时可启动的 sled。控制平面有两种安装方式:

  • 从源码:Voxel 克隆 GitHub 上的 Omicron 仓库(或使用本地定义的源码以便快速开发),并用 cargo 编译。
  • 从 TUF 仓库:我们用 Tufaceous 借助 The Update Framework(TUF)和 CI 工具 Buildomat 来打包 Omicron 发布版本。

以第二种方式为例,流程一开始是用 voxel image create --from-tuf 把构成 sled 配置的关键部分拉出来。运行 pfexec ./target/debug/voxel image create --from-tuf ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip 5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c 后,日志会显示 TUF 源、system version 为 23.0.0-0.ci+git5d176e0be7e、omicron 为 5d176e0be7e,接着提取 12 个 zone、3 个 measurement corpus 工件、host 工件(其中 host boot image 为 1258295296 字节,phase 1 rom 为 33554432 字节),以及 SP/RoT 固件。经过大量解包和暂存后,它会启动一个 VM,把所有东西写入并安装进去。最后日志显示 ready: voxel-cp version=unknown unpacked_artifacts=18 built=2026-09-06T21:37:58,然后清空 device-instance map、停机、刷写(最多 25 秒)、停止 hypervisor,并写出 voxel image voxel-cp-5d176e0be7e-tuf。这个镜像是一个 zfs snapshot,可以反复用来创建机柜。

准备启动

启动机柜的过程类似,只是控制平面已经装好了。此时我们需要:

  1. 一个 “frr” 镜像(用于网络的 Debian VM)
  2. 一个控制平面镜像(就像我们刚创建的那种)
  3. voxel-init 代理,为每个虚拟 sled 分配任务

日志输出很长,这里直接概括步骤。实际发生的事情是:

  1. 以 sprockets PKI 的形式生成信任材料
  2. 模拟的 STM32H753 和 LPC55 微控制器刷入 Hubris 固件
  3. 我们的自动网络拓扑套件(称为 falcon)用 simnet 和 VNIC 对为每一条链路接线,并暂存 sled(本例中是 g0、g1 和 g2)
  4. 启动 Propolis VM:本例共 6 个,其中 3 个驱动网络,3 个作为 Oxide sled
  5. voxel-init 运行:这是每个节点上的轻量 Rust 代理,负责
    • 设置 NAT 和上行链路寻址
    • 在每个 sled 上配置 underlay
    • 在模拟启动盘上写入 M.2 分区布局,并标记五个 U.2 数据盘
    • 把 host phase 2 镜像和 install dataset 播种到 M.2
    • 分配 scrimlet,并运行一个 switch enforcer,让它们在重启后仍“记得”自己的身份
    • 激活 sled-agent,也就是控制平面在每个 sled 上的配置层
  6. 连接 wicketd 的 commission API,让机柜走完初始化阶段

这是一串复杂的步骤,我前面其实把“模拟微控制器”这个重点埋掉了。但这些就是任何 Oxide 机柜都要走的完整且正确的步骤,只不过硬件被表示成了独立的一段段代码。比如,想在两个虚拟机柜之间加一条新互连?在 rack.rs 里加几行代码就行。

还有 少数 几件事目前还没遵守首要原则。例如上面构成 sprockets PKI 的那些证书,并不像生产机柜那样在制造时设定、并来自 RoT。不过这一点我们暂时可以接受,因为它不影响机柜实际运行时的行为。

模拟服务处理器

这正是我前面提到的部分。我们的机柜之所以独特,很大程度在于每个 sled 里都嵌了 RoT 和 SP。我们很早就有 SpSim 这个构造,它作为服务处理器的桩,会在正确的时机返回正确的信息。但那是模拟,不是仿真。它永远只走 happy path,对真实测试场景来说保真度不够。

因此我们为 illumos 和 Linux(x86 与 ARM)构建了真正仿真的 Cortex-M7 和 Cortex-M33 设备。这个工具(sp-emu)的仓库是开源的。两个设备各自运行在单线程里,并(大体上)忠实地仿真三个核心需求:指令、时间和中断。

在此基础上,你可以把我们的 Hubris 固件直接刷到这些仿真设备上,然后通过一个普通的 socket 通信。Voxel 实验室可以通过 Management Gateway Service(MGS)和 Inter-Processor Communication Channel(IPCC)与这些虚拟设备对话。想深入了解这些概念,可以看 Management Gateway Service 仓库和 Hubris 文档。

我实在想不出有什么“一个巧妙技巧就能把真实微控制器当虚拟设备跑”的说法。但 sp-emu 真正的挑战是时间。别忘了,我们跑在那里的代码本来是给硅片用的。Hubris 基于 1ms 的 SysTick 调度,而仿真器并不使用墙上时钟,于是我们不得不发明一个时钟。而且仿真硬件也比真实硬件 更慢,所以可说是“每单位时间里的可用时间更少”。与其在时间上较真,我们干脆让时间变成指令。以 maybe_tick 为例:它先看 systick 是否为 0,是则重载为 syst_rvr;随后 systick 减 1,减到 0 时再次重载并让 tick_events 加 1;如果 syst_csr 的第 1 位非零、处于 Mode::Thread 且 primask 未屏蔽,就调用 exception_entry(15, bus) 触发 SysTick 异常。也就是说,我们把指令数和内核自己的重载值对照着计数,并在单优先级设计允许的安全位置触发异常——也就是线程模式、中断未屏蔽的时候。同样,外设访问在我们的时钟里消耗一个 tick(一条指令),而在真实硅片上可能因为总线停顿几十个周期。

你会注意到 self.exception_entry,它在代码别处定义为 fn exception_entry(&mut self, vecnum: u32, bus: &mut Bus)。简单来说,这个函数模拟了 Cortex-M 内核在发生异常时硬件所做的一切,从“异常变为活跃”一直到第一条异常处理指令。vecnum 是 ARMv7-M 的异常号,也就是该异常处理函数在向量表中的索引;bus 是内存系统,用来实际压栈帧并读取向量表。

Hubris 的内核不可抢占,但任务调度是抢占式的。SVCall、PendSV、SysTick 和所有 IRQ 运行在同一优先级,而 fault 保持更高优先级。因此仿真器不需要嵌套处理函数。它根据 PRIMASK、FAULTMASK 和 BASEPRI 的设置来处理挂起的 IRQ;如果其中某个挂起了 PendSV,就会执行内核的延迟上下文切换。更多内部细节可以看这份不错的文档。在中断投递部分,逻辑是:如果有挂起 IRQ 且优先级高于 basepri,就清除挂起并调用 exception_entry(16 + irq, bus);之后如果 take_pendsv 取到 PendSV,就调用 exception_entry(14, bus)。

概括起来,sp-emu 的主循环大致是:循环执行 cpu.step 跑一条 Thumb-2 指令,然后 cpu.maybe_tick 把 SysTick 当作指令时间来推进,再 cpu.maybe_interrupt 处理 NVIC 和 PendSV 投递;每 4096 个 cycles 就调用 bus.pump_eth 收发 MGS 包,并调用 bus.pump_uart。没错,我知道我前面说过“没有 cycles”,这里却有个叫 cycles 的字段——这就是仿真里“先假装,直到成真”的那部分。

这就是 sp-emu 的整体心跳,它会一直跑下去。其他一切存在的意义,就是让这些调用看起来像真的一样。

还有一件事

除了仿真 sled 和微控制器的实际运作之外,剩下的就是再普通不过的用户体验了。当你在一台系统上运行那么多组件时,很容易搞不清各个部件在哪儿、怎么连到某个机柜的某个 sled 而不是另一个,等等。所以我们用来启动和销毁虚拟机柜的 voxel 命令,也充当了连接工具。例如:

执行 voxel host login g0 会直接把用户登录到 g0 sled 的 global zone。登录后用 zoneadm list 可以看到 global 以及一批 oxz_ 区域,例如 oxz_switch、oxz_ntp、多个 oxz_cockroachdb 与 oxz_crucible、oxz_nexus 等。

另一种方式是执行 voxel tp login switch0,它会先 ssh 到主机,再 zlogin oxz_switch,进入 g0 的 switch0 区域。这里的 tp 就像我们的技术端口,让用户能直接连到交换机区域,执行整机柜范围的命令和网络操作。此外还有很多命令用来管理镜像(导出/导入、删除)、配置(网络拓扑、sled 规格、多机柜等),以及更多我们正在开发的功能。

Voxel 已基于 MPL 2.0 许可协议发布,可以下载。要运行 Voxel,你需要使用 Helios 作为操作系统。建议至少 16 线程、64GB 以上内存,外加一块带板载 DRAM 的高品质 NVMe 硬盘。

评论

讨论气氛积极且偏动手向:大家纷纷分享用 whoah-cli 或家庭实验室裸机来模拟、启动 Oxide(Helios/Omicron)栈的经验,普遍认可这类仿真对开发和测试的价值,也有人提醒旧硬件上性能一般且空闲占用偏高。主轴不是否定模拟,而是“先靠模拟解馋”与“官方应把内部小型 racklettes 开放给开发者和家庭实验室”之间的落差,后者呼声正变强。

认为在家模拟或裸机跑 Oxide 栈值得尝试且对开发测试很有价值

  • 社区已经出现 whoah-cli 之类的工具来模拟整套虚拟化栈,也有人在家庭实验室的老服务器上直接裸机启动 Helios 与 Omicron;即便性能一般、空闲时处理器占用偏高,大家仍觉得能上手摸索很有意义,长期更看重它对构建速度和质量保障的助益。(较多人) > 我们意识到必须能模拟升级,这虽带来复杂度但值得;自动化测试让我们更快构建更好的东西,并发现问题、回归、验证新功能。(@happyPersonR)

认为官方应把内部小型 racklettes 开放给开发者和家庭实验室

  • Oxide 内部已有 racklettes,它们是比平均更小的机架,每个有四块服务器托架和两台交换机,能像真实机架一样初始化和运行,只是规格形态不适合售卖;社区一直渴望这种缩小版真实硬件,如今知道它存在后,要求向开发者和家庭实验室开放的呼声会更高。(较多人) > 每次 Oxide 发帖都有人热切盼望这种小型机架;现在知道它真的存在,要求官方提供给开发者和家庭实验室的声音只会更大。(@qchris)

认为搭配超大容量 SSD 与类 VAST Data 软件会更酷

  • 只有个别人顺带提出,若 Oxide 机架用上 245TB 的 SSD 并配合 VAST Data 风格的软件会很有吸引力,但这只是配置上的畅想,未展开进一步讨论。(少数人)

有价值的补充

  • 实测显示,在双路至强 2.6GHz、32 核、256GB DDR4 的老服务器上裸机运行 Helios 与 Omicron 能成功启动,但性能并不好,空闲时处理器占用约 30%,说明家庭实验室旧硬件跑这套栈会有明显开销。