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 把它推广到了整个系统:虚拟机存档、设置、池,一起在同一个按钮后面。等第一次真正抬格式的版本发出去、每个版本的升级回归跑起来,我们再来更新这篇。