<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Nfs · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/nfs/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/nfs/index.xml" rel="self" type="application/rss+xml"/><item><title>NFS 把远程目录通过网络共享出去</title><link>https://www.xbcatm.com/zh/posts/nfs-exporting-directories/</link><pubDate>Wed, 23 Jun 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/nfs-exporting-directories/</guid><description>NFS 把目录导出到网络，客户端像本地路径一样挂载它，由单个服务器统一维护那份权威副本。</description><content:encoded>&lt;p&gt;一个 NFS 导出，是服务器上的一个目录加上一份允许访问它的客户端名单；而描述它的那个文件，并不是真正对外服务的东西。&lt;code&gt;/etc/exports&lt;/code&gt; 由 &lt;code&gt;exportfs&lt;/code&gt; 读取，&lt;code&gt;exportfs&lt;/code&gt; 把主导出表写进 &lt;code&gt;/var/lib/nfs/etab&lt;/code&gt;，客户端发来 MOUNT 请求时，是 &lt;code&gt;rpc.mountd&lt;/code&gt; 去查这张表。三个文件：一个由人编辑，另外两个才决定实际发生什么。&lt;/p&gt;
&lt;p&gt;这层间接就是最常见的 NFS 事故的来源：导出列表看着没问题，客户端却被拒，因为文件变了而表没变。剩下的配置归结为两个更难的问题——服务器认为一个请求带着什么身份，以及一个回答“已写入”的回复能不能挺过服务器崩溃。&lt;/p&gt;
&lt;h2 id="文件与表"&gt;文件与表&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/etc/exports&lt;/code&gt; 的一行是 &lt;code&gt;&amp;lt;目录&amp;gt; &amp;lt;客户端&amp;gt;[(&amp;lt;选项&amp;gt;)] ...&lt;/code&gt;，所以 &lt;code&gt;/srv/data 10.0.0.0/24(rw,sync,no_subtree_check,root_squash)&lt;/code&gt; 把某个目录以四个选项导出给一个网段，同一行后面还能再接一组客户端、带自己的一套选项。客户端可以写成主机名、通配符、CIDR 网段或 netgroup；&lt;code&gt;#&lt;/code&gt; 之后到行尾是注释，行尾反斜杠表示续行。文件里的内容在 &lt;code&gt;exportfs -ra&lt;/code&gt; 依据它重建表之前一律不生效，而 &lt;code&gt;exportfs -v&lt;/code&gt; 打印的是真正被加载的那张表，所以它既能用来核对文件里的每一条，也能用来发现表里多出来的东西。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;showmount -e&lt;/code&gt; 从客户端一侧展示同一张表：它问的是 mount 守护进程而不是文件，所以它回答的是“服务器认为自己导出了什么”，而不是“我刚才写了什么”。fixture 精确复现了这个陷阱：把 &lt;code&gt;/srv/newshare&lt;/code&gt; 追加进导出文件却没有重新导出之后，文件里有四条，表里只有三条，客户端请求这个新共享得到的是 NOENT。&lt;/p&gt;
&lt;h2 id="选项真正决定了什么"&gt;选项真正决定了什么&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ro&lt;/code&gt; 与 &lt;code&gt;rw&lt;/code&gt; 决定写方向，而且只读是默认值：不写 &lt;code&gt;rw&lt;/code&gt;，任何会改动文件系统的请求都会被拒绝——这一点常被忽略，默认是只读，要让人写入必须显式写上 &lt;code&gt;rw&lt;/code&gt;。&lt;code&gt;sync&lt;/code&gt; 与 &lt;code&gt;async&lt;/code&gt; 决定服务器什么时候可以回答——&lt;code&gt;sync&lt;/code&gt; 是先提交到稳定存储再回复，&lt;code&gt;async&lt;/code&gt; 则可能先回复、后提交，文档明确说这会让一次崩溃丢掉已经应答过的写入。&lt;code&gt;async&lt;/code&gt; 是 nfs-utils 1.0.0 之前的默认值；从那时起默认就是 &lt;code&gt;sync&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;squash 系列选项是身份策略。&lt;code&gt;root_squash&lt;/code&gt; 把远端的 uid 0 映射成匿名 uid，绝大多数场合都该用它；&lt;code&gt;no_root_squash&lt;/code&gt; 让客户端的 root 在服务器上就是 root，只有在你本来就信任那台主机时才说得过去；&lt;code&gt;all_squash&lt;/code&gt; 配合 &lt;code&gt;anonuid&lt;/code&gt;/&lt;code&gt;anongid&lt;/code&gt; 把所有客户端 id 都映射成同一个匿名身份，这才是公开共享该有的形状。&lt;code&gt;subtree_check&lt;/code&gt; 与 &lt;code&gt;no_subtree_check&lt;/code&gt; 是可靠性开关，不是安全开关：开启子树检查时服务器要确认被请求的文件仍在导出子树内，于是文件在客户端打开期间被重命名就可能访问失败；自 nfs-utils 1.1.0 起默认是 &lt;code&gt;no_subtree_check&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="挂载以及-id-映射陷阱"&gt;挂载，以及 id 映射陷阱&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mount -t nfs -o vers=4.2 server:/srv/data /mnt/data&lt;/code&gt; 把导出挂进来；用 NFSv4 时你可以只导出一次 &lt;code&gt;/&lt;/code&gt;（配 &lt;code&gt;fsid=0&lt;/code&gt;），再按路径挂载子树。优先用 &lt;code&gt;hard&lt;/code&gt;（也是默认值），因为 &lt;code&gt;soft&lt;/code&gt; 挂载在 &lt;code&gt;retrans&lt;/code&gt; 次重传之后就放弃并向应用返回 EIO，man page 明确警告这可能导致静默的数据损坏；代价是服务器不可达时，一次 &lt;code&gt;ls&lt;/code&gt; 会变成卡在 &lt;code&gt;D&lt;/code&gt; 状态的进程。&lt;code&gt;intr&lt;/code&gt; 已经帮不上忙：自内核 2.6.25 起它就被忽略了。&lt;/p&gt;
&lt;p&gt;权限由数字 uid/gid 对照服务器上的账号决定，而不是按名字，所以一个在服务器上没有对应账号的客户端 id 会落到匿名 uid 上。两边都跑 &lt;code&gt;id&lt;/code&gt;、再在挂载点上跑 &lt;code&gt;ls -ln&lt;/code&gt;，就是那个检查：你自己拥有的文件显示成 65534，说明是映射错了，而不是权限位错了。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;最经典的是导出表过期，而且两个方向都会出问题。加进文件却从未导出的共享是不可见的；从文件里删掉却从未重新导出的共享仍在对外服务，对象正是你以为已经锁掉的那些客户端。两种情况都靠 &lt;code&gt;exportfs -ra&lt;/code&gt; 修好，也都能通过把 &lt;code&gt;/etc/exports&lt;/code&gt; 与 &lt;code&gt;exportfs -v&lt;/code&gt; 对比看出来。&lt;/p&gt;
&lt;p&gt;第二种是客户端卡死。对不可达服务器的 &lt;code&gt;hard&lt;/code&gt; 挂载会在内核里无限阻塞，于是所有碰这个挂载点的进程都杀不掉，机器上越积越多 &lt;code&gt;D&lt;/code&gt; 状态进程；fixture 里那条 &lt;code&gt;showmount -e 127.0.0.1&lt;/code&gt; 花了 35 秒才失败，正是同一个 RPC 超时，让一个坏掉的 NFS 挂载看起来像 &lt;code&gt;ls&lt;/code&gt; 卡住，而不像报错。那个诱人的修法 &lt;code&gt;soft&lt;/code&gt;，是用静默的写入损坏去换掉这次卡死。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;把已加载的表与文件对照，然后检查 id：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;exportfs -v
showmount -e localhost
exportfs -ra &amp;amp;&amp;amp; exportfs -v
mount | grep &amp;#39;type nfs&amp;#39;
id; ls -ln /mnt/data | head
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;exportfs -v&lt;/code&gt; 必须与 &lt;code&gt;/etc/exports&lt;/code&gt; 里的目录和选项集合完全一致（顺序无所谓，集合不能差）；少了什么就说明表是过期的，此时文件看着没错，客户端拿到的却是 NOENT。&lt;code&gt;showmount -e&lt;/code&gt; 从远端调用时应当打印同一组目录。在客户端一侧，&lt;code&gt;id&lt;/code&gt; 与 &lt;code&gt;ls -ln&lt;/code&gt; 对数字属主的判断必须一致——显示为 65534 或 nobody，说明服务器上没有对应账号。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/exports.5.html"&gt;exports(5)&lt;/a&gt;，导出语法、squash 选项、sync/async 与 subtree_check 默认值，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/exportfs.8.html"&gt;exportfs(8)&lt;/a&gt;，/var/lib/nfs/etab 中的主导出表以及 exportfs 如何维护它，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/nfs.5.html"&gt;nfs(5)&lt;/a&gt;，挂载选项，含 hard/soft、timeo 以及 intr 的失效，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/showmount.8.html"&gt;showmount(8)&lt;/a&gt;，从客户端查询服务器的导出列表，已核验 2026-09-16。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>