4. NFS/HTTP 根文件系统挂载失败:Export 配置、路径权限、网络连通性、内核参数
说实话,根文件系统挂不上,是网络启动里最让人头疼的问题之一。你想想看,内核都起来了,IP 也拿到了,结果卡在挂载根文件系统这一步——屏幕上一行行错误刷过去,就是进不了系统。我早期做嵌入式项目时,有一半的调试时间都耗在这上面。
这一章,我们就来拆解 NFS 和 HTTP 两种方式下,根文件系统挂载失败的常见原因。我会从 Export 配置、路径权限、网络连通性、内核参数四个维度入手,把那些坑一个个填平。
4.1 NFS 根文件系统挂载:Export 配置是第一道坎
NFS 挂载根文件系统,服务端配置是第一步。很多人一上来就改 /etc/exports,然后重启服务,觉得完事了。其实没那么简单。
4.1.1 Export 选项的陷阱
先看一个典型的 exports 配置:
/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
这里最关键的是 no_root_squash。客户端内核在挂载根文件系统时,默认以 root 身份发起 NFS 请求。如果服务端没有 no_root_squash,root 会被映射成 nobody,权限不够,挂载自然失败。
rw 但忘了 no_root_squash,结果内核报 "Permission denied"。排查了半天,最后发现是 root 被 squash 了。这个选项,务必加上。
另外,sync 选项也建议加上。虽然 async 性能更好,但网络启动环境下,数据一致性更重要。我曾经因为用了 async,导致根文件系统写入时出现数据损坏,系统启动后行为诡异。
4.1.2 Export 路径与挂载路径的匹配
服务端 export 的路径,必须与客户端内核参数中指定的 NFS 路径一致。举个例子:
- 服务端 export:
/srv/nfs/rootfs - 客户端内核参数:
root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs
这里有个细节:nfsroot 参数中的路径,是服务端的绝对路径,不是相对路径。有些发行版允许用相对路径,但为了保险,我建议始终用绝对路径。
4.2 路径权限:不只是 755 那么简单
Export 配置对了,接下来就是文件系统本身的权限问题。很多人觉得,把根文件系统目录设成 755 就万事大吉了。其实不然。
4.2.1 目录权限的递归检查
NFS 挂载时,客户端内核会尝试访问根文件系统的顶层目录。如果顶层目录权限不对,挂载就会失败。但更隐蔽的问题是——子目录权限。
我记得有一次,根文件系统挂上去了,但 /dev 目录权限是 700,导致 udev 无法创建设备节点。系统启动到一半就卡住了。所以,检查权限时,要递归检查关键目录:
/:至少 755/dev:755/etc:755/bin、/sbin、/lib:755/tmp:1777
4.2.2 文件所有权与 NFS 映射
即使权限对了,所有权也可能出问题。NFS 默认使用 UID/GID 来映射用户。如果服务端和客户端的 UID 不一致,就会出现 "Permission denied"。
举个例子:服务端上 /srv/nfs/rootfs/bin/busybox 的 owner 是 UID 1000,但客户端内核以 root(UID 0)访问,按理说没问题。但如果文件权限是 750,且 group 没有读权限,那 root 也读不了。
4.3 网络连通性:别忽略二层和三层的问题
网络连通性是根文件系统挂载的基础。但很多人只检查了 IP 地址,忽略了更底层的网络问题。
4.3.1 二层连通性:ARP 和 MAC 地址
客户端内核在挂载 NFS 之前,会先发送 ARP 请求解析服务端的 MAC 地址。如果 ARP 解析失败,NFS 挂载自然无法进行。
我遇到过一种情况:客户端和服务端在同一个 VLAN,但中间有个交换机开启了端口安全,只允许特定 MAC 地址通过。客户端的 MAC 不在白名单里,ARP 请求被丢弃了。排查了很久才发现是交换机的问题。
4.3.2 三层连通性:路由和防火墙
如果客户端和服务端不在同一个网段,就需要路由。检查路由表是否正常,默认网关是否可达。
另外,防火墙也是常见问题。NFS 使用的端口包括:
| 服务 | 端口 | 协议 |
|---|---|---|
| portmapper | 111 | TCP/UDP |
| nfsd | 2049 | TCP/UDP |
| mountd | 动态端口(通常 20048) | TCP/UDP |
很多人在防火墙里只开了 2049 端口,忘了开 111 和 mountd 的端口。结果 NFS 挂载时,客户端能连上 nfsd,但 mountd 的请求被拦截了。
/etc/nfs.conf 中设置 mountd_port=20048,然后在防火墙中放行。
4.4 内核参数:编译时和启动时的双重检查
内核参数是根文件系统挂载的最后一道关卡。如果内核本身不支持 NFS 或 HTTP 根文件系统,那前面所有配置都是白费。
4.4.1 内核编译选项
内核必须编译进以下选项:
CONFIG_NFS_FS=y:NFS 文件系统支持CONFIG_NFS_V3=y或CONFIG_NFS_V4=y:NFS 版本支持CONFIG_ROOT_NFS=y:允许将 NFS 作为根文件系统CONFIG_IP_PNP=y:内核启动时自动配置 IPCONFIG_NFSD=y(服务端):NFS 服务端支持
如果是 HTTP 根文件系统,还需要:
CONFIG_BLK_DEV_NBD=y:网络块设备支持CONFIG_NFS_FS=y或CONFIG_9P_FS=y:根据具体实现选择
我见过有人编译内核时,忘了选 CONFIG_ROOT_NFS。结果内核启动时,虽然能识别 root=/dev/nfs 参数,但内部逻辑直接跳过了 NFS 挂载,报 "VFS: Unable to mount root fs"。
4.4.2 启动参数的正确写法
内核启动参数必须正确传递。NFS 根文件系统的典型参数如下:
root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs,v3,tcp rw ip=192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0:client:eth0:off
参数说明:
root=/dev/nfs:告诉内核使用 NFS 根文件系统nfsroot=:指定 NFS 服务端地址、路径、协议版本和传输协议ip=:指定客户端 IP、服务端 IP、网关、掩码、主机名、网卡、自动配置方式
HTTP 根文件系统的参数略有不同:
root=/dev/nbd0 nbdroot=192.168.1.100:/srv/http/rootfs.img rw ip=dhcp
这里 nbdroot 指定了 NBD 服务端和镜像文件路径。
4.4.3 调试参数:让内核多说点话
当根文件系统挂载失败时,内核的错误信息往往不够详细。我建议在启动参数中加入以下调试选项:
nfsrootdebug nfs.callback_tcpport=2049 nfs.nfs4_unique_id=client1
nfsrootdebug 会让内核打印 NFS 挂载的详细过程,包括发送的 RPC 请求和收到的响应。这个参数在排查问题时非常有用。
nfsrootdebug 后,发现内核发送的 mount 请求中,路径参数多了一个斜杠。原来是 nfsroot 参数中路径写成了 /srv/nfs/rootfs/,多了一个尾随斜杠。去掉后,问题解决。
4.5 知识体系:根文件系统挂载故障诊断流程图
下面这张图,是我根据多年经验总结的根文件系统挂载故障诊断流程。你可以把它当作排查指南:
4.6 实战案例:一次完整的 NFS 根文件系统挂载故障排查
最后,分享一个我实际遇到的案例。有一次,客户反馈说他们的网络启动环境总是卡在 "VFS: Unable to mount root fs via NFS"。我按照上面的流程一步步排查:
- 网络连通性:客户端能 ping 通服务端,ARP 解析正常,防火墙也放行了 NFS 端口。没问题。
- Export 配置:检查
/etc/exports,发现没有no_root_squash。加上后重启 NFS 服务,问题依旧。 - 路径权限:递归检查根文件系统目录,权限都是 755,所有者是 root。没问题。
- 内核参数:检查内核编译选项,发现
CONFIG_ROOT_NFS没有编译进内核。重新编译内核后,问题解决。
你看,有时候问题就是这么简单——一个内核编译选项没选。但如果没有系统的排查流程,你可能要在网络、配置、权限之间来回折腾好几个小时。
嗯,这一章的内容就到这里。记住,根文件系统挂载失败,不要慌。按照 Export 配置、路径权限、网络连通性、内核参数这四个维度,一步步排查,总能找到问题所在。