一个 NFS 导出,是服务器上的一个目录加上一份允许访问它的客户端名单;而描述它的那个文件,并不是真正对外服务的东西。/etc/exports 由 exportfs 读取,exportfs 把主导出表写进 /var/lib/nfs/etab,客户端发来 MOUNT 请求时,是 rpc.mountd 去查这张表。三个文件:一个由人编辑,另外两个才决定实际发生什么。

这层间接就是最常见的 NFS 事故的来源:导出列表看着没问题,客户端却被拒,因为文件变了而表没变。剩下的配置归结为两个更难的问题——服务器认为一个请求带着什么身份,以及一个回答“已写入”的回复能不能挺过服务器崩溃。

文件与表

/etc/exports 的一行是 <目录> <客户端>[(<选项>)] ...,所以 /srv/data 10.0.0.0/24(rw,sync,no_subtree_check,root_squash) 把某个目录以四个选项导出给一个网段,同一行后面还能再接一组客户端、带自己的一套选项。客户端可以写成主机名、通配符、CIDR 网段或 netgroup;# 之后到行尾是注释,行尾反斜杠表示续行。文件里的内容在 exportfs -ra 依据它重建表之前一律不生效,而 exportfs -v 打印的是真正被加载的那张表,所以它既能用来核对文件里的每一条,也能用来发现表里多出来的东西。

showmount -e 从客户端一侧展示同一张表:它问的是 mount 守护进程而不是文件,所以它回答的是“服务器认为自己导出了什么”,而不是“我刚才写了什么”。fixture 精确复现了这个陷阱:把 /srv/newshare 追加进导出文件却没有重新导出之后,文件里有四条,表里只有三条,客户端请求这个新共享得到的是 NOENT。

选项真正决定了什么

ro 与 rw 决定写方向,而且只读是默认值:不写 rw,任何会改动文件系统的请求都会被拒绝——这一点常被忽略,默认是只读,要让人写入必须显式写上 rw。sync 与 async 决定服务器什么时候可以回答——sync 是先提交到稳定存储再回复,async 则可能先回复、后提交,文档明确说这会让一次崩溃丢掉已经应答过的写入。async 是 nfs-utils 1.0.0 之前的默认值;从那时起默认就是 sync。

squash 系列选项是身份策略。root_squash 把远端的 uid 0 映射成匿名 uid,绝大多数场合都该用它;no_root_squash 让客户端的 root 在服务器上就是 root,只有在你本来就信任那台主机时才说得过去;all_squash 配合 anonuid/anongid 把所有客户端 id 都映射成同一个匿名身份,这才是公开共享该有的形状。subtree_check 与 no_subtree_check 是可靠性开关,不是安全开关:开启子树检查时服务器要确认被请求的文件仍在导出子树内,于是文件在客户端打开期间被重命名就可能访问失败;自 nfs-utils 1.1.0 起默认是 no_subtree_check。

挂载,以及 id 映射陷阱

mount -t nfs -o vers=4.2 server:/srv/data /mnt/data 把导出挂进来;用 NFSv4 时你可以只导出一次 /(配 fsid=0),再按路径挂载子树。优先用 hard(也是默认值),因为 soft 挂载在 retrans 次重传之后就放弃并向应用返回 EIO,man page 明确警告这可能导致静默的数据损坏;代价是服务器不可达时,一次 ls 会变成卡在 D 状态的进程。intr 已经帮不上忙:自内核 2.6.25 起它就被忽略了。

权限由数字 uid/gid 对照服务器上的账号决定,而不是按名字,所以一个在服务器上没有对应账号的客户端 id 会落到匿名 uid 上。两边都跑 id、再在挂载点上跑 ls -ln,就是那个检查:你自己拥有的文件显示成 65534,说明是映射错了,而不是权限位错了。

坏掉的样子

最经典的是导出表过期,而且两个方向都会出问题。加进文件却从未导出的共享是不可见的;从文件里删掉却从未重新导出的共享仍在对外服务,对象正是你以为已经锁掉的那些客户端。两种情况都靠 exportfs -ra 修好,也都能通过把 /etc/exports 与 exportfs -v 对比看出来。

第二种是客户端卡死。对不可达服务器的 hard 挂载会在内核里无限阻塞,于是所有碰这个挂载点的进程都杀不掉,机器上越积越多 D 状态进程;fixture 里那条 showmount -e 127.0.0.1 花了 35 秒才失败,正是同一个 RPC 超时,让一个坏掉的 NFS 挂载看起来像 ls 卡住,而不像报错。那个诱人的修法 soft,是用静默的写入损坏去换掉这次卡死。

快速验证

把已加载的表与文件对照,然后检查 id:

exportfs -v
showmount -e localhost
exportfs -ra && exportfs -v
mount | grep 'type nfs'
id; ls -ln /mnt/data | head

exportfs -v 必须与 /etc/exports 里的目录和选项集合完全一致(顺序无所谓,集合不能差);少了什么就说明表是过期的,此时文件看着没错,客户端拿到的却是 NOENT。showmount -e 从远端调用时应当打印同一组目录。在客户端一侧,id 与 ls -ln 对数字属主的判断必须一致——显示为 65534 或 nobody,说明服务器上没有对应账号。

来源

  • exports(5),导出语法、squash 选项、sync/async 与 subtree_check 默认值,已核验 2026-09-16。
  • exportfs(8),/var/lib/nfs/etab 中的主导出表以及 exportfs 如何维护它,已核验 2026-09-16。
  • nfs(5),挂载选项,含 hard/soft、timeo 以及 intr 的失效,已核验 2026-09-16。
  • showmount(8),从客户端查询服务器的导出列表,已核验 2026-09-16。