NFS 实战与排错 — mount/exports 配置、抓包与 hang 死定位

NFS 动手实战:Wireshark RPC 过滤式、exportfs/showmount/rpcinfo/nfsstat 命令、/etc/exports 选项、Stale file handle 与 D 状态排错、与 SMB 对比 / TCP 2049

这篇你能学到

  • 怎么用 showmount/rpcinfo/exportfs/nfsstat 把 NFS 服务端"看个通透",以及 /etc/exports 每个选项到底意味着什么安全与性能取舍。
  • 怎么用 Wireshark / tshark 抓包,从 nfs.statusrpc.timexid 三处快速定位是权限问题、慢请求还是服务器无响应。
  • 遇到 Permission deniedStale file handle、进程 D 状态 hang 死、UID 错乱、性能差时,先记住结论、再按图索骥地排查,以及 NFS 与 SMB 该怎么选型。

抓包观察

Wireshark 显示过滤式

下面这些过滤式是排错时最高频的武器,先扫一眼有个印象,后文"排错三看"会用到:

过滤式用途
nfs所有 NFS 报文
rpcRPC 层(含 mount / nlm / portmap)
tcp.port == 2049NFS 主端口
portmaptcp.port == 111 || udp.port == 111rpcbind 端口查询(v3 挂载第一步)
mountMOUNT 协议(v3 挂载)
nlm文件锁(v3)
nfs.procedure_v3 == 6只看 READ(v3 过程号:1 GETATTR / 3 LOOKUP / 6 READ / 7 WRITE / 17 READDIRPLUS / 21 COMMIT)
nfs.procedure_v3 == 7只看 WRITE
nfs.status != 0只看出错的响应,排错第一过滤式
nfs.status == 70只看 NFS3ERR_STALE(Stale file handle)
nfs.status == 13只看 NFS3ERR_ACCES(权限拒绝)
rpc.auth.uid看客户端自报的 UID(AUTH_SYS 明文可见
nfs.fhandle按文件句柄追踪某个文件的全部操作
nfs.name == "app.log"按文件名筛 LOOKUP/CREATE
rpc.time > 1响应时间超过 1 秒的慢请求(性能排查神器)
nfs.opcodeNFSv4 的 COMPOUND 内操作码

命令行抓包:下面这套 tcpdump/tshark 命令覆盖"抓完整会话、统计各操作耗时、捞慢请求、验证 AUTH_SYS 明文 UID、统计错误分布"五个常见动作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 抓完整 NFS 会话(含 v3 的 111/mountd)
sudo tcpdump -i eth0 -s 0 -w nfs.pcap \
  'port 2049 or port 111 or port 20048'

# 统计各操作耗时(Wireshark 的 RPC 服务响应时间统计)
tshark -r nfs.pcap -q -z rpc,srt,100003,3 # v3
tshark -r nfs.pcap -q -z rpc,srt,100003,4 # v4

# 找出所有慢请求
tshark -r nfs.pcap -Y 'rpc.time > 1' -T fields \
  -e frame.number -e ip.src -e nfs.procedure_v3 -e rpc.time

# 看客户端自报的 UID(验证 AUTH_SYS 的不安全性)
tshark -r nfs.pcap -Y 'rpc.auth.flavor == 1' -T fields \
  -e ip.src -e rpc.auth.uid -e rpc.auth.gid

# 统计错误分布
tshark -r nfs.pcap -Y 'nfs.status != 0' -T fields -e nfs.status | sort | uniq -c | sort -rn

典型字段说明

一次 v3 READ 在 Wireshark 中是两层结构(外层 RPC、内层 NFS),重点看 Program/Procedure 怎么定位调用、以及 AUTH_UNIX 里那串明文 UID:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Remote Procedure Call, Type:Call XID:0x3f2a1b4c
    XID: 0x3f2a1b4c (1059427148)  请求响应配对键
    Message Type: Call (0)
    Program: NFS (100003)  程序号
    Program Version: 3
    Procedure: READ (6)  过程号
    Credentials
        Flavor: AUTH_UNIX (1)  弱认证
        Length: 32
        Stamp / Machine Name: client01
        UID: 1001  客户端【自称】的 UID,服务器直接信任
        GID: 1001
        Auxiliary GIDs (2): 10, 100

Network File System, READ Call FH: 0x8f3d2a1c Offset: 0 Len: 131072
    [Program Version: 3]
    file: /export/data/app.log
        filehandle: 0100070100000000...  不透明的 64 字节句柄
    Offset: 0
    Count: 131072  rsize,通常 128KB  1MB

排错三看

  1. nfs.statusNFS3ERR_ACCES(13) 权限;NFS3ERR_STALE(70) 句柄失效;NFS3ERR_NOSPC(28) 空间满;NFS3ERR_PERM(1) 操作不允许;NFS3ERR_JUKEBOX(10008) 服务器忙(客户端会重试)。
  2. rpc.time:Wireshark 自动计算的 RPC 往返耗时。正常局域网应在毫秒级;持续 >100ms 说明服务端或网络有问题。
  3. 有没有大量重传:同一 xid 反复出现 = 客户端在重试 = 服务器无响应或丢包。配合 nfsstat -rcretrans 计数验证。

常用命令 / 配置

服务端配置(Linux)

安装与启动:下面两条是 Debian/Ubuntu 与 RHEL/Rocky 两系发行版的安装与服务启用方式。

1
2
3
4
5
6
7
# Debian / Ubuntu
sudo apt install nfs-kernel-server
# RHEL / Rocky
sudo yum install nfs-utils

sudo systemctl enable --now nfs-server
sudo systemctl status nfs-server

/etc/exports 语法与完整选项:这一条是 NFS 安全与权限的总开关,尤其注意"空格"那个坑。

1
2
3
4
5
6
7
8
# 格式: <导出目录> <客户端1>(选项...) <客户端2>(选项...)
# 客户端与左括号之间【不能有空格】!有空格 = 对所有人开放该选项,是经典安全事故

/export/data 192.168.1.0/24(rw,sync,no_subtree_check,root_squash)
/export/pub *(ro,sync,no_subtree_check,all_squash,anonuid=65534,anongid=65534)
/export/home 192.168.1.10(rw,sync,no_subtree_check) 192.168.1.11(ro,sync,no_subtree_check)
/export/k8s 10.0.0.0/8(rw,sync,no_subtree_check,no_root_squash) # 高危,仅受控环境
/export/v4root 192.168.1.0/24(rw,sync,fsid=0,no_subtree_check) # NFSv4 伪根

关键选项详解

选项含义建议
rw / ro读写 / 只读按需
sync写请求落盘后才响应默认且推荐,保证数据安全
async写入内存即响应但服务器崩溃会丢数据且客户端不知情,慎用
root_squash客户端 root → nobody(65534)默认,保持开启
no_root_squash客户端 root = 服务端 root极度危险,可被写 SUID 提权。仅在完全受控网络(如 k8s 内网)使用
all_squash所有 UID → anonymous公开只读共享用
anonuid=N / anongid=N指定 squash 目标配合 all_squash
no_subtree_check不检查请求文件是否在导出子树内推荐(默认)。subtree_check 有性能开销且会在重命名时出错
secure / insecure要求 / 不要求客户端源端口 <1024默认 secure;macOS 客户端有时需 insecure
fsid=0标记 NFSv4 伪文件系统根v4 必需(或用 fsid=UUID 保证句柄稳定)
fsid=<UUID>固定文件系统 ID强烈建议:底层设备变化时避免 ESTALE
crossmnt允许客户端跨越服务端的子挂载点导出目录下有别的挂载时需要
sec=sys|krb5|krb5i|krb5p认证方式安全环境用 krb5p

生效与查看:改了 /etc/exports 之后用下面这组 exportfs 让配置生效或临时查看/取消导出。

1
2
3
4
5
6
7
8
sudo exportfs -ra # 重新读取 /etc/exports 并生效(-r 重导出 -a 全部)
sudo exportfs -v # 查看当前导出(含所有实际生效的默认选项)
sudo exportfs -u 192.168.1.0/24:/export/data # 临时取消某个导出
sudo exportfs -o rw,sync,no_subtree_check 192.168.1.5:/tmp/adhoc # 临时导出(不写文件)

# 强制 NFSv4 only(关闭 v3,省掉 rpcbind 与动态端口)
sudo sed -i 's/^# *vers3=.*/vers3=n/' /etc/nfs.conf # 或编辑 [nfsd] 段
sudo systemctl restart nfs-server

固定 v3 端口(为了穿防火墙):v3 的 mountd/statd/lockd 默认随机端口,防火墙没法配规则;下面在 /etc/nfs.conf 里把它们钉死再放行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /etc/nfs.conf
[mountd]
port = 20048
[statd]
port = 32765
outgoing-port = 32766
[lockd]
port = 32803
udp-port = 32769

sudo systemctl restart nfs-server rpcbind

# 防火墙放行
sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --permanent --add-service=mountd
sudo firewall-cmd --permanent --add-service=rpc-bind
sudo firewall-cmd --reload

客户端操作

下面这组命令是客户端的"日常五件套":探测服务器导出了什么、RPC 服务是否可达、怎么用 v3/v4/Kerberos 挂载、怎么查看和卸载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 探测:服务器导出了什么(v3 的 MOUNT 协议,v4-only 服务器会失败)
showmount -e 192.168.1.100
showmount -a 192.168.1.100 # 谁挂载了(依赖 rmtab,未必准确)
showmount -d 192.168.1.100 # 被挂载的目录列表

# 探测:RPC 服务清单与端口
rpcinfo -p 192.168.1.100
rpcinfo -t 192.168.1.100 nfs 3 # 测试 NFS v3 是否可达
rpcinfo -T tcp 192.168.1.100 nfs

# 查看服务器支持的 NFS 版本
rpcinfo -p 192.168.1.100 | grep nfs

# ---------- 挂载 ----------
sudo mkdir -p /mnt/data

# NFSv4(推荐)—— 注意 v4 路径是相对于 fsid=0 伪根的
sudo mount -t nfs4 -o vers=4.2,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2 \
  192.168.1.100:/data /mnt/data

# NFSv3
sudo mount -t nfs -o vers=3,proto=tcp,hard,rsize=131072,wsize=131072,timeo=600,retrans=2 \
  192.168.1.100:/export/data /mnt/data

# Kerberos 加密挂载
sudo mount -t nfs4 -o sec=krb5p 192.168.1.100:/data /mnt/data

# 只读挂载
sudo mount -t nfs4 -o ro 192.168.1.100:/data /mnt/data

# ---------- 查看挂载信息 ----------
mount | grep nfs
findmnt -t nfs,nfs4 # 树形显示,更清晰
cat /proc/mounts | grep nfs # 看【实际生效】的全部参数(含内核默认值)
nfsstat -m # 每个挂载点的详细参数与统计

# ---------- 卸载 ----------
sudo umount /mnt/data
sudo umount -l /mnt/data # lazy umount,服务器挂了时用(先摘下再慢慢清理)
sudo umount -f /mnt/data # 强制(可能失败)

/etc/fstab 持久化:把挂载写进 fstab 时,关键是避免开机因网络未就绪而 hang 死,_netdevx-systemd.automount 是标配。

1
2
3
4
5
6
7
8
# NFSv4,网络就绪后再挂,避免开机 hang 死
192.168.1.100:/data /mnt/data nfs4 rw,hard,vers=4.2,rsize=1048576,wsize=1048576,timeo=600,retrans=2,_netdev,x-systemd.automount,x-systemd.mount-timeout=30 0 0

# 关键参数:
# _netdev 标记为网络设备,等网络起来
# x-systemd.automount 按需挂载(首次访问时才挂),避免开机卡住
# x-systemd.mount-timeout=30 挂载超时
# noauto 不自动挂(配合 autofs)

挂载参数速查:这些参数直接决定"卡不卡、快不快、丢不丢数据",先记住 hard 默认、proto 一律 tcp、noatime 强烈建议。

参数默认说明
vers=3|4|4.0|4.1|4.2协商最高显式指定避免协商问题
proto=tcp|udptcp一律用 tcp
hard✅ 默认无限重试,保证数据不丢,代价是可能 hang
soft超时返回 EIO,有静默数据损坏风险
timeo=N600(=60 秒,单位 0.1s)单次 RPC 超时
retrans=N2(TCP)/ 3重传次数
rsize / wsize服务器协商(常 1MB)单次读写块大小。太小会导致大量小 I/Onfsstat -m 可看实际值
noatime强烈建议:不更新访问时间,能省掉大量 SETATTR
nodiratime目录同上
actimeo=N属性缓存时长(默认 reg 3-60s,dir 30-60s)
nconnect=N1多 TCP 连接并行(内核 5.3+),万兆网下 nconnect=8 能显著提升吞吐
sec=sys|krb5|krb5i|krb5psys认证方式
noexec,nosuid,nodev安全加固:不允许执行、SUID、设备文件

监控与统计

下面这组是"服务器卡不卡、线程够不够、谁占着挂载点"的监控命令,性能排查必用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
nfsstat -c # 客户端统计(各操作调用次数)
nfsstat -s # 服务端统计
nfsstat -rc # RPC 统计,重点看 retrans(重传)和 authrefrsh
nfsstat -m # 每个挂载点的参数 + 每操作的平均 RTT/执行时间

# 实时 I/O 监控(最实用的性能工具)
nfsiostat 2 10 # 每 2 秒一次,共 10 次
mountstats /mnt/data # 超详细统计
cat /proc/self/mountstats

# 服务端连接数
ss -tn state established '( sport = :2049 )' | wc -l

# 服务端 nfsd 线程数(默认 8,高负载需调大)
cat /proc/fs/nfsd/threads
echo 64 > /proc/fs/nfsd/threads # 临时
# 永久:/etc/nfs.conf 的 [nfsd] threads=64

# 谁在占用挂载点(卸载失败时用)
sudo lsof +D /mnt/data
sudo fuser -vm /mnt/data

常见故障与排错

故障 1:mount.nfs: access denied by server while mounting

先记住结论:这是服务端 mountd/nfsd 在挂载阶段就拒绝了你,几乎都和"客户端 IP 不在 exports 白名单"或"exports 语法写错(尤其那个空格)“有关,先去服务端 exportfs -v 看实际生效项。

原因排查
客户端 IP 不在 exports 白名单服务端 exportfs -v 看实际生效的网段;注意反向 DNS 解析(用主机名导出时)
/etc/exports 语法错:客户端与括号间有空格/export 192.168.1.0/24 (rw) ← 这个空格会让选项对所有人生效而对该网段用默认(ro,root_squash)。必须写成 192.168.1.0/24(rw)
改了 exports 没生效sudo exportfs -ra
客户端源端口 >1024服务端加 insecure 选项,或客户端用 root 挂载
v4 挂载路径写错v4 路径相对 fsid=0 的伪根:服务端导出 /export/data/exportfsid=0,客户端应挂 server:/data 而非 server:/export/data
SELinuxgetenforcesetsebool -P nfs_export_all_rw 1

服务端日志是最直接的线索,拒绝挂载时 journalctl 里通常有 refused mount request 字样:

1
2
3
4
# 服务端日志是最直接的线索
sudo journalctl -u nfs-server -f
sudo tail -f /var/log/messages | grep -i 'rpc.mountd\|nfsd'
# 典型:rpc.mountd[1234]: refused mount request from 192.168.1.50 for /export/data: not exported

故障 2:mount.nfs: Connection timed out / No route to host

先记住结论:这通常是"网络不通 / 端口被墙 / 服务没起"三层里某一层断了,按"网络 → 2049 → 111 → 防火墙"的顺序逐层 ping/nc 就能定位。v3 还要额外确认 111 和 mountd 动态端口没被墙(或直接换 v4 只留 2049)。

排查层命令
网络可达ping 192.168.1.100
2049 端口nc -zv 192.168.1.100 2049
v3 还要 111nc -zv 192.168.1.100 111rpcinfo -p 192.168.1.100
v3 动态端口被墙这是 v3 最常见的防火墙问题 → 固定端口(见上文 /etc/nfs.conf)或改用 v4(只需 2049)
服务端服务未起systemctl status nfs-server rpcbind
服务端监听ss -tlnp | grep 2049
防火墙firewall-cmd --list-alliptables -L -n、云安全组

故障 3:Permission denied —— 挂上了但读写不了

先记住结论:挂上了还写不了,99% 是"UID 对不上"或"root_squash 在起作用”——不是 NFS 坏了,是权限映射在拦你。先 ls -ln 看文件属主是不是变成了陌生数字,再想是不是在用 root 写。

原因排查
UID 不匹配(v3 最常见)客户端 id zhangsan 与服务端 id zhangsan数字 UID 是否一致?不一致就会出现"文件属主显示成数字"和权限失效
root_squash 生效用 root 写文件被拒是正常的(默认行为)。用普通用户写,或(仅受控环境)改 no_root_squash
服务端目录本身权限不足服务端 ls -ld /export/datachmod/chown 调整
挂载成 ro`mount
exports 是 roexportfs -v
SELinux 上下文ls -Zchcon -R -t nfs_t /export/datasetsebool -P use_nfs_home_dirs 1
辅助组超过 16 个AUTH_SYS 的 gids 字段最多 16 个辅助组!用户组多于 16 个时会被截断导致权限判断错误。解法:服务端开 rpc.mountd --manage-gids(服务端自己查组,不信客户端的)

快速验证 UID 是否一致、看 NFS 上文件实际属主:

1
2
3
4
5
6
# 快速验证 UID 一致性
echo "=== client ==="; id zhangsan
ssh server 'echo "=== server ==="; id zhangsan'

# 看 NFS 上文件的实际属主
ls -ln /mnt/data # -n 显示数字 UID,一眼看出映射问题

故障 4:Stale file handle(陈旧的文件句柄)

先记住结论:你手里的文件句柄在服务端已经失效了——文件被删/重建、服务端重导、或底层设备变了导致 fsid 变了。临时 cd 出去再进来(触发重新 LOOKUP)或 umount -l 重挂即可,根治是在 exports 里钉死 fsid=<UUID>

含义:客户端手里的文件句柄在服务端已失效。

原因解决
文件/目录被服务端或其它客户端删除或重建重新访问路径(cd .. && cd -)让客户端重新 LOOKUP
服务端重新导出了文件系统sudo exportfs -ra 后客户端 remount
服务端底层设备变化导致 fsid 变了根治方案:exports 里显式指定 fsid=<UUID>,让句柄跨重启/换盘稳定
服务端把导出目录 remount 了客户端重挂
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 客户端恢复
cd / # 先离开该目录
sudo umount -l /mnt/data # lazy umount
sudo mount -a

# 根治:服务端固定 fsid
uuidgen # 生成一个 UUID
# /etc/exports:
# /export/data 192.168.1.0/24(rw,sync,no_subtree_check,fsid=8f1a2b3c-...)
sudo exportfs -ra

故障 5:进程卡死在 D 状态,df / ls 全部 hang 住

先记住结论:这是 hard 挂载下服务器宕机/网络中断的经典表现,不是死机——进程进入 D 状态(不可中断睡眠)、kill -9 也杀不掉。别慌,恢复服务器网络后 hard 挂载会自动继续;应急用 umount -l 摘掉挂载点,实在不行只能重启客户端。切忌为了"解 hang"改 soft,那会换来静默数据损坏。

这是 NFS 最恐怖也最经典的故障:服务器宕机或网络中断,hard 挂载下所有访问该挂载点的进程进入 D 状态(不可中断睡眠)kill -9 无效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 定位是哪个挂载点出问题(不要直接敲 df,它自己也会 hang!)
cat /proc/mounts | grep nfs # 读 /proc 不会 hang
timeout 3 df -h # 加超时保护
findmnt -t nfs4 -o TARGET,SOURCE # 也不会 hang

# 找出卡住的进程
ps -eo pid,stat,wchan:30,cmd | awk '$2 ~ /D/'
cat /proc/<PID>/stack # 看内核栈,会看到 rpc_wait_bit_killable

# 内核日志
dmesg -T | grep -i 'nfs: server .* not responding'
# 典型:nfs: server 192.168.1.100 not responding, still trying
# nfs: server 192.168.1.100 OK ← 恢复后自动继续

# 应急处理(按激烈程度递增)
sudo umount -l /mnt/data # ① lazy umount,最常用,立刻摘掉挂载点
sudo umount -f -l /mnt/data # ② 强制 + lazy
# ③ 恢复服务器网络后,hard 挂载会自动继续,D 状态进程自行退出
# ④ 实在不行只能重启客户端(D 状态进程无法被杀死)

预防措施

  • x-systemd.automount(按需挂载)+ x-systemd.mount-timeout,避免开机和空闲时 hang;
  • autofs 自动挂载/超时卸载,不常用的挂载点自动摘掉;
  • 监控告警 NFS 服务器可用性;
  • 不要为了避免 hang 而改用 soft —— soft 会导致静默数据损坏,比 hang 更可怕。

故障 6:性能差(吞吐低 / 延迟高)

先记住结论:性能差先看三处——rsize/wsize 是不是太小、retrans 重传比例高不高、nfsiostat 的 avg RTT 大不大。多数"慢"是块太小或网络丢包;小文件海量场景本身就不是 NFS 强项,别指望调参解决。

诊断顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# ① 看实际的 rsize/wsize(很多"性能差"就是块太小)
nfsstat -m | grep -E 'rsize|wsize'
# 理想值:局域网 1048576(1MB);若显示 32768 说明协商出了问题

# ② 看重传(网络丢包 / 服务端过载)
nfsstat -rc
# retrans 占比 > 1% 就有问题

# ③ 看每操作的延迟
nfsiostat 2
# 关注 avg RTT (ms):正常 <5ms;>50ms 说明服务端或网络有问题

# ④ 抓包看慢请求
tshark -i eth0 -Y 'rpc.time > 0.5' -T fields -e nfs.procedure_v3 -e rpc.time

# ⑤ 服务端 nfsd 线程是否打满
cat /proc/net/rpc/nfsd | grep '^th'
# 第 2 个数字是"所有线程都忙"的次数,持续增长说明线程不够

调优清单

措施命令 / 参数效果
加大读写块rsize=1048576,wsize=1048576减少 RPC 次数,大文件吞吐显著提升
多连接并行nconnect=8(内核 5.3+)单挂载点多条 TCP,万兆网下吞吐可翻倍
关闭 atimenoatime,nodiratime消除大量 SETATTR
用 v4.1/4.2vers=4.2COMPOUND 减少 RTT;Delegation 减少校验
增大属性缓存actimeo=60减少 GETATTR(代价是一致性变差
服务端加线程/etc/nfs.conf[nfsd] threads=64高并发场景必调
用 TCP 不用 UDPproto=tcpUDP 丢包重传整个 RPC,大块 I/O 灾难
巨帧ip link set eth0 mtu 9000(两端+交换机)减少包数,提升大 I/O 效率
服务端 async(慎用)exports async快但掉电丢数据

小文件场景的真相:NFS 处理海量小文件天生慢——每个文件都要 LOOKUP + OPEN + READ + CLOSE 多次往返。这不是调参能解决的,应改用打包传输、对象存储或本地缓存(FS-Cache/cachefilesd)。

故障 7:安全加固清单

先记住结论:NFS 本身没有加密、AUTH_SYS 形同虚设,所以"不暴露公网 + 白名单 + root_squash + Kerberos"是四道必做的防线;能用 sec=krb5p 就别裸奔,能禁止 * 就别对外开放。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# ① 绝不暴露公网。NFS 无内置加密,AUTH_SYS 形同虚设
# 只在内网/VPN 内使用,防火墙精确限制源 IP

# ② exports 使用具体网段,禁止 *
/export/data 192.168.1.0/24(rw,sync,no_subtree_check,root_squash) # ✅
/export/data *(rw,no_root_squash) # ❌ 灾难

# ③ 保持 root_squash;no_root_squash 只在完全受控环境用

# ④ 客户端挂载加安全选项
mount -t nfs4 -o nosuid,nodev,noexec server:/data /mnt/data

# ⑤ 生产环境用 Kerberos
mount -t nfs4 -o sec=krb5p server:/data /mnt/data
# krb5 = 仅认证
# krb5i = 认证 + 完整性校验
# krb5p = 认证 + 完整性 + 【加密】← 唯一能防窃听的

# ⑥ 自查暴露面
nmap -p 111,2049 --script nfs-showmount,nfs-ls,nfs-statfs <目标IP>
showmount -e <你的公网IP> # 如果能列出导出 = 已暴露,立即整改

与其他协议对比

NFS vs SMB —— 最常被问的对比

维度NFSSMB / CIFS
出身Sun Microsystems(1984),Unix 世界IBM/Microsoft,DOS/Windows 世界
标准化IETF RFC(开放标准)微软 MS-SMB2 开放规范(非 RFC)
端口TCP 2049(v3 另需 111 等)TCP 445(旧:139 over NetBIOS)
承载ONC RPC + XDR自有 SMB2 消息格式
语义POSIX:mode 位、硬链接、区分大小写、可删除已打开文件Windows:ACL、不区分大小写、打开的文件被锁定不可删
认证AUTH_SYS(弱)/ KerberosNTLM / Kerberos(内置强认证)
加密需 Kerberos krb5pSMB3 原生 AES-128/256-GCM,开箱即用
状态v3 无状态 / v4 有状态始终有状态(Session/Tree/File ID)
文件锁v3 外挂 NLM(弱)/ v4 内置强制锁 + 机会锁(Oplock/Lease),语义严格
缓存一致性close-to-open(Oplock/Lease(
性能特点大文件顺序 I/O 强小文件与元数据操作强(SMB2 复合请求)
客户端支持Linux/Unix 原生;Windows 需装 NFS 客户端Windows 原生;Linux 用 cifs-utils/Samba
典型场景Linux 服务器集群、K8s PV、HPC、VMware DatastoreWindows 文件服务器、办公共享、打印共享
一句话选型纯 Linux 环境选 NFS有 Windows 客户端选 SMB

NFS vs 其它存储访问方式

维度NFSSMBiSCSIS3/对象存储FTP/SFTP
抽象层级文件级文件级块级对象级文件传输
谁管文件系统服务器服务器客户端(自己格式化)服务提供方服务器
多客户端并发读写✅ 支持✅ 支持❌ 需集群文件系统✅(最终一致)
部分读写(随机 I/O)需 Range 请求❌ 整文件
POSIX 语义部分✅(本地 FS 提供)
广域网友好❌ 延迟敏感专为 WAN 设计
典型用途共享 home、K8s PV办公共享数据库/虚拟机磁盘备份、静态资源、大数据文件分发

速查表 / 常见面试题

端口 / 关键值速查

NFS 主端口TCP 2049(v4 只需这一个
rpcbind / portmapperTCP+UDP 111(v3 必需,v4 不需要)
mountd / statd / lockd动态端口(可在 /etc/nfs.conf 固定)
RPC 程序号NFS 100003 / MOUNT 100005 / NLM 100021 / NSM 100024 / portmap 100000
文件句柄长度v2 32B / v3 ≤64B / v4 ≤128B
v4 租约 / 宽限期默认各 90 秒
AUTH_SYS 辅助组上限16 个(超出会被截断)
属性缓存默认文件 3–60s,目录 30–60s
推荐 rsize/wsize1048576(1MB)

一行命令速查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
showmount -e SERVER # 看导出了什么
rpcinfo -p SERVER # 看 RPC 服务与端口
exportfs -v # 服务端:看实际生效的导出
exportfs -ra # 服务端:重载 exports
mount -t nfs4 -o vers=4.2,hard SERVER:/data /mnt/data
nfsstat -m # 看挂载参数与统计
nfsiostat 2 # 实时 I/O 与延迟
umount -l /mnt/data # 服务器挂了时救急
cat /proc/mounts | grep nfs # 不会 hang 的挂载点查看方式
ps -eo pid,stat,wchan:30,cmd | awk '$2~/D/' # 找 D 状态进程

常见面试题

Q1:NFSv3 和 NFSv4 最本质的区别是什么? A:状态性。v3 是无状态的——服务器不记录任何客户端信息,每个请求自带完整上下文(文件句柄+偏移量),好处是服务器重启后客户端重试即可自动恢复,坏处是文件锁必须靠外挂的 NLM/NSM 协议、缓存一致性弱、需要 rpcbind 和多个动态端口。v4 是有状态的——引入 OPEN/CLOSE、stateid、租约和委托,锁整合进主协议,只需 2049 一个端口,还有 COMPOUND 复合操作大幅减少 RTT。代价是服务器重启需要 90 秒宽限期恢复状态。

Q2:什么是文件句柄?为什么不直接用路径名? A:文件句柄是服务器分配的一串不透明字节(v3 ≤64B,v4 ≤128B),内部通常编码 {fsid, inode, generation}。不用路径名有三个原因:① 无状态设计要求每个请求自包含,路径名需要服务器每次重新解析全路径,开销大;② 路径名会变(rename 后同一文件路径不同),句柄绑定 inode 更稳定;③ 安全——句柄不暴露服务器的目录结构。代价是客户端必须逐级 LOOKUP,且句柄失效时会报 ESTALE

Q3:hardsoft 挂载怎么选? A:hard(默认)无限重试,服务器不可用时进程进入 D 状态永久阻塞,kill -9 都杀不掉,但数据绝不丢失softretrans 次重试后返回 EIO,进程能继续跑,但应用可能拿到不完整的数据而不自知,造成静默数据损坏读写数据一律用 hard。想避免 hang 的正确做法是用 x-systemd.automount/autofs + 监控,而不是改 soft

Q4:root_squash 是什么?为什么 no_root_squash 危险? A:root_squash(默认)把客户端的 UID 0 映射为 nobody(65534),防止客户端 root 以 root 身份操作服务端文件。no_root_squash 则让客户端 root 等同于服务端 root。危险在于 NFS 用 AUTH_SYS——客户端自报 UID,服务器无条件信任。任何能挂载这个共享的机器上的 root(甚至攻击者自己的笔记本)就能在服务器上以 root 权限写文件,比如写一个 SUID 的 shell 然后本地提权。

Q5:为什么在 NFS 上跑数据库/SQLite 是坏主意? A:三个原因:① 锁语义不可靠——v3 的 NLM 锁在网络分区时行为不确定,SQLite 依赖 POSIX 咨询锁做并发控制,在 NFS 上可能失效导致数据库损坏;② 缓存一致性弱——NFS 只保证 close-to-open 语义,属性缓存默认 60 秒,两个客户端看到的文件状态可能不一致;③ 随机小 I/O 性能差——每次 I/O 都是网络往返。真要在网络存储上跑数据库应该用 iSCSI(块级) 或数据库自带的复制。

Q6:Stale file handle 为什么会出现?怎么根治? A:客户端持有的文件句柄在服务端已失效——文件被删除重建、服务端重新导出、或底层设备变化导致 fsid 改变。临时解决是 cd 出去再进来(触发重新 LOOKUP)或 umount -l 后重挂。根治方案是在 /etc/exports 里显式指定 fsid=<UUID>,让文件系统 ID 不随设备/重启变化,句柄就能保持稳定。

Q7:NFSv3 为什么难穿防火墙? A:v3 除了固定的 2049,还依赖 rpcbind(111)、mountd、statd、lockd、rquotad,而后面这几个默认每次启动随机分配端口,防火墙根本没法配规则。解决办法两个:① 在 /etc/nfs.conf 里给 mountd/statd/lockd 固定端口再放行;② 直接用 NFSv4 —— 它废弃了 rpcbind 和 MOUNT 协议,锁也整合进主协议,只需要 2049 一个端口

Q8:WRITE 的 UNSTABLE + COMMIT 机制解决什么问题? A:性能与可靠性的矛盾。如果每次 WRITE 都要求 FILE_SYNC(落盘才返回),吞吐会极低。所以客户端用 UNSTABLE 高速批量写(服务器写内存即返回),最后发一个 COMMIT 让服务器统一 flush。关键在于服务器每次返回一个 write verifier——如果客户端发现 COMMIT 返回的 verifier 与之前 WRITE 返回的不同,说明服务器中途重启过、缓存里的数据丢了,客户端就重发全部未确认的写。这样既有异步写的性能,又有同步写的可靠性。

Q9:NFS 和 SMB 怎么选? A:看客户端操作系统和语义需求。纯 Linux/Unix 环境选 NFS——POSIX 语义原生(mode 位、硬链接、区分大小写、可删除已打开文件)、内核态实现性能好、大文件顺序 I/O 强。有 Windows 客户端选 SMB——Windows 原生支持、ACL 权限模型匹配、SMB3 内置 AES 加密和 Kerberos 认证开箱即用、小文件和元数据操作更快。混合环境可以用 Samba 同时导出(但要注意两种权限模型的映射问题)。

Q10:客户端和服务端 UID 不一致会怎样?怎么解决? A:NFSv3 传的是数字 UID。如果服务端 zhangsan 是 1001 而客户端是 1050,客户端会看到文件属主显示为陌生的 “1001”,且权限判断完全错乱(客户端的 1050 会被当成服务端的 1050 用户)。解决方案:① 用 LDAP/NIS 统一全网 UID/GID(根本方案);② 用 NFSv4 + idmapd,传 user@domain 字符串而非数字;③ 小规模场景用 all_squash + anonuid/anongid 强制统一映射。另外注意 AUTH_SYS 的辅助组最多 16 个,用户组多时要在服务端开 rpc.mountd --manage-gids