使用 NFS 共享家目录

使用 NFS 共享家目录

September 17, 2024

起因

在集群上做科学计算时,需要在各个节点之间同步一些文件,例如计算用到的输入文件和计算结果。 虽然 Slurm 也有诸如 sbcast 可以在节点之间复制文件, 但这个方案我并没有见过谁使用。 你只要想一下这个问题:复制过去的文件是否要在作业后删除?如果要,在什么时机删除?如何让它避免删除别的作业或者诸如 .zshrc 之类的文件? 你就会发现这种在节点之间复制文件的方案的使用前提是, 要么使用者自己能理解集群是如何工作的,并且有良好的使用习惯,并且熟悉 linux 的操作,自己去主动维护集群的健康; 要么管理者需要是全职的,能够随时处理这些问题,或者对鸡脚旮旯的细节行为有足够的了解,去设计一个自动化的解决方案。 总之,很难行得通。 所以最好的办法就是把用户的家目录共享给各个节点。 Windows 电脑大家都用过,共享之后的家目录对于用户来说就和单台的 Windows 电脑没有太大的区别,完全没有操作门槛。

之所以使用 NFS 而不是更专业的文件系统来共享,是因为我要处理的集群只有四个节点,而且只有千兆网互联 (有万兆网卡,但不知为什么没有配置光纤一直闲置着)。 NFS 应该足够用了。

遇到了什么坑?

关于 NixOS 特有的问题(例如 home-manager 和 impermanence)这里不讨论,实际上这些问题都可以预先想到并且想好解决方案。 这里只讨论一些与发行版无关并且不太容易预料到的问题。

挂载 NFS 的目录

和 sshfs 不同,NFS 必须先指定一个根目录(不同于系统的根目录,而是暴露给其它节点的根目录,使用 fsid=0 指定) ,然后指定要导出的目录(即 /home)。 在客户端,指定的目录是相对于这个根目录的,而不是系统的根目录。

举例来说,如果我在服务端指定导出:

/home 192.168.178.0/24(fsid=0)
/home/chn 192.168.178.0/24()

在客户端要挂载 /home/chn,需要写成:

192.168.178.1:/chn

最终我导出的是:

/ 192.168.178.0/24(rw,no_root_squash,fsid=0,sync,crossmnt)
/home 192.168.178.0/24(rw,no_root_squash,sync,crossmnt)

其中 rw 选项是因为默认 ro,我当然要 rwsync 是因为 NFS 默认不会及时地向下层文件系统传递 sync 命令,我觉得还是传过去比较好。 关于其它选项,以及它的安全性,稍后会介绍。

no_root_squash 选项

NFS 默认会把 root 用户的权限映射为 nobody。 这会导致一个初看起来很无厘头的问题:我在客户端用 root 居然 cd 不到 /home/chn,说我权限不够;用 chn 却没什么问题。 需要加上 no_root_squash 才会恢复更直觉的行为。

按照文档的说法,这个是为了避免轻易地被设置带 suid 的文件。 比如我有某个节点上的 root 权限和另外一个节点上的普通用户权限,我就可以在前者上设置一个带 suid 的文件丢到共享目录里, 然后在后者上执行这个文件,就可以获得 root 权限。 但是我觉得这个问题不是问题,一方面即使限制了 root 其它用户也可以设置所有者为其它用户的 suid 文件。 况且如果黑客都能在某个节点上拿到 root 权限了,那说明这个集群已经被打成筛子了,还有什么保护的必要吗。

crossmnt 选项

在 NixOS 上使用 impermanence 后会导致大量的很多层的挂载。 例如 / 是 bind mount 自 /nix/rootfs/current, 但 /home/chn/Desktop 是 bind mount 自 /nix/persistent/home/chn/Desktop。 其它发行版可能因为挂载行为没有这样复杂所以不太会遇到这个问题。

默认情况下,NFS 的表现行为类似于 bind 而不是 rbind,也即如果我在 NFS 客户端所在节点上的 /home/chn/Desktop 创建一个文件, 这个文件会出现在 NFS 服务端的 /nix/rootfs/current/home/chn/Desktop, 而不是 /nix/persistent/home/chn/Desktop/home/chn/Desktop。 这个选项会让 NFS 表现得像 rbind,也即 bind mount 的目录也会被导出。

安全性

集群的网络我是这样配置的:其中一个节点插一根网线到路由器(可以上网),一根网线到一个交换机;其它节点都只插一个网线到这个交换机。 这样把内部的网络和外部的网络隔离开来,在内部网络上就可以“为所欲为”了(比如跑 NFS)。

按照上面的 NFS 的配置,任何能走进这个实验室并在交换机上插一个网线的人都可以随意读写任何一个文件,或者获得任何一个节点的 root 权限。 这个问题看起来很严重,但我觉得单纯地这个问题就也还好:他都可以走进这个实验室了,为什么不能物理地把硬盘拔下来读写呢? 如果一定要给 NFS 加认证的话,我觉得套个 wireguard 是可以的,但是不知道会不会有性能问题。 况且物理接触到交换机还是比较难的,感觉没必要折腾。

我有点担心的是另外一些可能和这里产生联动的网络设置。 比如,为了做 nginx 在三层协议的透明代理,我其实允许了一个不太安全的路由 net.ipv4.conf.all.route_localnet。 这些设置会不会导致他在门口蹭到 WiFi 就可以访问 NFS,我暂时觉得不能,但是也不太确定。 这里需要的专业知识太多,说不准哪里会出问题。 诸如 net.ipv4.conf.all.route_localnet 这种不太常见但我需要的网络设置,我初期写 NixOS 的配置的时候并没有把它拆分到特定的服务里 (即,不论是否开 nginx 都会设置这个)。 诸如此类的设置叠加起来会不会真的导致安全问题,以我的三脚猫功夫不太能打包票。

在 initrd 中联网

我需要在 initrd 中配置好网卡(固定 IP),以在 switch root 之前挂载好 home,避免之后出现什么 race condition。 如果你搜索互联网,会发现有许多文章提到有个 ip=xxxx 的内核参数。 它确实会对网络配置产生影响,但影响是会导致配置的 systemd-networkd 全部失效,按照 ip 参数的配置来配置网络。 我之前在别的模块里配置了 ip=on,导致 systemd-networkd 配置的静态地址不生效。 实际上完全不写这个参数,只要在 initrd 里启用了 systemd-networkd,就可以在 initrd 使用网络。 在 initrd 里配置 systemd-networkd 的方法和 switch root 之后基本一样。

在 initrd 中挂载 NFS

有两个点与其它文件系统不同:一个是挂载 NFS 还需要用户态的程序支持,一个是 NFS 不同版本的支持是不同的内核模块和用户态程序。 总之就是两个内核模块 nfsnfsv4 (后者依赖前者),两个用户态程序 mount.nfsmount.nfs4。 我用的应该是 NFSv4,但我把这些都塞进 initrd 里了,能用。

最后更新于