English日本語

文章

keelOS: 系统升级,原来可以这么简单!

keelOS 的升级分两步。第一步换系统、试用:虚拟机不关机,存储池和虚拟机的存档都保持旧格式,觉得不对随时退回旧版本。第二步由你按下“确认升级”,这时才换成新格式,几秒钟完成,虚拟机照样不停。

先用两段话交代 keelOS。keelOS 是给家里或小办公室那一台服务器用的虚拟化系统,基于 FreeBSD 和 bhyve。UEFI 先加载一个约 100 KB 的 keel hypervisor(hyp),它常驻内存,保管虚拟机的内存、vCPU 状态和 IOMMU;FreeBSD 宿主(root)跑在它上面,是一个只读镜像,可以升级、重启。

宿主重启时,虚拟机先挂起,内存留在 hyp 里;新的宿主起来以后把它们恢复,虚拟机只暂停约 25 秒,然后原地继续,不关机,TCP 连接不断。升级系统,就是换一个镜像再做一次这样的重启。

为什么升级让人害怕

一台机器上跑着 NAS、路由器和 Windows 桌面,宿主一升级,它们全跟着停。keelOS 先解决了这一层:宿主重启时虚拟机只暂停。但还有第二层:新版本不好用,能不能退回去?

换回旧程序不难,难的是数据。新版本可能写下旧版本读不了的东西:

  • 虚拟机的挂起存档(checkpoint):新版本多存了几个字段,旧版本读不了。
  • 设置文件:废弃的写法被新版本改写,退回后旧版本看到的是改过的。
  • ZFS 池:新版本建的池或 zpool upgrade 过的池打开了新特性,旧版本导入不了。

这三件事过去都发生在新版本第一次启动的时候。用户还没来得及判断新版本行不行,后路已经断了。

我们自己就碰到了。2026 年 10 月 4 日,我们把 keelOS 的基础从 FreeBSD 14.5 换到 stable/15:15 的虚拟机存档多了一段 CPU 寄存器(MTRR),ZFS 从 2.2 到 2.4 多了 8 个池特性。在一台开发机上,带着一台开着的 Debian 虚拟机从 15 退回 14.5:14.5 读不了 15 写的存档,干净地拒绝了,虚拟机留在挂起状态,没有丢;再切回 15 才恢复。数据没坏,但这台虚拟机在旧版本上跑不起来。

别家怎么做

常见的虚拟化和 NAS 系统都能“回到上一个版本”,但回的只是程序。数据格式要么不管,要么只管 ZFS 一项。

系统 怎么升级 怎么回退 数据格式
Proxmox VE apt 升级,没有 A/B;大版本有检查脚本 没有官方回退(ZFS 根可以自己打快照) 文档提醒 zpool upgrade 之前先确认引导程序支持新特性,否则机器起不来
TrueNAS SCALE 每个版本一个 boot environment 启动时选回旧的 池有单独的“Upgrade Pool”按钮,单向;配置库在新版本第一次启动时就迁移了
ESXi 两个 bootbank,写备用的那个 开机时 Shift+R 回退不管 VMFS 和虚拟硬件版本;硬件版本每台 VM 单独、关机升级,不能回退
Unraid U 盘上换新文件,旧的留在 previous/ 网页上“Restore previous” 设置文件新旧都认,没有格式级别的概念

Proxmox、TrueNAS、ESXi 三行的说法来自它们的文档(链接在表里),Unraid 一行是我们的理解,没有逐条实测。

最接近我们想法的是 ZFS 自己。ZFS 的特性标志把“升级软件”和“升级盘上格式”分成两步:换了新的 OpenZFS,池还是旧格式,旧软件照样能导入;管理员运行 zpool upgrade 以后,新特性用到了才变成不可回退。我们的用户看到这一点,问整个系统能不能也这样。MongoDB 的 featureCompatibilityVersion、Kubernetes 的 --emulated-version 也是同一个思路:新程序先按旧格式工作,确认以后再抬格式。

keelOS 怎么做

两个槽:先写进去,再选时间切换

启动分区上有 A、B 两个槽,各放一个完整的系统。新版本只写进不在运行的那个槽,写完就停在“已写入”,不重启。什么时候切换由你选:现在,或者定一个时间(比如今晚 3 点)。一次无关的重启、断电,都留在当前的槽。

切换就是一次宿主重启:虚拟机挂起,新系统起来,虚拟机恢复。新槽只试一次,起不来 hyp 就回到旧槽。这次升级如果连 hyp 本身也换了,keelOS 自己判断要经固件重启一次,虚拟机先关机,开机后自动开回来,网页在切换前就说明。

试用:新程序,旧格式

每个 keelOS 版本带一个“格式级别”。只有这个版本会写出旧版本读不了的东西时,级别才加一;大多数版本不加,它们之间随时可以来回切。主机也记着一个级别:它的数据现在是哪一级的格式。

切到一个级别更高的版本,就进入试用。试用期间,新程序按主机的旧级别写:

  • 虚拟机存档:vmm.ko 和 bhyve 有一个“写的上限”,挂起时按旧版本号写。某台虚拟机的状态旧格式装不下,就拒绝挂起,虚拟机接着跑,绝不悄悄丢状态。
  • 设置文件:废弃的写法照读,但不改写。
  • ZFS 池:不 zpool upgrade;试用期间新建的池带 compatibility 属性,只开旧版本认识的那一组特性。
  • 新功能:需要新格式才存得下的功能先关着,网页列出“确认升级以后才有的”。
  • 虚拟 TPM:进入试用时每台虚拟机的 TPM 状态拷一份。退回时如果两边的 TPM 库(libtpms)版本不同,换回拷贝。

所以试用期间,旧槽里的版本随时能接手:它看到的存档、设置、池,都是它自己认识的。

两段式升级:写入、切换、试用,然后退回或确认
两段式升级:写入、切换、试用,然后退回或确认

试用是加了底色的那一格:从这里可以随时退回旧槽,也可以按下确认;只有确认这一步回不去。

退回:就是再切一次槽

觉得新版本不对,在升级页按“退回”。它和升级是同一个动作:切到另一个槽,宿主重启一次,挂起的虚拟机在旧版本上接着跑。试用期间新建的虚拟机、改过的设置也都在。

退回前还有一道检查。虚拟机都挂起以后、真正换系统之前,keelOS 拿每一份存档的版本号,对照旧槽的版本清单。有一份旧版本读不了,就取消这次切换,虚拟机在当前版本上恢复,网页说明是哪台、哪一部分。

确认升级:一个按钮,几秒钟

用了一阵子,没有问题,按“确认升级”。对话框只列这台主机上真会发生的事:哪个池启用新特性、以后挂起按新格式存、设置改成新写法、旧槽不能再启动。要勾上“我明白之后不能退回”才能按。

确认一共 9 步,每步记进启动分区上的日志,中途断电,下次开机从断点接着做。第一步先关门:读不懂新格式的旧槽改名成“已被取代”,hyp 再也不会启动它。这样后面任何一步做到一半,都不会有旧版本起来碰半迁移的数据。然后抬主机级别、取消存档的写上限、改写设置、给池换特性组并 zpool upgrade。

确认不停虚拟机,也不重启宿主。keelOS 从不替你自动确认:试用满 7 天,状态栏提醒一句,不弹窗。网页、命令行(keel upgrade --apply)和 MCP 走同一段代码。

虚拟机各自换代

主机确认升级,不等于每台虚拟机当场跟着变。已经在跑的虚拟机不被打断,各自在自己下一次冷启动时才换:

  • 存档格式:bhyve 的写上限是虚拟机启动时定的。确认以后,正在跑的虚拟机还按旧格式存,直到它下次重启。新版本本来就读得了旧的,没有坏处。
  • 新功能:试用期间关着的功能,确认后在每台虚拟机下次启动时用上,和 ZFS “用到时才生效”一个道理。
  • 每台只写自己需要的版本:存档里虚拟机那一段按每台的情况选版本。没开 Hyper-V 增强的写版本 1,开了的写 2,带第二步特性的写 3。用不到新东西的虚拟机,存档就不变。

还没有的,是 VMware 那样的“虚拟硬件版本”:在网页上一台一台选“升级到最新的虚拟硬件”。我们定的是第一版先不做。现在的规矩是:试用期间,现有的虚拟机看不到任何新的虚拟硬件,因为 Windows 看到新硬件会装新驱动、可能要重新激活,退回以后又是旧硬件。确认以后,标了“对 guest 无害”的改动在下次冷启动时生效,其余的只给新建的虚拟机。等第一次出现“不能对现有 Windows 虚拟机悄悄换”的改动,再给每台虚拟机加一个硬件级别,由你逐台确认。

实测结果

两段式升级在 2026 年 10 月 4 日写完,当天在 QEMU 里走了一遍流程,又在我们的一台开发机上用真的宿主重启测了一整套(UTC 10:32–10:50)。那时还没有真正抬格式的发布版本,我们做了一对只为测试的版本:级别 2 把存档里一个小部件的格式改了,级别 1 是当时的主线。机器上开着三台虚拟机:Debian 13、一台带 TPM 的 UEFI 虚拟机、试用中新建的一台。

测了什么 结果
级别 1 的主机切到级别 2(进入试用),虚拟机开着 宿主重启约 60 秒,三台都恢复,Debian 的 uptime 连着;主机级别还是 1
试用中挂起 存档按级别 1 的版本写
试用中退回、再试用,反复 旧版本恢复了试用中写的存档;15 分钟里 4 次宿主重启,Debian 一直没断
试用中新建一台虚拟机,退回再试用 两边都能跑
设置里带一个废弃的键 试用中没被改写;确认时删掉
确认升级,虚拟机都开着 1 秒,虚拟机不停;旧槽变成“已被取代”;之后挂起按新格式写
确认后再切回旧槽 拒绝(“cannot read this host's data any more”)
确认后手改启动配置指向旧槽,整机重启 hyp 找不到旧槽,启动新槽;虚拟机关机后自动开回
确认做到第 3 步时重启 开机后从第 3 步接着做完
确认后往另一个槽写旧版本 拒绝
存档里有一份旧版本读不了,还要退回 挂起后查出来,取消切换,三台在当前版本恢复,Debian 没断
状态旧格式装不下(手工压低写上限) 拒绝挂起,虚拟机接着跑

试用 → 退回 → 试用 → 确认 → 拒绝退回,整套有一个脚本,在 QEMU 和这台开发机上都是 PASS。带 TPM 的虚拟机在 QEMU 里测了“退回时换回拷贝”。

从 FreeBSD 14.5 换到 15.1

同一天(2026 年 10 月 4 日),keelOS 自己的基础从 FreeBSD 14.5 换到 15.1,四台测试机经两个槽从 14.5 升上去,另一台开发机直接装了 15。先在一台开发机上试:Debian 虚拟机开着从 14.5 切到 15,虚拟机接着跑,宿主 59 秒回来;加上写上限以后,试用中的 15 按旧格式存档,再退回 14.5,14.5 把挂起的虚拟机恢复了——正是前面那次拒绝的反面。P340 小主机从 14.5 切到 15 进入试用:池的 39 个特性一个没变,15 多出来的 8 个都是 disabled。

要说清楚的一点:14.5 → 15 这一次大版本,我们定了不要求存档跨版本兼容。正式切换时虚拟机先关机,在 15 上冷启动。挂起着过切换,是之后 15 → 15 的升级。

10 月 4 日 16:01–16:10(UTC),四台开发机和 P340 小主机一起换到同一个 15.1 镜像:写进另一个槽、切换,旧版本留在原来的槽里。其中一台是从 14.5 直接上来的,另外四台已经在早一些的 15 上。

机器 切换时 结果
开发机 4 台虚拟机开着 宿主 32 秒回来;重启回归脚本全 PASS,虚拟机停顿 14.5 秒,时钟差不超过 0.27 秒
开发机 5 台虚拟机开着 35 秒回来;5 台都从存档恢复
开发机 Debian 虚拟机开着,试用中 约 40 秒;虚拟机挂起、恢复
开发机 14.5 → 15,没有在跑的虚拟机 41 秒回来;之后冷启动一台虚拟机试过
P340 小主机 显示虚拟机在跑 主机屏幕上是新版本的控制台

每台切完都查了:系统是 FreeBSD 15.1-STABLE KEEL,池 ONLINE,这次开机的内核日志里没有 panic,网页正常应答。五台都正常升级,没有一台出问题。其中两台当时还在试用中,没有确认。

为什么每次升级都要跑回归

主机升级有时会影响虚拟机。换到 FreeBSD 15 那天,开发机上的回归就查出一个:15 的 bhyve 库把虚拟机内存排得更紧,和 keel 内存池里 3G–4G 的空洞撞上,内存大于 4 GB 的虚拟机跑几十秒就报磁盘 I/O 错误、停掉。当天修好,没有进发布版。反过来也要分清楚:2026 年 9 月 30 日两台开发机换了新的 vmm.ko、bhyve 和 keel,第一台 14 台虚拟机逐台重启、挂起恢复全过,第二台遇到的三个问题都拿升级前的 vmm.ko 对照过,是早就有的,不是这次引入的。

所以我们计划:每个版本发布前,在实验室跑一轮升级回归。支持列表上的每一种 guest 都从安装开始,装在上一个版本上,再带着它们走一遍升级、试用、退回、确认。目标是列表上的每一个 guest,都在你升级之前已经在我们这里跑过一遍。这还是计划,没有做。

还没做完的

  • 第一次真正的格式升级还没发生。 真机上测的是只为测试的级别 2。这次 14.5 → 15,我们决定让虚拟机先关机、在 15 上冷启动,不要求 14.5 和 15 互读存档;两段式留给以后的 15.x → 15.y、16.0。
  • 确认时给池换特性组、zpool upgrade 的那一步没在真机上走过:测试的两个版本用的是同一组特性。
  • 网页上的试用卡片和确认对话框只在 QEMU 里截图看过,开发机上走的是命令行。
  • 挂起被拒绝时,keel 要等满 60 秒才报失败:虚拟机本身没事,但失败没有马上传回来。
  • 每台虚拟机的虚拟硬件版本没做,见上一节。
  • 多台主机一起确认没做:现在每台自己确认,等主机间热迁移做了再说。
  • 每个版本的升级回归是计划:每种 guest 从安装开始、带着走一遍升级的那一轮还没有建起来;“装好的、带非默认设置的主机”的升级测试也还没有脚本。
  • keelOS 还在开发中,没有正式发布。

结语

升级简单,是因为不可逆的事被挑出来、放到了最后。换系统不关虚拟机;试用期间数据保持旧格式,退回就是再切一次槽;只有你按下“确认升级”,格式才往前走,而且只要几秒。

我们自己先用上了:keelOS 从 FreeBSD 14.5 换到 15.1,五台测试机最后都经两个槽换到同一个镜像,没有一台出问题。

这个点子来自 ZFS:先升级软件,再升级格式。keelOS 把它推广到了整个系统:虚拟机存档、设置、池,一起在同一个按钮后面。等第一次真正抬格式的版本发出去、每个版本的升级回归跑起来,我们再来更新这篇。

← 文章 · 为什么选 keelOS