NFS 共享存储实战:从挂载到生产读写分离

业务上线后没两天就会遇到一个问题:文件上传功能在多台服务器上各存各的,用户在 A 机传的图片,请求打到 B 机就 404 了。解法是让所有机器挂载同一份存储——NFS 是内网共享存储里最便宜、最常见的选择。这篇把 NFS 从原理到生产读写分离完整过一遍。

一、先理解 NFS 在干什么

NFS(Network File System)让你像用本地目录一样用远程目录。数据真正落在服务端,客户端只做挂载和读写。涉及两个角色:

  • 服务端:共享目录方,监听 2049 端口;NFSv3 还需要 rpcbind(端口映射服务)配合;
  • 客户端:挂载方,本地挂载点只是一个”门”,读写都打到服务端。

一句话记忆:服务端出目录、客户端进目录,数据永远在服务端那份。

二、服务端配置(6 步)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 安装(内网按网段放行 2049/rpcbind 即可,不建议直接全关防火墙)
yum install -y nfs-utils

# 2. 配置共享目录 /etc/exports
# 格式:共享路径 客户端地址(权限参数)
/data 172.18.1.0/24(rw,sync,no_root_squash) # 注意客户端地址与参数间没有空格

# 3. 创建共享目录并授权
mkdir -p /data && chown -R nfsnobody:nfsnobody /data

# 4. 启动并加入开机自启(rpcbind 提供 NFSv3 端口映射,一起启)
systemctl start rpcbind && systemctl start nfs
systemctl enable rpcbind nfs

# 5. 验证:端口监听 + 实际生效的导出信息
ss -tulnp | grep 2049
cat /var/lib/nfs/etab # etab 里有 /data 记录,说明 exports 配置正确
exportfs -rv # 修改 exports 后平滑加载,无需重启

三、客户端挂载(6 步)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 安装客户端
yum install -y nfs-utils

# 2. 查看服务端共享了什么
showmount -e 172.18.1.31

# 3. 挂载
mkdir -p /nfsdir
mount -t nfs 172.18.1.31:/data /nfsdir

# 4. 验证
df -h | grep nfsdir
# 172.18.1.31:/data 62G 880M 58G 2% /nfsdir

# 5. 写入测试:客户端写,服务端能看到同一份
echo "test" > /nfsdir/test.txt && ls -l /data/

# 6. 开机挂载(生产必须加 _netdev,避免网络未就绪时挂载失败卡开机)
echo '172.18.1.31:/data /nfsdir nfs defaults,_netdev 0 0' >> /etc/fstab
mount -a # 写完 fstab 必须用 mount -a 验证

卸载时提示 device is busy 是高频小坑:先 cd / 再 umount;实在不行 umount -lf 强制卸载。

四、权限与安全:生产必配的参数

NFS 的权限坑集中在身份映射上。服务端默认把客户端 root 映射成 nobody,但普通用户的 uid/gid 会原样透传——两边 uid 对不上就会出现”文件能看到但没权限写”。

统一身份的标准做法:all_squash + anonuid/anongid

1
2
3
4
5
6
7
8
9
# 服务端 exports:
/data 172.18.1.0/24(rw,sync,all_squash,anonuid=666,anongid=666)

# 服务端创建对应用户并授权
useradd -u 666 -g 666 www -s /sbin/nologin
chown -R 666.666 /data

# 客户端也建同 uid 用户(统一身份,避免权限不足)
useradd -u 666 -g 666 www -s /sbin/nologin

这样不管客户端是谁写的,落盘统一变成 uid=666,权限问题一次性根治。

安全挂载参数(静默数据不需要执行权限):

1
2
3
mount -t nfs -o noexec,nodev,nosuid 172.18.1.31:/data /nfsdir
# noexec:禁止执行(防上传木马被直接运行)
# nodev / nosuid:禁设备文件与提权位

性能参数(可选): noatime,nodiratime 禁止更新访问时间戳,减少 NFS 写放大,高 IO 场景明显。

五、生产案例:读写分离

需求:静态资源读多写少,且不同目录权限隔离——/data/r 只读、/data/w 可写,都映射到 www(666)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 服务端:
mkdir -p /data/{r,w}
echo 'www:x:666:666::/home/www:/sbin/nologin' >> /etc/passwd # 或 useradd -u 666
cat >> /etc/exports <<'EOF'
/data/r 172.18.1.0/24(ro,sync,all_squash,anonuid=666,anongid=666)
/data/w 172.18.1.0/24(rw,sync,all_squash,anonuid=666,anongid=666)
EOF
exportfs -rv

# 客户端:
mount -t nfs 172.18.1.31:/data/r /data/r
mount -t nfs 172.18.1.31:/data/w /data/w
touch /data/r/xx && echo "Read-only file system" # 只读目录写入报错(预期)
echo ok > /data/w/test # 可写目录正常

六、排障要点

  1. 挂载报 access denied:路径写错最常见——showmount -e 服务端 先看服务端实际导出的名字;
  2. 挂载卡住/超时:检查 rpcbind 是否启动、2049 与 rpc 端口(111)防火墙是否放行;
  3. 改完 exports 不生效exportfs -rv 平滑加载,别重启 NFS 中断业务;
  4. 客户端看到文件但没权限:九成是 uid 不一致,按上面统一 anonuid 方案处理。

NFS 本身不复杂,把”数据在服务端、权限靠 anonuid、安全靠 noexec”这三条记住,生产基本够用。它解决的是”共享”,备份和灾备就是下一篇文章的事了。