集群的文件系统应该怎么做?
NixOS 的文件系统本来就比较复杂(加上 impermanence 之后)。
集群又需要共享家目录。
又考虑到集群里机器的 CPU 不一样(home-manager 等工具生成的配置文件指向的二进制在不同机器上不一定能用,因为编译时 -march
不同),
搞得就很麻烦。
前几天把一直分开运行的两个机器合并到一起了,但在合并前并没有非常仔细地思考文件系统怎么设计,
于是成功地自己把自己绕进去了,出了一些自己也不理解的 bug。
impermanence 的配置文件,自从我一年前把它写好之后就没有大动过,遇到需要的时候就得过且过修修补补,现在它已经很混乱了,是时候重新设计一下了。
按照我的设想,/home
中的文件主要来自两个地方。
一个是 /nix/persistent/home
,这些文件在每次重启后依然会保留;一个是 /nix/rootfs/current/home
,这些文件在每次重启后都会丢失。
只有少量的文件挂载或软链接自别的地方。
需求
明确需求才能提出合理的解决方案。 最后解决方案需要满足所有的需求,也不需要过度设计,不要创造需求。
- 只需要考虑
/home
挂载的问题。其它目录的挂载运行良好,不需要更改。 - 集群上,一部分文件需要共享,另一部分则不能共享:
在集群中,
/home
下的大多数文件需要在所有节点上共享(即,从机通过 NFS 来获得大部分文件)。 但是有一些文件和目录是不应该共享的,包括例如.config
.zshrc
等。 这些目录中包含许多 home-manager 生成的文件,其中指向的二进制文件在不同机器上不兼容。 - 可复现与留存状态的平衡:
桌面用途下的
chn
用户是一个特例,/home
下的大部分文件应该在重启后丢失,只有少数留存。 除了这个特例以外,/home
下的大部分文件应该留存状态(即,在重启后不丢失,也即来自于/nix/persistent
), 少部分文件(例如.cache
)应该为了确保可复现而丢失状态(即,在重启后丢失,也即来自于/nix/rootfs/current
)。 - 设置正确的权限和所有者。
基本上就这些。
解决方案
/home
以外的挂载代码不动。/home
本身不挂载。- 在所有挂载开始之前,确认
/nix/persistent/home/user
和/nix/rootfs/current/home/user
存在并设置合理的权限。 这通过system.activationScripts
来实现。 这个设置会在两个情况下起作用:一个是在启动时,在挂载好了根目录但还没有 switch root 前进行;另一个是 rebuild 时进行。 这里已经有了一些内容,包括:
users
是 nixpkgs 设置用户的一些内容(例如创建家目录)。createPersistentStorageDirs
是 impermanence 生成的, 用于在挂载前用正确的权限创建需要的目录(指 target,不是挂载点所在的目录),如果 target 已经存在则不检查权限; 并根据 target 的权限调整挂载点及其父目录的权限。 在前者之后运行。persist-files
也是 impermanence 生成的,用于挂载需要的文件(不涉及目录)。 仔细阅读之后,确认现在的代码应该已经足够,不再需要手动在这里添加代码。
- 挂载
/home/user
。这分为几种情况:
- 在集群的主节点上,导出
/nix/persistent/home
;再其它节点上,通过 NFS 挂载。 这个挂载需要在activationScripts
之前完成(neededForBoot = true
)。 - 对于桌面用途的
chn
用户,不需要挂载。 - 对于其它情况,挂载
/nix/persistent/home/user
到/home/user
。 这通过 nixos 的 impermanence 模块实现(以设置正确的权限)。
- 挂载更详细的目录。大部分使用 nixos 的 impermanence 中,
users.user
来实现,有一个例外需要直接写systemd.mounts
。
- 对于所有用户,
.cache
需要在重启后丢失,它应该挂载自/nix/rootfs/current/home/user/.cache
。 - 对于桌面的
chn
用户,有一些额外的目录需要挂载自/nix/persistent/home/chn
或/nix/rootfs/current/home/chn
。 - 对于集群的非主节点,需要采取额外的措施来避免覆写主节点的文件,包括:
- 禁用 home-manager 在家目录的根目录中创建的一些符号链接,包括
.zshrc
等;改而使用挂载。 - 额外从
/nix/rootfs/current/home/user
挂载一些目录过来,例如.ssh
,home-manager 将需要覆写这些目录中的文件。 - 由于 impermanence 会将 target 及其父目录的权限复制给挂载点,这会导致一个问题:
挂载来自 nix store 的文件(即 home-manager 生成的那些文件)时,家目录会被改写为
root:root 555
。 这些文件改为直接用systemd.mounts
来挂载。
- 禁用 home-manager 在家目录的根目录中创建的一些符号链接,包括
最终代码在这里。
其中的坑
我遇到了一些问题。
第一个问题是,如果 home-manager 生成的文件也用 impermanence 来挂载(最开始我就是这样做的),
那么家目录的权限会在从机启动后被改写为 root:root 555
。
出现这个状况的原因在之前已经解释过了。
通过将这些文件直接用 systemd.mounts
来挂载,这个问题可以被避免——然后我就发现家目录权限被改成了 root:root 755
。
为什么会出现这个状况,我想了很久才想通。这个过程是这样的:
- 在 NFS 挂载之前:
/nix/rootfs/current
已经被挂载到了/
,而/home/user
和/nix/rootfs/current/home/user
都不存在。 - 在 NFS 挂载时,会自动创建不存在的挂载点然后再挂载。
这样,在 NFS 挂载
192.168.178.1:/nix/persistent/home/user
到/home/user
之后,/home/user
的权限是远程文件系统的user:user 700
, 但/nix/rootfs/current/home/user
的权限是被自动创建的挂载点在挂载前的权限,也就是root:root 755
。 - 之后
system.activationScripts
中 impermanence 中的脚本会运行, 它发现/nix/rootfs/current/home/user
已经存在了,就不会再创建或者修改它的权限; 然后它将/nix/rootfs/current/home/user
的权限复制给/home/user
,这个行为也一并把远程的权限覆盖掉了。
直觉来说,只要告诉 systemd 创建挂载点时用某个用户、设定某个权限就好了;
但我翻了一圈资料,似乎没有设定所有者的办法(权限是可以设定的,通过 X-mount.mkdir
选项)。
所以最终解决办法是:
- 先将
192.168.178.1:/nix/persistent/home
挂载到/remote/home
。/remote/home
的权限无所谓,随他便,这只是为了避免创建/nix/rootfs/current/home/user
。 - 再将
/remote/home/user
挂载到/home/user
,这一步通过 impermanence 进行, 以设定正确的权限(/home/user
和/nix/rootfs/current/home/user
的权限都会被设定为user:user 700
)。 - 之后,impermanence 再挂载其它来自
/nix/rootfs/current/home/user
的目录或文件,都不会出问题了。