你有没有想过一个问题:一个系统声称自己是“不可变的”,却让你能正常使用 dnf install ollama——这究竟是技术的妥协,还是对“不可变”这个概念的重新定义?
在尝试 RakuOS 之前,我长期处于一种认知分裂中。不可变 Linux 通过将关键目录挂载为只读,从根本上杜绝了恶意代码篡改系统文件的风险,这确实是操作系统安全性的未来方向。但问题出在日常使用里。ZDNET 作者 Jack Wallen 的经历很典型:他想安装 Ollama 的最新版本,Flatpak 安装虽然命令完成,“应用却无处可寻”;而 sudo dnf install ollama 在 RakuOS 上“工作得很好,但它不应该工作”。一个不可变系统,为什么能接受原生包管理器的写入操作?这让我陷入困惑。
这意味着什么?意味着我们长久以来对“不可变”的理解,一直建立在一种过于简化的二元对立上:要么完全只读,要么完全可变。而 RakuOS 的解决方案,恰恰指向一种中间状态——一个“可变的不可变系统”。
技术细节的深层解剖:overlay 与 package layering 的真实机制
从实现角度看,RakuOS 的核心机制是“在 /usr 上挂载持久化的 overlay 系统”。官方描述是:“RakuOS 使用一个挂载在 /usr 上的持久化覆盖层。你的基础系统始终是原始的——但你可以安装任何原生包,并且它会在每次系统更新后保留。”
这里必须立刻纠正一个容易产生的误解。我们可能下意识地想象这是 Docker OverlayFS 的简单复刻:底层是只读的 OSTree 镜像,上层是一个可写层,文件写入就留在上层,更新时只替换底层。但实际机制更精确,也更关键——rpm-ostree 使用的是 package layering,而不是简单的“保留 overlay 文件”。
据 rpm-ostree 的官方技术文档说明,package layering 的工作方式是:当你执行 rpm-ostree install(或 RakuOS 中封装后的 dnf install),系统不会直接把 RPM 包解压到某个文件系统层然后就不管了。相反,它将这个包记录为用户显式请求的“层叠包”(layered package)。等到系统更新时,rpm-ostree 会下载新的基础镜像,然后在新的基础镜像上重新应用所有已记录的层叠包,生成一个新的部署根(deployment root)。这意味着所谓的“持久化”,不是文件数据简单地横跨更新周期存活,而是安装请求本身被持久化——每次更新都是一次“重新构建”,将你的定制包重新叠加到干净的新基底上。
换个角度理解:这更像盖楼时的一种特殊工艺。你在已有的地基上额外浇筑了一层隔层(你的定制包),当地基需要重建时(系统更新),施工方不是试图保留那层旧隔层然后赌它和新地基兼容,而是按照原始图纸(层叠包的记录)在新地基上重新浇筑一层。隔层材料可能完全一样,但它是重新构建的,不是原封不动搬过来的。这样一来,底层共享库的版本变更不会导致“旧隔层裂痕”——如果新地基上的依赖关系无法满足,重新构建过程中就会直接报错,而不是留到运行时让系统崩溃。
这个机制的工程师思维很清晰:把“持久化一个状态”转变成“持久化一组意图”。状态会腐化,意图可以重新执行。
本质上的悖论:安全性与可用性的辩证
这种设计引出一个更底层的问题:我们追求的系统“不可变性”,到底保护的是什么?是二进制文件本身一旦写入就不可更改,还是系统的整体配置可回滚、可复现?
如果答案是后者,那么 RakuOS 的做法实际上比纯粹的只读模式更强大。你可以同时拥有两样东西。一方面是可回滚的基础系统:通过 sudo rpm-ostree reset 一键清除所有层叠包,回到初始状态——就像忒修斯之船换掉所有木板后依然是那艘船,但你现在可以迅速找回最初的那艘。另一方面是可自由定制的上层:通过 package layering 安装任何原生软件,包括那些尚未被容器化的前沿工具,比如 Ollama 的最新版本。
但代价是什么?安全性的折中是真实存在的。层叠包既然能被你安装,也能被获得 root 权限的恶意软件安装。攻击者在你的 overlay 层面写入恶意文件后,这部分数据会在下次系统更新时被忠实地“重新构建”到新部署根里——因为 rpm-ostree 分不清哪些层叠包是你主动装的,哪些是被污染的。不过,这仍然比传统 Linux 中恶意软件直接修改根文件系统要可控得多,因为底层基础镜像始终完整、干净,你可以通过 rpm-ostree reset 或 rpm-ostree rollback 回退到一个已知安全的状态。本质上这是一种“有边界的妥协”:你接受的不是任意文件篡改,而是“所有变更必须经过包管理器记录”这条规则的约束。攻击面被限制在“能注册为层叠包”这个通道内,而不是整个文件系统。
rm -rf /usr 的错误与认知模型升级
Jack Wallen 尝试 sudo rm -rf /usr 来“验证不可变性”,结果系统无法启动。这个错误极具象征意义。
为什么直接删除会破坏系统,而 rpm-ostree reset 能恢复?因为 overlay 文件系统的挂载依赖元数据的完整性——具体说,是 upperdir、workdir 和底层目录之间的关联关系。rm -rf /usr 相当于撕掉了一本书的目录页:正文(底层 OSTree 数据)还在,但你已经无法通过正常的索引结构找到任何内容。文件系统无法挂载,启动过程就中断了。而 rpm-ostree reset 是按 OSTree 的部署协议显式卸载层叠包——它更新的是部署根的选择指针,告诉系统“忽略之前注册的所有层叠包,直接使用基础镜像”,而不是暴力破坏数据结构。
这引申出一个更深层的意义:用户对“不可变系统”的认知模型需要升级。你不能把“不可变”理解为“所有文件都绝对只读”,而应该理解为“系统状态通过原子化操作管理,可记录、可回滚”。rm -rf /usr 是绕开管理协议的直接底层操作,在传统 Linux 中可能只是删文件,在不可变系统中却意味着破坏了管理协议依赖的结构完整性。这就像你不能用锤子砸开保险柜然后说“保险柜不安全”——你需要理解锁的机制,而不是测试锁能承受多大的暴力。
开放式结尾:一个设计约束的显式化问题
RakuOS 的做法让我想起工程中一个常见的原则:一个好的设计,不在于它消除了所有约束,而在于它把约束画成了一条可见的边界线。RakuOS 画出的这条线是:系统核心不可变,但允许通过 package layering 声明式地叠加定制;所有变更都被记录为显式意图,更新时在新的基底上重新执行这些意图,而不是被动保留历史遗留的文件状态。
但这种模式能撑到多大规模?当你的层叠包数量从几个变成几十个,每次系统更新都意味着这些包需要在新基底上重新解析依赖、重新构建。如果某个层叠包需要的共享库在新基底中被废弃或版本不兼容,更新就会失败——而你面对的不是一个包的问题,是所有层叠包都可能成为阻断点。这种依赖冲突的风险会不会在某一天爆炸,让“可定制的不可变系统”变成“无法更新的定制系统”?
这个问题我现在无法回答。但也许,未来我们讨论操作系统的设计时,核心问题不再是“是否不可变”,而是“可变的部分用什么协议管理,这个协议的可扩展上限在哪里”。答案不在某个发行版的实现细节里,而在于我们如何定义“系统状态”这个概念本身。你愿意接受多大程度的显式意图记录,来换取一次 sudo dnf install 的自由?这个权衡,可能比技术实现本身更值得想。