集群的文件系统应该怎么做?

集群的文件系统应该怎么做?

January 14, 2025

NixOS 的文件系统本来就比较复杂(加上 impermanence 之后)。 集群又需要共享家目录。 又考虑到集群里机器的 CPU 不一样(home-manager 等工具生成的配置文件指向的二进制在不同机器上不一定能用,因为编译时 -march 不同), 搞得就很麻烦。 前几天把一直分开运行的两个机器合并到一起了,但在合并前并没有非常仔细地思考文件系统怎么设计, 于是成功地自己把自己绕进去了,出了一些自己也不理解的 bug。 impermanence 的配置文件,自从我一年前把它写好之后就没有大动过,遇到需要的时候就得过且过修修补补,现在它已经很混乱了,是时候重新设计一下了。

按照我的设想,/home 中的文件主要来自两个地方。 一个是 /nix/persistent/home,这些文件在每次重启后依然会保留;一个是 /nix/rootfs/current/home,这些文件在每次重启后都会丢失。 只有少量的文件挂载或软链接自别的地方。

需求

明确需求才能提出合理的解决方案。 最后解决方案需要满足所有的需求,也不需要过度设计,不要创造需求。

  1. 只需要考虑 /home 挂载的问题。其它目录的挂载运行良好,不需要更改。
  2. 集群上,一部分文件需要共享,另一部分则不能共享: 在集群中,/home 下的大多数文件需要在所有节点上共享(即,从机通过 NFS 来获得大部分文件)。 但是有一些文件和目录是不应该共享的,包括例如 .config .zshrc 等。 这些目录中包含许多 home-manager 生成的文件,其中指向的二进制文件在不同机器上不兼容。
  3. 可复现与留存状态的平衡: 桌面用途下的 chn 用户是一个特例,/home 下的大部分文件应该在重启后丢失,只有少数留存。 除了这个特例以外,/home 下的大部分文件应该留存状态(即,在重启后不丢失,也即来自于 /nix/persistent), 少部分文件(例如 .cache)应该为了确保可复现而丢失状态(即,在重启后丢失,也即来自于 /nix/rootfs/current)。
  4. 设置正确的权限和所有者。

基本上就这些。

解决方案

  1. /home 以外的挂载代码不动。
  2. /home 本身不挂载。
  3. 在所有挂载开始之前,确认 /nix/persistent/home/user/nix/rootfs/current/home/user 存在并设置合理的权限。 这通过 system.activationScripts 来实现。 这个设置会在两个情况下起作用:一个是在启动时,在挂载好了根目录但还没有 switch root 前进行;另一个是 rebuild 时进行。 这里已经有了一些内容,包括:
  • users 是 nixpkgs 设置用户的一些内容(例如创建家目录)。
  • createPersistentStorageDirs 是 impermanence 生成的, 用于在挂载前用正确的权限创建需要的目录(指 target,不是挂载点所在的目录),如果 target 已经存在则不检查权限; 并根据 target 的权限调整挂载点及其父目录的权限。 在前者之后运行。
  • persist-files 也是 impermanence 生成的,用于挂载需要的文件(不涉及目录)。 仔细阅读之后,确认现在的代码应该已经足够,不再需要手动在这里添加代码。
  1. 挂载 /home/user。这分为几种情况:
  • 在集群的主节点上,导出 /nix/persistent/home;再其它节点上,通过 NFS 挂载。 这个挂载需要在 activationScripts 之前完成(neededForBoot = true)。
  • 对于桌面用途的 chn 用户,不需要挂载。
  • 对于其它情况,挂载 /nix/persistent/home/user/home/user。 这通过 nixos 的 impermanence 模块实现(以设置正确的权限)。
  1. 挂载更详细的目录。大部分使用 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 生成的文件也用 impermanence 来挂载(最开始我就是这样做的), 那么家目录的权限会在从机启动后被改写为 root:root 555。 出现这个状况的原因在之前已经解释过了。 通过将这些文件直接用 systemd.mounts 来挂载,这个问题可以被避免——然后我就发现家目录权限被改成了 root:root 755。 为什么会出现这个状况,我想了很久才想通。这个过程是这样的:

  1. 在 NFS 挂载之前:/nix/rootfs/current 已经被挂载到了 /,而 /home/user/nix/rootfs/current/home/user 都不存在。
  2. 在 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
  3. 之后 system.activationScripts 中 impermanence 中的脚本会运行, 它发现 /nix/rootfs/current/home/user 已经存在了,就不会再创建或者修改它的权限; 然后它将 /nix/rootfs/current/home/user 的权限复制给 /home/user,这个行为也一并把远程的权限覆盖掉了。

直觉来说,只要告诉 systemd 创建挂载点时用某个用户、设定某个权限就好了; 但我翻了一圈资料,似乎没有设定所有者的办法(权限是可以设定的,通过 X-mount.mkdir 选项)。 所以最终解决办法是:

  1. 先将 192.168.178.1:/nix/persistent/home 挂载到 /remote/home/remote/home 的权限无所谓,随他便,这只是为了避免创建 /nix/rootfs/current/home/user
  2. 再将 /remote/home/user 挂载到 /home/user,这一步通过 impermanence 进行, 以设定正确的权限(/home/user/nix/rootfs/current/home/user 的权限都会被设定为 user:user 700)。
  3. 之后,impermanence 再挂载其它来自 /nix/rootfs/current/home/user 的目录或文件,都不会出问题了。
最后更新于