NFS(Net File System)

NFS 挂载问题深度复盘与标准工作流建立

前言

本文档旨在复盘一次在嵌入式 BusyBox 系统 (david 主机) 挂载 Ubuntu NFS 服务器 (Anastasia-Ubuntu) 时,反复出现 Connection refused 错误的排查过程。最终,通过添加 -o nolock 挂载选项成功解决了问题。本文将详细分析失败的根本原因,并建立一套标准的 NFS 服务端与客户端配置工作流,为未来的部署提供可靠参考。


Part 1: Connection refused 问题深度复盘

1.1 问题现象

在嵌入式客户端 (david) 上,尝试使用标准命令挂载 NFS 服务器 (192.168.3.164) 上的共享目录,始终遭遇 Connection refused 错误,即便是修正了多次语法错误之后。

关键日志复现:

[root@david:/mnt]# mount -t nfs 192.168.3.164:/mnt/nfs/rv1126 /mnt/nfs
mount: mounting 192.168.3.164:/mnt/nfs/rv1126 on /mnt/nfs failed: Connection refused

然而,当添加 -o nolock 选项后,挂载立即成功。

成功挂载日志:

[root@david:/mnt]# mount -t nfs 192.168.3.164:/mnt/nfs/rv1126 -o nolock /mnt/nfs
[root@david:/mnt]# df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/root                 2.9G    751.8M      2.1G  26% /
...
192.168.3.164:/mnt/nfs/rv1126
                        453.2G    236.4G    193.8G  55% /mnt/nfs

1.2 排查之路:排除所有“常规嫌犯”

在定位到 -o nolock 之前,我们进行了地毯式的排查,逐步排除了所有常见的 NFS 故障原因。

  • 服务器NFS服务: 通过在服务器上执行 rpcinfo -p localhost,确认了 NFS v3 和 v4 服务,以及 mountd 服务均在正常运行并监听网络端口。 关键日志证据:
    james@Anastasia-Ubuntu:~$ rpcinfo -p localhost
       program vers proto   port  service
       ...
        100005    3   tcp  13025  mountd
        100003    3   tcp   2049  nfs
        100003    4   tcp   2049  nfs
       ...
    
  • 服务器防火墙: 通过 sudo ufw status 确认防火墙处于 inactive 状态,不存在网络拦截。
  • 服务器导出配置 (/etc/exports): 确认了共享目录已正确导出给所有客户端 (*),并为兼容嵌入式设备添加了 insecure 选项。
  • 客户端内核支持: 通过在客户端执行 cat /proc/filesystems,确认了内核已编译 nfs 文件系统支持。 关键日志证据:
    [root@david:/root]# cat /proc/filesystems
    ...
    nodev   nfs
    ...
    
  • 客户端功能完好性: 一个决定性的线索是“客户端 david 可以成功挂载其他PC的NFS共享”,这证明了客户端的 NFS 功能本身没有问题。

所有常规检查都显示“一切正常”,但问题依旧,这迫使我们将问题焦点转向 NFS 协议的更深层次细节。

1.3 核心原因解析:-o nolock 为何是关键?

根本原因在于客户端与服务器之间关于“文件锁”功能的通信失败。

  1. 文件锁 (File Locking) 机制: NFS 协议为了保证多客户端访问同一文件时的数据一致性,实现了一套文件锁机制。这个机制依赖于一个独立的协议——NLM (Network Lock Manager)。一个标准的 NFS 挂载流程,除了建立数据传输通道,还需要客户端与服务器的“锁管理器”服务(nlockmgr)进行“握手”,以便后续处理文件锁请求。

  2. 客户端的功能缺失: 我们的客户端 david 是一个高度精简的 BusyBox 系统。为了极致地压缩体积,它的 NFS 客户端实现很可能只包含了核心的数据读写功能,而阉割了完整的 NLM 文件锁客户端模块

  3. 失败的“握手”: 当 david 尝试挂载时,它遵循标准流程。在数据通道建立后,它尝试与服务器的 nlockmgr 服务进行通信。但由于自身功能不完整,它发送的请求可能是服务器无法理解的畸形请求。服务器的 RPC 系统在收到这个无效的请求后,无法处理,因此主动拒绝了这次连接 (Connection refused),导致整个挂载操作失败。

  4. -o nolock 的作用: 这个选项是一个指令,它告诉客户端内核:“在本次挂载中,完全禁用 NLM 文件锁协议。” 内核在收到这个指令后,会跳过与服务器 nlockmgr 服务进行通信的步骤。由于这个必然失败的步骤被绕过了,挂载流程的其他部分得以顺利完成,最终挂载成功。

结论: Connection refused 并非源于网络不通或权限不足,而是由于精简的嵌入式客户端无法完成标准 NFS 挂载流程中的“文件锁协商”步骤,从而被功能完善的服务器所拒绝。-o nolock 选项是解决此类因客户端功能不完整而导致挂载失败问题的关键。


Part 2: 标准 NFS 搭建工作流 (Workflow)

以下工作流旨在提供一个可靠、可复现的 NFS 环境搭建流程,特别考虑了将嵌入式设备作为客户端的场景。

2.1 服务端 (Server) 配置 - Ubuntu/Debian 示例

步骤 1: 安装 NFS 服务

sudo apt update
sudo apt install nfs-kernel-server

步骤 2: 创建并准备共享目录

# 示例:创建一个名为 nfs_share 的共享目录
sudo mkdir -p /mnt/nfs_share
# 赋予宽松的权限,确保客户端有权访问
sudo chown nobody:nogroup /mnt/nfs_share
sudo chmod 777 /mnt/nfs_share

步骤 3: 配置导出文件 /etc/exports (核心)

使用文本编辑器打开 /etc/exports

sudo nano /etc/exports

添加一行来定义共享规则。以下是推荐的配置,兼容性强且适合开发环境:

/mnt/nfs_share   *(rw,sync,no_subtree_check,no_root_squash,insecure)

选项解析与备注:

  • /mnt/nfs_share: 要共享的目录的绝对路径
  • *: 允许来自任何 IP 地址的客户端访问。为提高安全性,可替换为特定网段,如 192.168.3.0/24
  • rw: 允许读写操作。
  • sync: 同步写入数据(而不是异步 async),数据更安全,不易丢失。
  • no_subtree_check: 禁用子树检查,可以提高性能和稳定性。
  • no_root_squash: 允许客户端的 root 用户在挂载点上拥有 root 权限。对于嵌入式开发和调试至关重要。
  • insecure: 【备注1: 嵌入式设备兼容性关键】 允许来自非特权端口(>1024)的客户端连接。许多精简的嵌入式 NFS 客户端无法使用特权端口,缺少此选项是导致 Connection refused 的常见原因

步骤 4: 使配置生效

每次修改 /etc/exports 后,必须执行此命令刷新配置:

sudo exportfs -ra

为确保所有服务都已更新,可以重启 NFS 服务:

sudo systemctl restart nfs-kernel-server

步骤 5: 检查防火墙 (如果开启)

如果服务器防火墙 ufw 处于激活状态,需要添加入站规则:

# 查看状态
sudo ufw status

# 允许整个局域网的 NFS 连接 (推荐)
sudo ufw allow from 192.168.3.0/24 to any port nfs

2.2 客户端 (Client) 配置 - 以嵌入式 david 为例

步骤 1: 确认客户端基础能力

在执行挂载前,进行快速诊断,确保基本条件满足。

# 1. 检查内核是否支持 NFS
cat /proc/filesystems | grep nfs
# 预期输出应包含 "nfs"

# 2. 检查网络连通性
ping <服务器IP地址>

步骤 2: 创建本地挂载点

mkdir -p /mnt/server_share

步骤 3: 执行挂载命令 (核心)

使用包含兼容性选项的完整命令进行挂载:

mount -t nfs -o nfsvers=3,nolock <服务器IP>:<服务器共享目录绝对路径> <本地挂载点>

具体示例:

mount -t nfs -o nfsvers=3,nolock 192.168.3.164:/mnt/nfs_share /mnt/server_share

选项解析与备注:

  • -t nfs: 明确指定文件系统类型为 NFS。
  • -o nfsvers=3: 【备注2: 嵌入式设备兼容性关键】 明确指定使用 NFSv3 协议。嵌入式客户端对更复杂的 NFSv4 协议支持可能不完善,强制使用 v3 可以避免很多认证和协议协商问题。
  • -o nolock: 【备注3: 嵌入式设备兼容性关键】 禁用文件锁(NLM)协议。如本次复盘所示,精简的客户端可能缺少 NLM 功能,此选项是解决因锁协商失败而导致 Connection refused 的终极手段

步骤 4: 验证挂载

# 1. 检查挂载列表
df -h
# 或
mount

# 2. 检查是否能读写文件
ls /mnt/server_share
touch /mnt/server_share/test_file_from_david

步骤 5: 设置开机自动挂载 (可选)

如果需要设备开机后自动挂载,编辑 /etc/fstab 文件。 注意: 嵌入式系统的 /etc/fstab 可能不存在或位于只读分区,需谨慎操作。

$$# <服务器IP>:<共享目录> <本地挂载点> nfs <选项> 0 0 192.168.3.164:/mnt/nfs_share /mnt/server_share nfs nfsvers=3,nolock,auto,bg 0 0$$
  • auto: 系统启动时自动挂载。
  • bg: 如果首次挂载失败(如网络未就绪),则在后台继续尝试。对嵌入式设备非常有用。