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.status、rpc.time、xid三处快速定位是权限问题、慢请求还是服务器无响应。 - 遇到
Permission denied、Stale file handle、进程 D 状态 hang 死、UID 错乱、性能差时,先记住结论、再按图索骥地排查,以及 NFS 与 SMB 该怎么选型。
抓包观察
Wireshark 显示过滤式
下面这些过滤式是排错时最高频的武器,先扫一眼有个印象,后文"排错三看"会用到:
| 过滤式 | 用途 |
|---|---|
nfs | 所有 NFS 报文 |
rpc | RPC 层(含 mount / nlm / portmap) |
tcp.port == 2049 | NFS 主端口 |
portmap 或 tcp.port == 111 || udp.port == 111 | rpcbind 端口查询(v3 挂载第一步) |
mount | MOUNT 协议(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.opcode | NFSv4 的 COMPOUND 内操作码 |
命令行抓包:下面这套 tcpdump/tshark 命令覆盖"抓完整会话、统计各操作耗时、捞慢请求、验证 AUTH_SYS 明文 UID、统计错误分布"五个常见动作。
| |
典型字段说明
一次 v3 READ 在 Wireshark 中是两层结构(外层 RPC、内层 NFS),重点看 Program/Procedure 怎么定位调用、以及 AUTH_UNIX 里那串明文 UID:
| |
排错三看:
nfs.status:NFS3ERR_ACCES(13)权限;NFS3ERR_STALE(70)句柄失效;NFS3ERR_NOSPC(28)空间满;NFS3ERR_PERM(1)操作不允许;NFS3ERR_JUKEBOX(10008)服务器忙(客户端会重试)。rpc.time:Wireshark 自动计算的 RPC 往返耗时。正常局域网应在毫秒级;持续 >100ms 说明服务端或网络有问题。- 有没有大量重传:同一
xid反复出现 = 客户端在重试 = 服务器无响应或丢包。配合nfsstat -rc的retrans计数验证。
常用命令 / 配置
服务端配置(Linux)
安装与启动:下面两条是 Debian/Ubuntu 与 RHEL/Rocky 两系发行版的安装与服务启用方式。
/etc/exports 语法与完整选项:这一条是 NFS 安全与权限的总开关,尤其注意"空格"那个坑。
| |
关键选项详解:
| 选项 | 含义 | 建议 |
|---|---|---|
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 让配置生效或临时查看/取消导出。
| |
固定 v3 端口(为了穿防火墙):v3 的 mountd/statd/lockd 默认随机端口,防火墙没法配规则;下面在 /etc/nfs.conf 里把它们钉死再放行。
| |
客户端操作
下面这组命令是客户端的"日常五件套":探测服务器导出了什么、RPC 服务是否可达、怎么用 v3/v4/Kerberos 挂载、怎么查看和卸载。
| |
/etc/fstab 持久化:把挂载写进 fstab 时,关键是避免开机因网络未就绪而 hang 死,_netdev 和 x-systemd.automount 是标配。
| |
挂载参数速查:这些参数直接决定"卡不卡、快不快、丢不丢数据",先记住 hard 默认、proto 一律 tcp、noatime 强烈建议。
| 参数 | 默认 | 说明 |
|---|---|---|
vers=3|4|4.0|4.1|4.2 | 协商最高 | 显式指定避免协商问题 |
proto=tcp|udp | tcp | 一律用 tcp |
hard | ✅ 默认 | 无限重试,保证数据不丢,代价是可能 hang |
soft | — | 超时返回 EIO,有静默数据损坏风险 |
timeo=N | 600(=60 秒,单位 0.1s) | 单次 RPC 超时 |
retrans=N | 2(TCP)/ 3 | 重传次数 |
rsize / wsize | 服务器协商(常 1MB) | 单次读写块大小。太小会导致大量小 I/O,nfsstat -m 可看实际值 |
noatime | — | 强烈建议:不更新访问时间,能省掉大量 SETATTR |
nodiratime | — | 目录同上 |
actimeo=N | — | 属性缓存时长(默认 reg 3-60s,dir 30-60s) |
nconnect=N | 1 | 多 TCP 连接并行(内核 5.3+),万兆网下 nconnect=8 能显著提升吞吐 |
sec=sys|krb5|krb5i|krb5p | sys | 认证方式 |
noexec,nosuid,nodev | — | 安全加固:不允许执行、SUID、设备文件 |
监控与统计
下面这组是"服务器卡不卡、线程够不够、谁占着挂载点"的监控命令,性能排查必用。
| |
常见故障与排错
故障 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 且 /export 是 fsid=0,客户端应挂 server:/data 而非 server:/export/data |
| SELinux | getenforce;setsebool -P nfs_export_all_rw 1 |
服务端日志是最直接的线索,拒绝挂载时 journalctl 里通常有 refused mount request 字样:
故障 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 还要 111 | nc -zv 192.168.1.100 111、rpcinfo -p 192.168.1.100 |
| v3 动态端口被墙 | 这是 v3 最常见的防火墙问题 → 固定端口(见上文 /etc/nfs.conf)或改用 v4(只需 2049) |
| 服务端服务未起 | systemctl status nfs-server rpcbind |
| 服务端监听 | ss -tlnp | grep 2049 |
| 防火墙 | firewall-cmd --list-all、iptables -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/data,chmod/chown 调整 |
挂载成 ro | `mount |
exports 是 ro | exportfs -v |
| SELinux 上下文 | ls -Z;chcon -R -t nfs_t /export/data 或 setsebool -P use_nfs_home_dirs 1 |
| 辅助组超过 16 个 | AUTH_SYS 的 gids 字段最多 16 个辅助组!用户组多于 16 个时会被截断导致权限判断错误。解法:服务端开 rpc.mountd --manage-gids(服务端自己查组,不信客户端的) |
快速验证 UID 是否一致、看 NFS 上文件实际属主:
故障 4:Stale file handle(陈旧的文件句柄)
先记住结论:你手里的文件句柄在服务端已经失效了——文件被删/重建、服务端重导、或底层设备变了导致 fsid 变了。临时 cd 出去再进来(触发重新 LOOKUP)或 umount -l 重挂即可,根治是在 exports 里钉死 fsid=<UUID>。
含义:客户端手里的文件句柄在服务端已失效。
| 原因 | 解决 |
|---|---|
| 文件/目录被服务端或其它客户端删除或重建 | 重新访问路径(cd .. && cd -)让客户端重新 LOOKUP |
| 服务端重新导出了文件系统 | sudo exportfs -ra 后客户端 remount |
| 服务端底层设备变化导致 fsid 变了 | 根治方案:exports 里显式指定 fsid=<UUID>,让句柄跨重启/换盘稳定 |
| 服务端把导出目录 remount 了 | 客户端重挂 |
故障 5:进程卡死在 D 状态,df / ls 全部 hang 住
先记住结论:这是 hard 挂载下服务器宕机/网络中断的经典表现,不是死机——进程进入 D 状态(不可中断睡眠)、kill -9 也杀不掉。别慌,恢复服务器网络后 hard 挂载会自动继续;应急用 umount -l 摘掉挂载点,实在不行只能重启客户端。切忌为了"解 hang"改 soft,那会换来静默数据损坏。
这是 NFS 最恐怖也最经典的故障:服务器宕机或网络中断,hard 挂载下所有访问该挂载点的进程进入 D 状态(不可中断睡眠),kill -9 无效。
| |
预防措施:
- 用
x-systemd.automount(按需挂载)+x-systemd.mount-timeout,避免开机和空闲时 hang; - 用 autofs 自动挂载/超时卸载,不常用的挂载点自动摘掉;
- 监控告警 NFS 服务器可用性;
- 不要为了避免 hang 而改用
soft—— soft 会导致静默数据损坏,比 hang 更可怕。
故障 6:性能差(吞吐低 / 延迟高)
先记住结论:性能差先看三处——rsize/wsize 是不是太小、retrans 重传比例高不高、nfsiostat 的 avg RTT 大不大。多数"慢"是块太小或网络丢包;小文件海量场景本身就不是 NFS 强项,别指望调参解决。
诊断顺序:
| |
调优清单:
| 措施 | 命令 / 参数 | 效果 |
|---|---|---|
| 加大读写块 | rsize=1048576,wsize=1048576 | 减少 RPC 次数,大文件吞吐显著提升 |
| 多连接并行 | nconnect=8(内核 5.3+) | 单挂载点多条 TCP,万兆网下吞吐可翻倍 |
| 关闭 atime | noatime,nodiratime | 消除大量 SETATTR |
| 用 v4.1/4.2 | vers=4.2 | COMPOUND 减少 RTT;Delegation 减少校验 |
| 增大属性缓存 | actimeo=60 | 减少 GETATTR(代价是一致性变差) |
| 服务端加线程 | /etc/nfs.conf → [nfsd] threads=64 | 高并发场景必调 |
| 用 TCP 不用 UDP | proto=tcp | UDP 丢包重传整个 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 就别裸奔,能禁止 * 就别对外开放。
| |
与其他协议对比
NFS vs SMB —— 最常被问的对比
| 维度 | NFS | SMB / 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(弱)/ Kerberos | NTLM / Kerberos(内置强认证) |
| 加密 | 需 Kerberos krb5p | SMB3 原生 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 Datastore | Windows 文件服务器、办公共享、打印共享 |
| 一句话选型 | 纯 Linux 环境选 NFS | 有 Windows 客户端选 SMB |
NFS vs 其它存储访问方式
| 维度 | NFS | SMB | iSCSI | S3/对象存储 | FTP/SFTP |
|---|---|---|---|---|---|
| 抽象层级 | 文件级 | 文件级 | 块级 | 对象级 | 文件传输 |
| 谁管文件系统 | 服务器 | 服务器 | 客户端(自己格式化) | 服务提供方 | 服务器 |
| 多客户端并发读写 | ✅ 支持 | ✅ 支持 | ❌ 需集群文件系统 | ✅(最终一致) | ❌ |
| 部分读写(随机 I/O) | ✅ | ✅ | ✅ | 需 Range 请求 | ❌ 整文件 |
| POSIX 语义 | ✅ | 部分 | ✅(本地 FS 提供) | ❌ | ❌ |
| 广域网友好 | ❌ 延迟敏感 | ❌ | ❌ | ✅ 专为 WAN 设计 | ✅ |
| 典型用途 | 共享 home、K8s PV | 办公共享 | 数据库/虚拟机磁盘 | 备份、静态资源、大数据 | 文件分发 |
速查表 / 常见面试题
端口 / 关键值速查
| 项 | 值 |
|---|---|
| NFS 主端口 | TCP 2049(v4 只需这一个) |
| rpcbind / portmapper | TCP+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/wsize | 1048576(1MB) |
一行命令速查
| |
常见面试题
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:hard 和 soft 挂载怎么选?
A:hard(默认)无限重试,服务器不可用时进程进入 D 状态永久阻塞,kill -9 都杀不掉,但数据绝不丢失;soft 在 retrans 次重试后返回 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。