Unikernels 曾经很难,AI 正在改变这一点
Unikernels were hard. key word: were
曾参与 MirageOS 和 Unikernel Systems 的 Justin Cormack 注意到人们正重新发现 unikernel。作者认为 unikernel 过去难在缺少存储等库、需从 NetBSD 拉驱动,而如今这些概念已进入模型权重,缺库可直接用 AI 把 Go 库移植到 OCaml,或用 agent 在几小时内用 Rust 重写 mkfs.xfs 并做到字节级一致。
Justin Cormack 当年参与过 MirageOS 和 Unikernel Systems,他和我一样注意到了这个现象:人们又开始发现(或重新发现)unikernel 了。他在自己的 newsletter 上就此展开了一系列对话,我是第一个受访者。他给我发了邮件,五分钟后我就在酒吧里一手端着大杯啤酒跟他聊上了。我们深入探讨了 Mirage、Orleans、Haskell、Nix、Cursed 以及更多内容。
以下是这次对话的要点整理,底部附有章节链接,方便你跳转到对话的特定部分。Justin 编辑过的文字稿在 Ignore Previous Directions 上。
Unikernel 曾经很难。关键词:曾经。现在我们有了 AI。
什么是 unikernel,以及它们为什么曾经很难
我第一次接触 unikernel 大约是在 2015 年。当时我组建了一支 Haskeller 团队,在函数式编程上钻研得相当深。哪里有 Haskeller,哪里就有 OCaml 程序员,从那里你就会发现 MirageOS。想法很棒。我那时候就玩过。
Unikernel 的理念是:你的应用就是操作系统。没有用户态。如果你想要一个 Web 服务器、DNS,或者发送邮件,没有什么可以 fork 或 spawn 的东西。你必须把这些东西作为库写进你的应用里。
这就是摩擦所在。Justin 记得很清楚:他们当年构建 Mirage 时,有 TCP 栈和 HTTPS 栈,但存储方面几乎什么都没有。他们从 NetBSD 里把驱动抽出来,因为可以在用户态运行它们。那时候真的很难。
我们这个行业里有很多教条。Nix 很难。Bazel 很难。Unikernel 很难。没错,它们曾经很难。这些难懂的概念如今已经存在于模型权重之中。你只需要用提示词把它们调出来,并且在认知上摆脱“它们很难”这个教条。
操作系统是设计债务
每一个不是 unikernel 的应用,都是建立在这样一个假设之上的:先有一个应用,然后底下有一个操作系统。我们为什么会有操作系统?因为四十年前有一个人类操作员。我用过 IBM 5250、AIX、Solaris 和大型机。多用户操作系统的存在是因为有个人坐在它面前,然后我们把应用放在上面。
我认为这是设计债务,而它现在之所以重要,原因如下。应用会被攻破。在 AI 出现之前它们就已经被攻破了。有人攻破了用户态应用,拿到了一个 shell。那个 shell 就是为数据外泄服务的 VIP 管家。
有了 unikernel,攻击面要小得多。如果功能不在应用(也就是操作系统)里,那攻击者就无计可施了。没有下一跳。
Justin 在这里提出了反驳,而且很有道理。攻击面缩减这件事,人们的认识非常模糊。你可以从 Linux 容器里移除 shell,但几乎每个 Linux 环境里仍然有某种实际上等同于解释器的东西。你可以在没有可写文件系统的情况下执行新程序。你仍然要担心内存安全和 gadget。
都没错。但看看我们这二十六年来一直在做什么。我印象中最早从 SunOS 和 cgi-bin 时代流传下来的格言是“不要把编译器放到生产环境上。”然后是构建容器和生产容器。再然后是 Chainguard。我们一直在一点点削减攻击面,而不是反其道而行之,去确保根本不存在攻击面。
而这一点正是人们忽略的:如果没有 shell 也没有解释器,模型权重里就没有任何东西知道接下来该做什么。这把一次顺手攻击(随便挑一个你所用框架本周的 RCE,你就拿到了一个 shell,而模型权重知道该拿 shell 访问权限做什么)变成了一次需要你源代码的定向攻击。
你现在就可以把缺失的库移植过来
经典的反驳:你的 unikernel 需要和 Stripe 通信,而 OCaml 没有 Stripe 库。在 AI 之前,你会叹口气然后自己写。现在呢?跑一个循环把 Go 库移植到 OCaml。给你:unikernel 里的 Stripe。
Justin 有一个同样情况的好例子。他一直在为设备构建最小化的 Linux OS 镜像,这其实已经算是半个 unikernel 了,因为你只把一个应用作为 PID 1 运行。他需要创建一个 XFS 文件系统。与其引入 xfsprogs 以及它带来的一切,他和一个 agent 坐下来,让它用 Rust 写 mkfs.xfs,产出逐字节完全相同的输出,并且每个标志都被解释。它逐个逆向工程了磁盘上的格式,并跨块大小做了测试。花了他几个小时。
这之所以可行,是因为原始工具是一个黄金标准。用两种实现生成不同大小的文件系统并对比它们。把测试移植过来。自动化它。
移植软件已经有一阵子是小菜一碟了。下面是你该怎么做。
如果你有一个标准参照,移植就是一个循环。
Geoffrey HuntleyGeoffrey Huntley
存储是另一个大缺口。如今大多数工作负载都是云形态的,即使在本地也是如此,所以采用 turbopuffer 的方式:以 S3 作为你的主存储、可无限增长的存储,配一个本地 NVMe 块缓存和一个用于热数据的 LRU(或者你喜欢的任何缓存算法)。Justin 也是“一切皆用 S3”的狂热粉丝。只要延迟不是约束,你就能获得可多用户访问的无限存储,并且可以在其上构建一切。
nix 机器测试与 overlay
Justin 也一直在试验 Nix,并且很惊讶地发现,他第一次让一个 agent 原型化一个 OS 时,它把所有测试都构建进了 flake 里。
Nix 这门语言很烂。Nixpkgs 很棒。NixOS 机器测试则是顶呱呱:你写一个测试,启动一组机器,并演练你的网络规则与应用之间的交互。这是人们不知道的东西。
而当上游有东西坏了,或者你的依赖里有供应链问题时,那不过是一个 overlay 的事。
这个世界还没搞明白,你其实可以直接用 Nix overlay 修复一切
修补世界。
Geoffrey HuntleyGeoffrey Huntley
如果你必须工具调用一个人类,也就是“亲爱的维护者”,他可能正在度假,或者可能已经放弃了项目,然后等一天、两天,甚至五分钟,那就不算 AGI。
我们在这里构建的是递归式产品。Agent 需要有能力把世界作为第一方源代码来修改,而不是作为第三方打包的二进制文件。我们要回到 contrib 文件夹和 Unix 补丁。
如果你关心安全,你有两个选择
Justin 问,unikernel 还需要什么才能让人们发现它们。老实说,就是这个。我们培养了两代开发者,他们甚至不知道有这种东西存在。
如果你深切关心安全,那真的只有两个选择:
- 你要把卫星送入太空,可能应该使用 seL4,一个经过形式化验证的操作系统。(我对澳大利亚政府解散那个团队仍然有点耿耿于怀。)
- 其他所有人都应该认真考虑 unikernel。别再试图加固一个非常容易被攻破的东西了。反过来,从另一个方向来设计。
另一个经典的批评是,许多早期的 unikernel 设计把所有东西都运行在单一特权级别上:你的应用程序和操作系统处于同一个环。到了 2026 年,如果你想要环隔离,这只是一个提示词就能解决的问题。它肯定比祈祷你的 systemd cgroup 配置是正确的要安全得多。
想想每次 Linux 里出现新漏洞时,企业要花多少时间来给全世界打补丁。上游现在期望你每周都给内核打补丁。就在我们录制的那一周,有人攻破了 KVM(本质上就是 Firecracker,我们所有人都以为那是很好的沙箱的核心原语),从 Vercel 和其他几家厂商那里拿到了 50,000 美元。对于一种可能 root 掉全世界所有托管云提供商的东西来说,这钱可不算多。
spaceleans:一个分布式 unikernel 操作系统
大约七个月前,我深入研究了 unikernel,以检验我的心智模型是否正确。我给 Justin 看了我的 Mirage 文件夹,里面装着我需要添加的所有功能。
- 一个 unikernel 集群没有办法保持时间同步,所以我从另一种语言里拿了一个 NTP 客户端并移植了过来。然后我基于 RADclock 构建了一个 NTP 服务器,并借鉴了 TigerBeetle 如何处理时间 的思路:不是单一时钟源,而是多个时钟源,打包成一个库。
- 网络栈、DNS、HTTP 客户端和服务器、结构化日志、OTel、Anthropic 和 OpenAI 客户端,以及通过 Airwallex 进行的支付。
- 一个通用的重试库,用于处理 HTTP 上的背压,外加一些 PPX 元编程作为消遣。
- 在日志边界处的一个 PII 包装器,这样机密和 PII 就永远不会通过日志子系统泄露。每个项目都应该有一个。它是可插拔的;用一个函子就行。
然后是最疯狂的东西,我之前从未给任何人看过:Spaceleans,把 Microsoft Orleans 移植到 OCaml,作为 unikernel 运行。
Orleans 是一个带事务的分布式 actor 系统。你把许多物理机器合并成一个可寻址的堆。一个 actor 始终存在:await GetCustomer(),如果它不在内存中,就会从可插拔的存储提供程序重新水合。你把 n 层架构折叠成 actor,不再关心某个东西是在机器 A、B、C 还是 D 上。运行时把它作为基础设施原语来处理。
所以从某种奇怪的意义上说,我用 actor 构建了一个分布式 unikernel 操作系统,上面还有一个文件系统。Justin 称它像 Erlang,他说得对。我一周之内就做完了这一切。我可能永远不会发布它,但它证伪了 unikernel 很难这个想法。
采样历史
年纪稍大一点意味着你可以采样历史,就像一个经验丰富的 DJ,比如 Carl Cox,他在这个圈子里待得够久,能够从以前的曲目中取材并把它带向前。所有这些想法在八十年代就存在了。模型已经读过那些论文。它们的训练数据里有 TAPL 和最先进的类型理论。
缺少的是人们的好奇心和雄心,去做这些疯狂的事情,以及知道存在以前的唱片可以采样。
我们怎么让人们尝试这些东西?我们就直接做。如果你有一辆涡轮增压兰博基尼外星太空火箭,更高效、更安全,那对你有好处;你占了先机。做酷炫的事,吸引好奇的新人,指导他们,成长。一如既往。
与此同时,其他人会试图用 Chainguard 来保护他们的 Ruby on Rails 应用,并让五十名 AWS 认证工程师来管理 AWS,而实际上两个懂 Nix 和 Hetzner 的人就够了。最终,这归结为钱。更强大的工具更高效,而效率会胜出,尤其是在 AI 压缩利润空间的情况下。
ocaml、rust、haskell 与背压
OCaml 的时代又回来了吗?它依然势头强劲。某家交易公司用得非常好。年初我和 Yaron 碰面时,我问他 OxCaml 的存在是不是为了让他们的语言扩展进入训练数据,从而提升整个公司。我得到了一个非常“无可奉告”的微笑。
对于智能体来说,OCaml 很可爱。模块之间的函子是美丽的。.mli 文件——一个解释模块应如何工作的类型化头文件——对智能体来说是非常高效的上下文。opam 和 Dune 确实很好。Hindley–Milner。而且编译时间很快。我觉得现在没有理由再用 F# 了。
Justin 主要写 Rust,而智能体擅长 Rust。但编译时间是背压的税。LLM 会产生幻觉,当编译很慢时,每次幻觉都很昂贵,因为每分钟能尝试的次数更少。Justin 的 S3 克隆大约有一百万行 Rust;四个智能体同时编译时,它们会争夺磁盘和 CPU。你最终在快速机器上花的钱比在 token 上还多。
Haskell 的类型系统很棒,模型也做得非常好。但我不太放心在生产环境运行它:空间泄漏存在于运行时状态空间中,只在生产环境才会显现。Justin 指出,Rust 中的线性类型思想正是源自试图解决这个问题的 Haskell 论文。然后是 Zig 的方法:预先分配一切,之后再也不分配,这正是八十年代和九十年代游戏开发者的做法。不过,很难说服智能体在 Rust 中这样做,因为常量内存程序不在它的训练集中。
我认为依赖类型是下一代语言的赢家。任何能让你把更多东西编码进类型系统的东西都是更多的背压。你大概不会惊讶地听到,我有一个带依赖类型的 Rust 工具链分支。现在你就能做这些事情。
面向智能体的语言,以及 cursed 教会我的事
语言发展的速度一直受限于人类学习新概念的速度。操作符链式调用本质上是对人类的语法糖。如果由智能体来写代码,我们可以依靠四十年的学术 PLT 研究,只要你知道如何采样它。
在 Python 2 到 3 之后,业界把“不要做破坏性变更”奉为准则。Justin 知道有些公司有数百人在这项迁移上花了好几年。我认为这条规则不再成立了。随破坏性变更一起发布一个技能包,让智能体自动迁移。
Justin 问,现在要让一门新语言成功,实际成本是多少。Go 是最后一门公司投入真金白银的语言,而且花了很长时间。这个问题我能回答。
我在循环中运行了 Claude 三个月,它创造了一门叫 cursed 的 Z 世代编程语言
这是唯一一门让你能用 sus、slay 和 vibes 编程的编译语言。
Geoffrey HuntleyGeoffrey Huntley
Cursed 是用 Sonnet 3.5 和 3.7 构建的,配上一个刻意模糊的提示词,再加上三个月的循环运行。我从 C 开始(背压不够;当 agent 覆盖掉自己的更新时,我在 Valgrind 里浪费了太多时间),然后是 Rust,再是 Zig。Zig 是个错误;如果我坚持用 Rust,今天就能跑通。它花了大约 6000 美元,而我把这件事做了三遍。对比一下 Go 花了多少。
现在说真正关键的部分,那个至今仍让我害怕的部分。如果你正确地分配上下文窗口——一张词法结构和语法的查找表——模型就能用一种不在它权重里的语言编程。这是蛮力且低效的,但它管用。
想想 T 型图(墓碑图)。锁定你的语法和词法结构,达到第二阶段的自举编译器,发布一个合理的标准库,然后开始下一轮训练。从那里,你可以极快地达到一个带有语言服务的 Roslyn 式自举编译器。Justin 问过,微调一个开放模型是否有助于引导这样一门语言。并不需要。
这在一年半前、模型弱得多的时候就已经成立了。只需要一位编程语言设计者全力投入这些好模型,就能震惊世界。
接下来呢
酒吧快打烊时,Justin 问我心里在想什么。如果你还没读我最新的文章,去读一下。如果你管理着人,现在就为他们创造实验的空间和时间,因为六个月内,领导层就会要求你把手下人放进活力曲线里。
如果你的团队忙得没空用 AI 做实验,那你就是在把他们培养成被替代的人
如今,使用 AI 已是保持就业能力的硬性要求。
Geoffrey HuntleyGeoffrey Huntley
是的,实验室们是在公共资源上训练的。我讨厌这一点,但我理解。不过你是用时间和技能换钱;雇主有最低标准,而这些标准在我们这个行业里变化得比以往任何时候都快。保持好奇,学会构建 agent,然后去创造美好的东西。我们正处在一场文艺复兴之中。
它是一台时间压缩装置。你经验越多,能采样的就越多。不是所有东西都会发布;我给 Justin 看的一些东西可能永远见不到天日。我把这些项目当作 kata,等模型变好时再重做一遍。
但如果你想构建安全的东西,请认真考虑 unikernel。
章节
- 0:21 — 发现 unikernel:从 Haskell 到 OCaml 再到 Mirage
- 0:55 — 什么是 unikernel,以及它为什么难
- 2:45 — 难已是过去时
- 3:33 — 我们为什么需要操作系统?
- 4:17 — shell 是用于数据外泄的管家服务
- 5:12 — 攻击面缩减是模糊的
- 7:26 — 不要把编译器放在生产环境上
- 9:13 — 把 Stripe 移植进 unikernel
- 9:54 — 最小化 Linux 和用 Rust 写 mkfs.xfs
- 12:22 — 一切皆用 S3
- 13:48 — Nix、机器测试和 overlay
- 15:28 — 工具调用人类不是 AGI
- 15:53 — seL4 还是 unikernel
- 16:56 — 特权环
- 18:24 — 每周内核补丁和 KVM 逃逸
- 19:38 — 演示:Mirage 文件夹、NTP 和时间
- 22:05 — Spaceleans:用 OCaml 写的 Orleans,作为 unikernel
- 25:31 — 采样历史
- 26:52 — 如何让人们尝试这个:直接去做
- 28:57 — OCaml 和 OxCaml
- 31:45 — Rust 编译时间和背压
- 33:06 — Haskell、空间泄漏与依赖类型
- 35:13 — 内存策略:Rust、Zig 与游戏开发者
- 36:10 — 为智能体设计的语言
- 37:41 — 破坏性变更与技能包
- 38:53 — Cursed
- 42:36 — Cursed 教会我的事
- 46:47 — 下一步:使用 AI 是强制性的
保持好奇。
来源:Hacker News · ghuntley.com