NFS
来自 维基百科
- 网络文件系统 (NFS) 是一种分布式文件系统协议,由 Sun Microsystems 于 1984 年开发,允许客户端计算机上的用户通过网络访问文件,其方式与访问本地存储类似。
NFS FAQ 列出了经过良好测试的文件系统,并详细说明了有关 FAT32 的限制。
- 默认情况下,NFS 是未加密的。在处理敏感数据时,请配置 #TLS 加密,或配置 Kerberos(使用
sec=krb5p以提供基于 Kerberos 的加密),或者通过加密的 VPN(如 WireGuard)隧道传输 NFS。 - 与 Samba 不同,NFS 默认没有任何用户身份验证,客户端访问受其 IP 地址/主机名限制。如果需要更强的身份验证,可以使用 Kerberos。
- NFS 要求客户端和服务器上的用户和/或用户组 ID 必须相同(除非使用 Kerberos)。请使用 NFSv4 ID 映射,或在
/etc/exports中结合使用all_squash和anonuid/anongid来手动覆盖 UID/GID。 - NFS 不支持 POSIX ACL。NFS 服务器仍会强制执行 ACL,但客户端将无法查看或修改它们。
安装
服务器配置
全局配置选项在 /etc/nfs.conf 中设置。简单配置的用户通常无需编辑此文件。
NFS 服务器需要一个共享目录列表,以导出 (exports) 的形式定义(详见 exports(5)),必须在 /etc/exports 或 /etc/exports.d/*.exports 中定义。默认情况下,目录按其原样路径导出;例如:
/etc/exports
/data/music 192.168.1.0/24(rw)
以上内容将使 /data/music 目录能够以 MyServer:/data/music 的形式同时挂载于 NFSv3 和 NFSv4。
自定义导出根目录
共享可以是相对于所谓的 NFS 根目录的。一种良好的安全实践是在离散的目录树中定义一个 NFS 根目录,将用户限制在该挂载点内。绑定挂载 (bind mounts) 用于将共享挂载点链接到文件系统中其他位置的实际目录。
考虑以下示例,其中:
- NFS 根目录为
/srv/nfs。 - 导出目录为
/srv/nfs/music,通过绑定挂载链接到实际目标/mnt/music。
# mkdir -p /srv/nfs/music /mnt/music # mount --bind /mnt/music /srv/nfs/music
要使绑定挂载在重启后保持有效,请将其添加到 fstab 中:
/etc/fstab
/mnt/music /srv/nfs/music none bind 0 0
在 /etc/exports 中添加要共享的目录,并通过 CIDR 或客户端机器的主机名限制允许挂载的地址范围,例如:
/etc/exports
/srv/nfs 192.168.1.0/24(rw,fsid=root) /srv/nfs/music 192.168.1.0/24(rw,sync) /srv/nfs/home 192.168.1.0/24(rw,sync) /srv/nfs/public 192.168.1.0/24(ro,all_squash,insecure) desktop(rw,sync,all_squash,anonuid=99,anongid=99) # map to user/group - in this case nobody
使用 NFSv4 时,选项 fsid=root 或 fsid=0 表示“根”导出;如果存在此类导出,则所有其他目录必须位于其下方。/etc/nfs.conf 文件中的 rootdir 选项对此无效。在没有 fsid=0 导出的默认行为下,其表现与 NFSv3 相同。
在上面的示例中,由于 /srv/nfs 被指定为根目录,导出目录 /srv/nfs/music 现在可以通过 NFSv4 以 MyServer:/music 的方式挂载——注意根前缀已被省略。
- 对于 NFSv3(NFSv4 不需要),
crossmnt选项使客户端能够访问标记有crossmnt的文件系统上挂载的所有文件系统,并且客户端无需分别挂载每个子导出。请注意,如果子目录与不同的地址范围共享,这可能不是理想选择。 - 除了
crossmnt,也可以在子导出上使用nohide选项,以便当客户端挂载根导出时,它们可以自动挂载。与crossmnt不同,nohide仍然遵循子导出的地址范围。注意该选项也是 NFSv3 特有的;NFSv4 总是表现得如同启用了 nohide 一样。 insecure选项允许客户端从 1023 以上的端口连接。(推测只有根用户可以使用小端口号,因此默认阻止其他端口会造成一种表面的访问障碍。在实践中,省略或包含insecure选项都不会对安全性产生任何显著的改善或损害。)- 使用星号 (
*) 允许来自任何接口的访问。
需要注意的是,在服务器运行时修改 /etc/exports 将需要重新导出才能使更改生效:
# exportfs -arv
要更详细地查看当前加载的导出状态,请使用:
# exportfs -v
有关所有可用选项的更多信息,请参阅 exports(5)。
fsid=1 选项。启动服务器
协议版本 4 导出的用户可能至少希望屏蔽 (mask) rpcbind.service 和 rpcbind.socket,以防止多余的服务运行。参见 FS#76453。此外,考虑屏蔽 nfs-server.service,因为它也会在某些情况下被拉入。
将 NFS 限制在特定接口/IP
默认情况下,启动 nfs-server.service 会监听所有网络接口上的连接,无论 /etc/exports 如何设置。这可以通过定义要监听的 IP 和/或主机名来更改。
/etc/nfs.conf
[nfsd] host=192.168.1.123 # Alternatively, use the hostname. # host=myhostname
重启 nfs-server.service 以立即应用更改。
防火墙配置
要通过防火墙启用对 NFSv4 服务器的访问,必须打开 TCP 端口 2049 以进行入站连接。(NFSv4 使用静态端口号;它不使用诸如 mountd 或 portmapper 之类的辅助服务。)
要启用对 NFSv3 服务器的访问,还需要额外打开 TCP/UDP 端口 111 用于端口映射器 (rpcbind),以及 MOUNT (rpc.mountd) 端口。默认情况下,rpc.mountd 会动态选择端口,因此如果您在防火墙后面,需要编辑 /etc/nfs.conf 来设置静态端口。使用 rpcinfo -p 查看 NFSv3 服务器上正在使用的确切端口:
$ rpcinfo -p
100003 3 tcp 2049 nfs 100003 4 tcp 2049 nfs 100227 3 tcp 2049 nfs_acl ...
客户端配置
打算将 NFSv4 与 Kerberos 一起使用的用户需要启动并启用 nfs-client.target。
手动挂载
对于 NFSv3,使用此命令显示服务器导出的文件系统:
$ showmount -e servername
对于 NFSv4,挂载 NFS 根目录并查看可用的挂载点:
# mount servername:/ /mountpoint/on/client
然后省略服务器的 NFS 导出根目录进行挂载:
# mount -t nfs -o vers=4 servername:/music /mountpoint/on/client
如果挂载失败,请尝试包含服务器的导出根目录(Debian/RHEL/SLES 所必需,某些发行版需要 -t nfs4 而不是 -t nfs):
# mount -t nfs -o vers=4 servername:/srv/nfs/music /mountpoint/on/client
servername 需要替换为有效的主机名(不能仅是 IP 地址)。否则挂载远程共享时会挂起。使用 /etc/fstab 挂载
使用 fstab 对于常开服务器非常有用,这样 NFS 共享在客户端启动时即可用。编辑 /etc/fstab 文件,并添加反映设置的相应行。同样,服务器的 NFS 导出根目录在这里被省略。
/etc/fstab
servername:/music /mountpoint/on/client nfs defaults,timeo=900,retrans=5,_netdev 0 0
一些需要考虑的额外挂载选项:
- rsize 和 wsize
rsize值是从服务器读取时使用的字节数。wsize值是写入服务器时使用的字节数。默认情况下,如果不指定这些选项,客户端和服务器会协商双方都能支持的最大值(详情请参阅 nfs(5))。更改这些值后,建议测试性能(参见 #性能调优)。- soft 或 hard
- 确定 NFS 客户端在 NFS 请求超时后的恢复行为。如果未指定任何选项(或指定了
hard选项),NFS 请求将无限期重试。如果指定了soft选项,NFS 客户端在发送 retrans 次重传后会使 NFS 请求失败,并向调用程序返回错误。
soft 超时可能会导致静默数据损坏。因此,仅在客户端响应性比数据完整性更重要时使用 soft 选项。使用 TCP 上的 NFS 或增加 retrans 选项的值可以减轻使用 soft 选项的一些风险。- timeo
timeo值是 RPC 超时后重发传输之前等待的时间(以十分之一秒为单位)。TCP 上的 NFS 默认值为 600(60 秒)。首次超时后,每次重试的超时值加倍,最大为 60 秒或直到发生严重超时。如果连接到速度较慢的服务器或网络拥塞,增加此超时值可以获得更好的稳定性。- retrans
- NFS 客户端在尝试进一步恢复操作之前重试请求的次数。如果未指定
retrans选项,NFS 客户端会尝试每个请求三次。NFS 客户端在 retrans 次重试后会生成“服务器无响应”消息,然后尝试进一步恢复(取决于是否启用了 hard 挂载选项)。 - _netdev
_netdev选项告诉系统在尝试挂载共享之前等待网络就绪——systemd 对 NFS 自动假定此项。
fs_passno) 设置为非零值可能会导致意外行为,例如当 systemd 自动挂载程序等待永远不会发生的检查时导致挂起。使用 systemd 的 /etc/fstab 挂载
另一种方法是使用 x-systemd.automount 选项,它可以在访问时挂载文件系统:
/etc/fstab
servername:/home /mountpoint/on/client nfs _netdev,noauto,x-systemd.automount,x-systemd.mount-timeout=10,timeo=14,x-systemd.idle-timeout=1min 0 0
要使 systemd 感知 fstab 的更改,请重载 systemd 并重启 remote-fs.target [1]。
noauto挂载选项直到访问时才挂载 NFS 共享:使用auto使其立即可用。
如果遇到因网络未启动/不可用而导致挂载失败的问题,请启用NetworkManager-wait-online.service。它将确保network.target在处于活动状态之前拥有所有可用的链路。users挂载选项允许用户挂载,但请注意,它隐含了诸如noexec等其他选项。x-systemd.idle-timeout=1min选项会在 1 分钟不使用后自动卸载 NFS 共享。这对可能突然断开网络的笔记本电脑很有用。- 如果因 NFS 导致关机/重启停滞过久,请启用
NetworkManager-wait-online.service,以确保在卸载 NFS 卷之前 NetworkManager 不会退出。 - 不要添加
x-systemd.requires=network-online.target挂载选项,因为这会导致 systemd 内的循环依赖 [2]。systemd 会自动为_netdev挂载的单元添加network-online.target依赖项。 - 使用
nocto选项可能会提高只读挂载的性能,但仅应在服务器上的数据偶尔更改时使用。
作为 systemd 单元
在 /etc/systemd/system 中创建一个新的 .mount 文件,例如 mnt-home.mount。详情请参阅 systemd.mount(5)。
mnt-home.mount 只能在您将共享挂载到 /mnt/home 下时使用。否则可能会出现以下错误:systemd[1]: mnt-home.mount: Where= setting does not match unit name. Refusing.。如果挂载点包含非 ASCII 字符,请使用 systemd-escape。What= 共享的路径。
Where= 共享的挂载路径。
Options= 共享挂载选项。
- 网络挂载单元会自动获得对
remote-fs-pre.target、network.target和network-online.target的After依赖关系,并获得对remote-fs.target的Before依赖关系,除非设置了nofail挂载选项。对于后者,还会添加一个Wants单元。 - 将
noauto附加到Options,以防止在启动时自动挂载(除非被其他单元拉入)。 - 如果您想使用您要共享的服务器的主机名(而不是 IP 地址),请将
nss-lookup.target添加到After。这可能会避免启动时出现挂载错误,而这些错误在测试单元时不会出现。
/etc/systemd/system/mnt-home.mount
[Unit] Description=Mount home at boot [Mount] What=172.16.24.192:/home Where=/mnt/home Options=vers=4 Type=nfs TimeoutSec=30 [Install] WantedBy=multi-user.target
ForceUnmount=true 追加到 [Mount],从而允许导出目录被(强制)卸载。要使用 mnt-home.mount,请启动该单元并启用它以在系统启动时运行。
automount
要自动挂载共享,可以使用以下自动挂载单元:
/etc/systemd/system/mnt-home.automount
[Unit] Description=Automount home [Automount] Where=/mnt/home [Install] WantedBy=multi-user.target
禁用/停止 mnt-home.mount 单元,并启用/启动 mnt-home.automount,以便在访问挂载路径时自动挂载共享。
使用 autofs 挂载
当多台机器需要通过 NFS 连接时,使用 autofs 非常有用;它们既可以是客户端也可以是服务器。此方法优于前一种方法的原因是,如果服务器关闭,客户端不会抛出因无法找到 NFS 共享而产生的错误。详情请参阅 autofs#NFS 网络挂载。
技巧与提示
NFSv4 ID 映射
- NFSv4 ID 映射并不能解决默认
sec=sys挂载选项带来的所有问题。参见 #静态映射 和 [3] - NFSv4 ID 映射需要在客户端和服务器上都启用。
- 另一个选择是确保用户和组 ID (UID 和 GID) 在客户端和服务器上是一致的。
- 启用/启动
nfs-idmapd.service不需要在客户端上运行,因为它已被新的 ID 映射器取代。
# dmesg | grep id_resolver
[ 3238.356001] NFS: Registering the id_resolver key type [ 3238.356009] Key type id_resolver registered
- 不要将
nfsidmap(仅用于 nfs 客户端)与nfs-idmapd.service混淆,后者由 NFS 服务器使用并衍生出rpc.idmapd进程。 - rpc.idmapd 和 nfsidmap 也共享来自 idmapd.conf(5) 的部分配置。
- 详情请参阅 idmapd(8) 和 nfsidmap(8)。
NFSv4 协议在网络上传输本地系统的 UID 和 GID 值时,使用 user@domain 格式的字符串。从 UID 到字符串以及从字符串到 UID 的转换过程称为 ID 映射。
域
- 默认情况下,字符串的域部分是系统的 DNS 域名。如果系统是多宿主的,或者系统的 DNS 域名与系统的 Kerberos 领域名称不匹配,也可以在 /etc/idmapd.conf 中指定。
- 当未在 /etc/idmapd.conf 中指定域时,将查询本地 DNS 服务器以获取 _nfsv4idmapdomain 文本记录。如果记录存在,则将其用作域。当记录不存在时,将使用 DNS 域名的域部分。
在标准输出上显示系统的有效 NFSv4 域名:
# nfsidmap -d
domain.tld
编辑以使服务器和/或客户端上的域匹配:
/etc/idmapd.conf
[General] Domain = guestdomain.tld
静态映射
- 此映射仅用于客户端在本地映射 UID。如果您创建了一个 UID(例如 1005)拥有的文件,而服务器不知道该 UID。该文件在服务器上存储为 UID 1005,但不会再“通过网络”显示正确的 UID。
- 与服务器交互(即列出文件)后,您可以在密钥环中看到所有条目:
# nfsidmap -l
7 .id_resolver keys found: uid:nobody user:1 uid:bin@domain.tld uid:foo@domain.tld gid:foo@domain.tld uid:remote_user@domain.tld uid:root@domain.tld
- 您可以使用
nfsidmap -c清除密钥环,但这并非必要,在默认设置下,条目会在 10 分钟后过期。
仅当服务器和客户端拥有不同的用户/组名时,才需要执行这些步骤。更改仅在客户端配置文件中进行。
/etc/idmapd.conf
[Translation] # The default is nsswitch and other methods exist. method = static,nsswitch [Static] foo@domain.tld = local_foo remote_user@domain.tld = user
回退映射
仅在客户端配置中。当映射无法完成时要使用的本地用户/组名:
/etc/idmapd.conf
[Mapping] Nobody-User = nobody Nobody-Group = nobody
性能调优
当在拥有大量客户端的网络上使用 NFS 时,可以根据服务器/网络需求将默认 NFS 线程数从 8 增加到 16 甚至更高。
/etc/nfs.conf
[nfsd] threads=16
可能有必要调整 rsize 和 wsize 挂载选项以满足网络配置的要求。
在较新的 Linux 内核(>2.6.18)中,NFS 服务器允许的 I/O 操作大小(默认最大块大小)根据 RAM 大小而变化,最大为 1M(1048576 字节),即使 NFS 客户端请求更大的 rsize 和 wsize,也会使用服务器的最大块大小。参见 https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/5.8_technical_notes/known_issues-kernel。可以通过在启动 nfsd 之前写入 /proc/fs/nfsd/max_block_size 来更改服务器允许的默认最大块大小。例如,以下命令恢复之前的 32k 默认 iosize:
# echo 32768 > /proc/fs/nfsd/max_block_size
max_block_size 可能会降低 NFS 性能。要使更改永久生效,请创建 systemd-tmpfile:
/etc/tmpfiles.d/nfsd-block-size.conf
w /proc/fs/nfsd/max_block_size - - - - 32768
以增加的 rsize 和 wsize 挂载选项进行挂载:
# mount -t nfs -o rsize=32768,wsize=32768,vers=4 servername:/srv/nfs/music /mountpoint/on/client
此外,尽管违反了 NFS 协议,但设置 async 而不是 sync 或 sync,no_wdelay 可能在旋转硬盘上获得显著的性能提升。使用此选项配置导出,然后执行 exportfs -arv 以应用。
/etc/exports
/srv/nfs 192.168.1.0/24(rw,async,crossmnt,fsid=0) /srv/nfs/music 192.168.1.0/24(rw,async)
async 有导致数据丢失或损坏的风险。自动挂载处理
此技巧对于无线网络和/或可能不可靠的网络上的 NFS 共享非常有用。如果 NFS 主机变得无法访问,NFS 共享将被卸载,以期在使用 hard 挂载选项时防止系统挂起 [4]。
确保 fstab 中正确指明了 NFS 挂载点:
/etc/fstab
lithium:/mnt/data /mnt/data nfs noauto 0 0 lithium:/var/cache/pacman /var/cache/pacman nfs noauto 0 0
创建 auto_share 脚本,该脚本将供 cron 或 systemd/定时器使用 ICMP ping 来检查 NFS 主机是否可访问:
/usr/local/bin/auto_share
#!/bin/bash
function net_umount {
umount -l -f $1 &>/dev/null
}
function net_mount {
mountpoint -q $1 || mount $1
}
NET_MOUNTS=$(sed -e '/^.*#/d' -e '/^.*:/!d' -e 's/\t/ /g' /etc/fstab | tr -s " ")$'\n'b
printf %s "$NET_MOUNTS" | while IFS= read -r line
do
SERVER=$(echo $line | cut -f1 -d":")
MOUNT_POINT=$(echo $line | cut -f2 -d" ")
# Check if server already tested
if [[ "${server_ok[@]}" =~ "${SERVER}" ]]; then
# The server is up, make sure the share are mounted
net_mount $MOUNT_POINT
elif [[ "${server_notok[@]}" =~ "${SERVER}" ]]; then
# The server could not be reached, unmount the share
net_umount $MOUNT_POINT
else
# Check if the server is reachable
ping -c 1 "${SERVER}" &>/dev/null
if [ $? -ne 0 ]; then
server_notok[${#server_notok[@]}]=$SERVER
# The server could not be reached, unmount the share
net_umount $MOUNT_POINT
else
server_ok[${#server_ok[@]}]=$SERVER
# The server is up, make sure the share are mounted
net_mount $MOUNT_POINT
fi
fi
done
# Check if the server is reachable
ping -c 1 "${SERVER}" &>/dev/null
与
# Check if the server is reachable
timeout 1 bash -c ": < /dev/tcp/${SERVER}/2049"
在上面的 auto_share 脚本中:确保脚本是可执行的。
接下来配置脚本每 X 时间运行一次,在下面的示例中是每一分钟:
Cron
# crontab -e
* * * * * /usr/local/bin/auto_share
systemd/定时器
/etc/systemd/system/auto_share.timer
[Unit] Description=Automount NFS shares every minute [Timer] OnCalendar=*-*-* *:*:00 [Install] WantedBy=timers.target
/etc/systemd/system/auto_share.service
[Unit] Description=Automount NFS shares After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/auto_share [Install] WantedBy=multi-user.target
使用 NetworkManager 分发器
NetworkManager 也可以配置为在网络状态更改时运行脚本。
在网络状态更改时挂载共享的最简单方法是符号链接 auto_share 脚本:
# ln -s /usr/local/bin/auto_share /etc/NetworkManager/dispatcher.d/30-nfs.sh
但是,在这种情况下,卸载仅在网络连接已禁用后才会发生,这不干净,可能会导致诸如 KDE Plasma 小程序冻结之类的效果。
以下脚本通过监听 down、pre-down 和 vpn-pre-down 事件,在相关网络连接被禁用之前安全地卸载 NFS 共享,确保脚本是可执行的:
/etc/NetworkManager/dispatcher.d/30-nfs.sh
#!/bin/sh
# Find the connection UUID with "nmcli con show" in terminal.
# All NetworkManager connection types are supported: wireless, VPN, wired...
WANTED_CON_UUID="CHANGE-ME-NOW-9c7eff15-010a-4b1c-a786-9b4efa218ba9"
if [ "$CONNECTION_UUID" = "$WANTED_CON_UUID" ]; then
# Script parameter $1: network interface name, not used
# Script parameter $2: dispatched event
case "$2" in
"up")
mount -a -t nfs4,nfs
;;
"down"|"pre-down"|"vpn-pre-down")
umount -l -a -t nfs4,nfs -f >/dev/null
;;
esac
fi
noauto 选项的挂载,请删除此挂载选项或使用 auto 以允许调度器管理这些挂载。在 /etc/NetworkManager/dispatcher.d/pre-down 中创建一个符号链接以捕获 pre-down 事件。
# ln -s /etc/NetworkManager/dispatcher.d/30-nfs.sh /etc/NetworkManager/dispatcher.d/pre-down.d/30-nfs.sh
TLS 加密
从 Linux 6.5 开始,可以使用 xprtsec=tls 挂载选项通过 TLS 加密 NFS 流量。首先,在客户端和服务器上安装 ktls-utilsAUR 软件包,并遵循以下针对每个设备的配置步骤。
服务器
创建私钥并获取包含服务器 DNS 名称的证书(详见 传输层安全#获取证书)。这些文件无需添加到系统的信任存储库中。
编辑 /etc/tlshd.conf 以使用这些文件,并为 x509.certificate 和 x509.private_key 使用您自己的值:
/etc/tlshd.conf
[authenticate.server] x509.certificate= /etc/nfsd-certificate.pem x509.private_key= /etc/nfsd-private-key.pem
客户端
将上一步中生成的服务器 TLS 证书添加到系统的信任存储库中(详见 传输层安全#将证书添加到信任存储库)。
现在您应该能够使用服务器的 DNS 名称挂载服务器了:
# mount -o xprtsec=tls servername.domain:/ /mountpoint/on/client
检查客户端上的 journalctl 应该显示 TLS 握手成功。
$ journalctl -b -u tlshd.service
Sep 28 11:14:46 client tlshd[227]: Built from ktls-utils 0.10 on Sep 26 2023 14:24:03 Sep 28 11:15:37 client tlshd[571]: Handshake with servername.domain (192.168.122.100) was successful
故障排除
有一个专门的文章 NFS/故障排除。
参见
- 参见 Avahi,这是一种允许自动发现 NFS 共享的 Zeroconf 实现。
- 指南:无盘网络启动 NFS 根目录
- Microsoft Unix NFS 客户端信息服务
- Snow Leopard 上的 NFS
- http://chschneider.eu/linux/server/nfs.shtml
- 如何进行 Linux NFS 性能调优和优化
- Linux: 调整 NFS 性能
- 配置仅 NFSv4 服务器