FreeBSD通过NFS共享ZFS数据集

我在 ZFS Stripe条带化 (FreeBSD环境实践) 构建了测试环境FreeBSD存储,其中 zdata 存储池用于存储测试数据,也包括我日常开发学习的 docs 数据集。由于我在多个测试环境中需要共享数据,我规划将 zdata 存储池的数据集用于:

FreeBSD内建OpenZFS和NFS

FreeBSD内建集成了OpenZFS和NFS使得其非常容易实现NFS共享: OpenZFS提供了一个 sharenfs 属性,可以在OpenZFS文件系统上非常方便地管理NFS共享已经集成到脚本和监控维护流程中。

FreeBSD集成的NFS服务器和客户端支持 NFSv3NFSv4 协议

准备工作

已经在 FreeBSD Jail访问ZFS文件系统 中构建了 zdata/docs 卷集:

创建卷集 zdata/docs
# 创建挂载到 /docs 目录的卷集 zdata/docs
#zfs create -o mountpoint=/docs zdata/docs

zfs create zdata/docs

Host主机设置

  • 在host主机上配置 /etc/rc.conf :

配置 /etc/rc.conf 启用NFS
nfs_server_enable="YES"
mountd_enable="YES"
rpc_lockd_enable="YES"
rpc_statd_enable="YES"
rpcbind_enable="YES"
  • 对ZFS数据集启动 sharenfs 属性,并且相应配置允许的客户端IP:

设置ZFS数据集 sharenfs 属性
#zfs set sharenfs="-rw,-alldirs,-network=192.168.7.0/24" zdata/docs
#zfs set sharenfs="[email protected]/24" zdata/docs

# 暂时没有解决权限问题,官方文档没有找到案例
# 参考solaris zfs文档存在实现偏差,例如solaris zfs文档是先设置sharefs参数,然后再设置 sharefs=on ,但是在FreeBSD上on直接覆盖了前面设置的参数
zfs set sharenfs=on zdata/docs

# 参考 https://man.freebsd.org/cgi/man.cgi?query=zfsprops&sektion=7&manpath=freebsd-release
# sharenfs=on 相当于默认共享参数: sec=sys,rw,crossmnt,no_subtree_check
  • 启动NFS服务:

启动NFS服务
service nfsd start
service mountd reload

这里执行 service nfsd start 时输出信息如下:

启动 nfsd 时信息
NFSv4 is disabled
Starting rpcbind.
/etc/rc.d/mountd: WARNING: /etc/exports is not readable.
Starting mountd.
Starting nfsd.

检查ZFS数据集 zdata/docs 属性:

检查 ZFS数据集 zdata/docssharenfs 属性
zfs get all zdata/docs | grep nfs

输出信息如下:

检查 ZFS数据集 zdata/docssharenfs 属性
zdata/docs  sharenfs              -rw,-alldirs,-network=192.168.7.0/24  local
zdata/docs  acltype               nfsv4                                 default

客户端

我的客户端实际上是想实现 Docker容器使用NFS ,也就是在客户端host主机上挂载ZFS NFS服务器的输出卷,然后再将通过 docker volume 映射到容器内部使用:

  • 在客户端Host主机上配置 /etc/fstab :

客户端Host主机 /etc/fstab
192.168.7.200:/zdata/docs  /home/admin/docs  nfs  rw,soft,intr,vers=3,proto=tcp,rsize=32768,wsize=32768 0 0
  • 在客户端执行挂载:

客户端执行挂载
mount /home/admin/docs

异常排查

  • 客户端挂载报错

客户端执行挂载报错
mount.nfs: access denied by server while mounting 192.168.7.200:/zdata/docs
mount: (hint) your fstab has been modified, but systemd still uses
       the old version; use 'systemctl daemon-reload' to reload.

为什么服务器端拒绝了客户端挂载? 我发现是我前面实践时设置了 sharenfs="-rw,-alldirs,-network=192.168.7.0/24" 导致,由于暂时没有找到合适的参考文档,所以我简化成 sharenfs=on ,这样基本使用就没有问题(完全依靠客户端的 uid/gid 权限设置)

警告

我这里的简化案例设置了 sharenfs=on ,只能用于测试环境简单使用,相当于默认参数 sec=sys,rw,crossmnt,no_subtree_check

docker容器

在客户端Host实现了简单的NFS挂载之后,启动 Debian镜像(tini进程管理器)acloud-dev :

运行包含开发环境的ARM环境debian镜像
docker run -dt --name acloud-dev --hostname acloud-dev \
    -p 1122:22 \
    -p 13000:3000 \
    -p 18080:8080 \
    -p 14000:4000 \
    -p 1180:80 \
    -p 1443:443 \
    -v /home/admin/secrets:/home/admin/.ssh \
    -v /home/admin/docs:/home/admin/docs \
    acloud-dev

# 如果需要在运行时注入环境变量,则添加类似如下参数(添加代理案例)
#    -e HTTP_PROXY=http://172.17.0.1:3128 \
#    -e HTTPS_PROXY=http://172.17.0.1:3128 \
#    -e NO_PROXY=localhost,127.0.0.1,*.baidu.com,192.168.0.0/16,10.0.0.0/8 \

此时登陆到 acloud-dev 容器中,就可以看到host主机挂载的NFS共享( df -h 输出信息):

acloud-dev 容器中检查 df -h 输出可以看到NFS挂载
Filesystem                 Size  Used Avail Use% Mounted on
overlay                     59G   15G   41G  27% /
tmpfs                       64M     0   64M   0% /dev
shm                         64M     0   64M   0% /dev/shm
/dev/mmcblk0p2              59G   15G   41G  27% /etc/hosts
192.168.7.200:/zdata/docs  6.2T  1.7T  4.5T  28% /home/admin/docs
tmpfs                      4.0G     0  4.0G   0% /proc/asound
tmpfs                      4.0G     0  4.0G   0% /sys/firmware

备注

需要保证NFS客户端和NFS服务端的用户 uid/gid 一致,这样才能正常读写共享文件。这里的用户是 admin ,其NFS客户端和服务器端都采用了相同的 uid/gid

异常排查

我重启了FreeBSD服务器和树莓派(Raspberry Pi OS, Debian 12),结果发现客户端再次无法挂载NFS:

无法挂载NFS服务器,提示协议不支持
mount.nfs: Protocol not supported

我了检查服务器已经启动了 nfsd ,并且ZFS的 sharenfs 显示如下 zfs get all /zdata/docs | grep -i nfs :

检查ZFS卷集的 sharenfs 设置
zfs get all zdata/docs | grep nfs

输出显示当前是非常简单的 on 状态:

显示ZFS卷集的 sharenfs 设置为 on 看起来没有问题
zdata/docs  sharenfs              -rw,-alldirs,-network=192.168.7.0/24  local
zdata/docs  acltype               nfsv4                                 default

尝试详细信息输出:

执行 mount -v 检查输出信息
mount -t nfs 192.168.7.200:/zdata/docs  /home/admin/docs -v

输出信息如下:

执行 mount -v 检查输出信息显示v4协议不支持,v3协议portmap查询失败
mount.nfs: timeout set for Sat Jun 28 19:00:37 2025
mount.nfs: trying text-based options 'vers=4.2,addr=192.168.7.200,clientaddr=192.168.7.221'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'vers=4,minorversion=1,addr=192.168.7.200,clientaddr=192.168.7.221'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'vers=4,addr=192.168.7.200,clientaddr=192.168.7.221'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'addr=192.168.7.200'
mount.nfs: prog 100003, trying vers=3, prot=6
mount.nfs: trying 192.168.7.200 prog 100003 vers 3 prot TCP port 2049
mount.nfs: prog 100005, trying vers=3, prot=17
mount.nfs: portmap query retrying: RPC: Program not registered
mount.nfs: prog 100005, trying vers=3, prot=6
mount.nfs: portmap query failed: RPC: Program not registered
mount.nfs: Protocol not supported
mount: (hint) your fstab has been modified, but systemd still uses
       the old version; use 'systemctl daemon-reload' to reload.

为何客户端尝试v4和v3协议都不成功?

  • 在Linux的NFS客户端执行 rpcinfo 命令检查远程服务的nfs输出协议情况:

检查NFS协议
rpcinfo 192.168.7.200 | egrep "service|nfs"

输出信息:

检查NFS协议
   program version netid     address                service    owner
    100003    2    udp       0.0.0.0.8.1            nfs        superuser
    100003    3    udp       0.0.0.0.8.1            nfs        superuser
    100003    2    udp6      ::.8.1                 nfs        superuser
    100003    3    udp6      ::.8.1                 nfs        superuser
    100003    2    tcp       0.0.0.0.8.1            nfs        superuser
    100003    3    tcp       0.0.0.0.8.1            nfs        superuser
    100003    2    tcp6      ::.8.1                 nfs        superuser
    100003    3    tcp6      ::.8.1                 nfs        superuser

可以看到服务器是支持 nfsv3nfsv2UDPTCP 协议的(为何没有 nfsv4 ?)

  • 在Linux的NFS客户端执行 showmount 检查:

在客户端检查 showmount
showmount -e 192.168.7.200

输出显示

在客户端检查 showmount 显示没有注册mountd服务
clnt_create: RPC: Program not registered

奇怪了

我检查了FreeBSD服务器的NFS启动配置 /etc/rc.conf :

配置 /etc/rc.conf 启用NFS
nfs_server_enable="YES"
mountd_enable="YES"
rpc_lockd_enable="YES"
rpc_statd_enable="YES"
rpcbind_enable="YES"

明明配置是激活 mountd

  • 在服务器上检查NFS相关服务,发现 mountd 服务果然没有启动:

检查NFS相关服务
root@xcloud:~ # service nfsd status
nfsd is running as pid 1747 1748.
root@xcloud:~ # service mountd status
mountd is not running.
root@xcloud:~ # service rpcbind status
rpcbind is running as pid 1663.

我检查了之前的执行步骤,发现之前在服务器上执行启动 nfsd 时候,有一个 service mountd reload 动作:

启动NFS服务
service nfsd start
service mountd reload

那么我再次执行 service mountd reload ,发现这个命令实际上要求 mountd 已经运行才行,现在我执行这条命令提示报错:

执行 service mountd reload 报错
mountd not running? (check /var/run/mountd.pid).

我尝试手工启动 mountd 服务:

手工启动 mountd
service mountd start

发现原来 mountd 必须读取 /etc/exports 配置文件才能启动,当前没有这个文件,则拒绝启动:

手工启动 mountd 报错
/etc/rc.d/mountd: WARNING: /etc/exports is not readable.
/etc/rc.d/mountd: WARNING: failed precmd routine for mountd

按照ZFS手册,似乎是不需要配置 /etc/exports 配置就可以,所以我先尝试 touch 一个空配置文件,果然可以启动 mountd 服务了:

创建 /etc/exports 空配置文件以后就能正常启动 mountd
root@xcloud:~ # touch /etc/exports
root@xcloud:~ # service mountd start
Starting mountd.

神奇了,现在再次在Linux NFS客户端执行 showmount :

在客户端检查 showmount
showmount -e 192.168.7.200

就能够看到FreeBSD正常输出了NFS:

在客户端检查 showmount 终于看到正常的NFS服务输出信息
Export list for 192.168.7.200:
/zdata/docs (everyone)
  • 现在再次手工 mount -v :

执行 mount -v 检查输出信息
mount -t nfs 192.168.7.200:/zdata/docs  /home/admin/docs -v

amazing 终于成功了,输出信息如下:

终于成功挂载NFS
root@acloud-w1:~# mount -t nfs 192.168.7.200:/zdata/docs  /home/admin/docs -v
mount.nfs: timeout set for Sat Jun 28 19:45:58 2025
mount.nfs: trying text-based options 'vers=4.2,addr=192.168.7.200,clientaddr=192.168.7.221'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'vers=4,minorversion=1,addr=192.168.7.200,clientaddr=192.168.7.221'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'vers=4,addr=192.168.7.200,clientaddr=192.168.7.221'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'addr=192.168.7.200'
mount.nfs: prog 100003, trying vers=3, prot=6
mount.nfs: trying 192.168.7.200 prog 100003 vers 3 prot TCP port 2049
mount.nfs: prog 100005, trying vers=3, prot=17
mount.nfs: trying 192.168.7.200 prog 100005 vers 3 prot UDP port 1016
mount: (hint) your fstab has been modified, but systemd still uses
       the old version; use 'systemctl daemon-reload' to reload.

root@acloud-w1:~# df -h
Filesystem                 Size  Used Avail Use% Mounted on
udev                       3.9G     0  3.9G   0% /dev
tmpfs                      807M  5.6M  801M   1% /run
/dev/mmcblk0p2              59G   15G   41G  27% /
tmpfs                      4.0G     0  4.0G   0% /dev/shm
tmpfs                      5.0M   48K  5.0M   1% /run/lock
/dev/mmcblk0p1             510M   67M  444M  14% /boot/firmware
tmpfs                      807M     0  807M   0% /run/user/501
overlay                     59G   15G   41G  27% /var/lib/docker/overlay2/646f8cdc15cb8dc4c47b5065439373167c61324b4c763c10f79166905cfe2b45/merged
192.168.7.200:/zdata/docs  6.2T  1.8T  4.4T  29% /home/admin/docs

备注

虽然 ZFS sharenfs 不需要配置 /etc/exports ,但是由于 mountd 启动时检查 /etc/exports 配置。如果该配置文件不存在(可以为空)就会拒绝启动 mountd 。由于NFS服务输出需要 mountd ,所以这个 mountd 不启动,客户端是看不到服务器输出的卷集的。

有点搞...

参考