第六章:网络文件系统

FreeBSD支持传统的网络文件系统(Network File System——NFS)。

NFS运行你与其他主机共享一台计算机上的挂载点和目录。关于NFS可以将一整本书。

本书仅侧重于建立和管理NFS服务。

NFS使用CS模型。服务器为其他计算机提供文件系统。这称为NFS导出(exporting),提供的文件系统成为导出(exports)。

NFS客户端可以挂载导出,就像挂载任何其他文件系统一样。

NFS不是作为安全协议设计的。不要在没有数据包过滤器或防火墙的情况下将NFS服务器放在互联网上。

仅在NFS级别限制访问是完全不够的——必须防止随机的互联网主机窥探主机的远程过程调用(Remote Procedure Call——RPC)服务。如果你使用FreeBSD内置的包过滤器之一,请确保通过IP地址和端口号限制NFS访问。

此外,NFS没有加密。任何具有数据包嗅探器访问权限的人都可以在客户端访问文件时看到它们。加密NFS需要Kerberos,这本身可能又是一整本书。

使用NFS在类Unix系统之间或与具有NFS功能的非Unix客户端共享文件。

NFS 版本

NFS有三个版本:NFSv2、NFSv3、NFSv4。

对于系统管理员来说,版本2和3非常相似。大多数主机可以协商使用这两个版本中的任意一个。

NFSv2是一个相当小的实现,可以追溯到人们很高兴物理性能如何都能实现文件共享的年代。

NFSv3比NFSv2有许多增量改进,并拥有大大提高的性能。然而,这些改进大多不需要特殊的设置。可以选择调整设置,但不必专门启用NFSv3功能。

NFSv4是一个完全不同的野兽,打破了NFS的许多长期规则。

当人们说NFS时,几乎肯定只指v2或v3。或者v2和v3被称为传统NFS。而提到v4时通常会说NFSv4。

虽然几乎每个类Unix操作系统都支持各种版本的NFS,但每个操作系统的实现方式略有不同。

如果你的网络上有多个操作系统,若其中一些主机需要调整才能和其他主机很好地互操作,不要感到奇怪。

NFSv2/NFSv3协议

对于系统管理员来说,NFS版本2和版本3非常相似。FreeBSD对两个版本的NFS使用相同的服务器软件。

传统的NFS是无状态的(stateless)。NFS服务器不会跟踪用户端的连接方式或访问内容。重新启动NFS服务器不会使客户端崩溃。客户端将无法访问不可用的NFS服务器上的文件,但一旦服务器返回,它将从停止的地方继续。其他网络文件共享协议并不总是这么有弹性。

然而,无状态(statelessness)也会造成自己的问题。例如,客户端无法知道它们当前打开的文件何时被另一个客户端修改。当服务器消失时,网络引导的无盘机可能会挂起。

我们先配置服务器,然后再讨论客户端。

启用NFS服务器

在/etc/rc.conf中添加以下条目启用NFS服务:

以上并非所有条目都是必须的,但启用它们可以提供最广泛的NFS兼容性和良好的性能。

这些服务协同工作提供NFS导出。初学者可以通过重启服务器来确保一切按正确的顺序启动,从而获得最佳结果。熟悉NFS的人可以启动nfsd、lockd和statd服务来激活NFS。

sockstat命令可以显示rpc.lockd、rpc.statd、mountd和rpcbind正在监听网络。

如果你没有看到所有这些程序都在运行,应检查/var/log/messages以了解原因。

rpcbind是NFS的关键程序。如果rpcbind无法启动,请使用-d尝试在调试模式下运行它。它不会与终端分离,而是打印状态信息。像任何其他程序一样,CTRL-C可以终止它。

NFS服务如何工作

NFS导出需要内核中的支持。虽然NFS服务器和客户端都被编译成FreeBSD的默认内核,但如果您的自定义内核缺乏这种支持,启用NFS服务器会加载内核模块。

客户端通过联系服务器的rpcbind服务来启动连接。此守护进程将RPC请求映射到本地网络地址和端口。客户端联系rpcbind请求NFS挂载守护进程mountd的IP和端口。

mountd守护进程接受并响应来自客户端的请求,这就是大多数人所认为的“NFS服务器”。它监听一个随机的高编号端口。客户端找到正在运行的mountd守护进程的唯一方法是访问rpcbind服务器。

虽然rpc.lockd和rpc.stats不是严格必须的,但它们使使用NFS变得更好。

rpc.lockd进程可确保NFS上的文件锁定操作顺利进行,以便客户端可以获得文件的独占锁(exclusive lock)。

rpc.statd进程监视客户端连接。当NFS客户端消失时,rpc.statd会释放专用于该客户端的资源。这些服务对原本无状态的协议施加了少量的状态。

NFS 服务器选项

可以更改NFS服务器连接到网络的方式、它支持的NFS版本以及运行的服务器数量。

如果需要,还可以回退到旧的NFS服务器。在rc.conf中使用nfs_server_flags选项将所有这些设置为nfsd的选项。

NFS可以通过TCP或UDP工作。

UDP是传统的NFS传输协议。

TCP在有损网络上工作得更好,可以更好地应对不同的网络速度。

然而,TCP是一种有状态的协议,中断的TCP连接不会透明地恢复。

使用-u标志启用UDP,使用-t启用TCP。FreeBSD默认使用TCP,但两者都启用。如果添加自己的挂载标志,则必须指定希望挂载使用的协议。

默认情况下,NFS服务器监听计算机上的所有IP地址。这会导致基于UDP的NFS出现问题。

UDP是无状态的,因此对NFS请求的回复可以来自服务器上的任何IP地址,这可能会混淆客户端。当NFS服务器有多个IP地址时,使用-h选项和所需的IP将NFS服务器配置为仅接受单个IP上的连接。

处理来自多个客户端的NFS流量的服务器可能需要额外的nfsd实例来支持这些客户端。-n标志指定了FreeBSD应该启动多少个nfsd副本。

以下rc.conf条目告诉FreeBSD同时支持UDP和TCP连接,绑定到IP地址203.0.113.99,并运行nfsd的六个副本:

FreeBSD9.0及以上版本具有全新的NFS内核支持,旨在使FreeBSD能够支持NFSv4。

新服务器可能有错误(尽管它经过了广泛的测试)。通过使用-o标志,可以告诉nfsd使用旧的NFS服务器。确定问题是特定于FreeBSD NFS服务器还是其他地方的一种方法是用旧的NFS服务器重现问题。

如果发现旧NFS服务器中不存在NFS问题,请务必提交错误报告。

现在,你的服务器已经准备好导出文件系统或目录了。

导出设置

你可以精确配置服务器可以向哪些客户端导出什么。

理想的NFS配置允许尽可能少的访问,同时让服务器履行其职责,客户端访问必要的文件。

虽然客户端可能需要/home下的文件,但他们可能不需要远程挂载NFS服务器的根文件系统。

FreeBSD允许以两种不同的方式配置导出。

最好不要同时使用这两种方式,除非你的系统上混用了UFS和ZFS。

导出条目

虽然以下讨论侧重于“导出文件”,但几乎所有内容都适用于ZFS管理的导出。

每一个导出条目都包三个部分:

客户端和磁盘设备的每种组合在导出文件中只能有一行。

如果你的NFS服务器只有一个巨大的分区,就像默认的UFS安装一样,并且你想将/home和/usr/ports都导出到客户端,它们必须出现在同一行。这两个导出将具有相同的选项和相同的权限。

NFS挂载不会跨越分区边界。如果主机对/usr和/usr/src有单独的UFS分区,则导出/usr不会自动导出/usr/src分区。

在导出条目的三个组成部分中,只有目录是必须的。导出行不能包含符号链接或句点。在目录中运行pwd以获取该目录的真实位置。

如果我想将我的主目录导出到整个互联网,可以使用以下/etc/exports条目:

就算有防火墙或数据包过滤器来保护你免受随机互联网探测,这依然是个糟糕的做法。

修改/etc/exports文件后需要重新启动或SIGHUO mountd服务:

如果mountd发现任何问题,它会在/var/log/messages中创建一个日志条目。mountd通常不会提供更多细节,所以这些日志消息没啥用。

经验之谈,在/etc/exports中最常见的错误是使用带有符号链接的路径。

NFS 和用户

传统的NFS通过UID号来传达文件所有权和权限。文件不属于用户mwl,而是属于用户1001。如果这些UID号没有映射到相同的用户名,则会遇到问题。

大型的NFS部署通常有一些跨所有系统同步用户名和UID的方法,比如LDAP甚或rdist。

在不受信任的用户在自己的计算机上拥有管理权限,并且可以使用任何UID创建账户的网络上,大多数站点都实现了Kerberos或其他身份验证控制方式。

在较小的网络上,同步所有机器上的密码文件通常就足够了。

root用户的处理方式略有不同。NFS服务器不能信任其他计算机上的root以服务器上的root身份执行命令。入侵NFS客户端的入侵者不应该自动获得NFS服务器的root权限。

NFS将客户端机器上的root请求映射到服务器上UID和GID为-2的用户。传统系统有16位用户ID,因此这变成了65534,即nobody账户的用户ID。

这就是为什么最初创建了高度无特权的nobody用户。

尽管如此,对于许多服务器程序来说,nobody账户似乎很有用,因此许多程序都想独占nobody。

多个安全实体同时使用nobody会造成安全访问问题。虽然现在大多数程序希望在自己的无特权用户下运行,但系统上的一些随机软件可能会以nobody身份运行。即使你审计了所有机器上以nobody运行的软件,下周、下月或明年依然有可能会有新装的软件包希望使用nobody账户。

实际上,nobody已经受到了污染,尽量不要让它出现在网络上。

现代类Unix操作系统使用32位UID,因此客户端的root帐户映射到服务器上的UID 4294967294。FreeBSD在/etc/passwd中没有这样的帐户。我建议创建一个无特权用户nfsroot,明确供NFS为root使用。如果可能,请为该用户分配UID 4294967294。

使用-maproot选项显式地将root分配给nfsroot映射。以下示例中,我将用户映射到这个分配的用户:

如果你真的希望客户端的root账户在NFS服务器上具有root权限,可以使用-maproot=0。无盘机器需要这个,这样它们就可以将根文件系统放在服务器上。

你可以在导出文件中位nfsroot用户分配组成员资格。此组成员资格可以不同于/etc/group中分配的成员资格,允许你使用共享特定的组权限。按名称或GUID指定组。使用冒号将组与用户名分隔开。以下,nfsroot用户也是www和staff的成员:

如果要删除特定共享的此用户的所有组成员身份,请在NFS映射的根用户后加一个冒号,不要添加任何组:

你可以使用-mapall选项将所有NFS用户重新映射到当账户,而不仅仅是重新映射根用户。这为所有用户提供了相同的访问权限:

-mapall分配组权限的方式与-maproot的一样。

不能任意将用户账户互相重新映射。在复杂的环境中,请确保在网络上的所有计算机上同步用户账户和UID。

要记住的一件事是,NFS中的用户最多只能属于16个组。一些较旧的操作系统可能会限制到更少的组。一些操作系统可以打破这一限制,但这样会破坏NFS协议。如果用户无法通过基于组的访问控制访问文件,请检查它们所在的组数。

导出多个目录

你可能希望在一个分区上导出多个目录。在/etc/exports的一行中列出导出到相同客户端的所有目录,用空格分隔。以下示例导出了系统根分区上的四个目录:

客户端可以挂载这些目录中的任何一个,并且来自root的请求会被映射到用户nfsroot。

也许你希望客户端能够挂载分区上的任何目录。使用-alldir选项允许此操作。以下示例中,用户可以挂载/home分区中的任何目录:

-alldirs选项仅在目录是分区挂载点时有效。我无法指定一个不是挂载点的目录,也无法允许挂载它下面的任何目录。

长行

不要在行的各部分之间使用任何标识符、分隔符、逗号或其他分隔符。但可以用tab。

在上面最后一个例子中,每个目录单独一样会更易读,但由于它们都在同一个分区上,所以不能这样做。

FreeBSD NFS人员可以以更结构化的方式重新设计/etc/exports,但FreeBSD的exports文件将与任何其他类Unix操作系统不兼容。在不同机器之间共享导出文件,需要考虑各机器可能使用不同的操作系统。

但是,你可以使用反斜杠来分割行。一旦exports的行过长,必须要分割它:

无论如何安排导出文件,阅读都不是好玩的事情。选你乐于接受的那种吧。

限制客户端

默认情况下,世界上任何地方的任何主机都可以访问导出的NFS共享。

要限制访问,请在/etc/exports的条目尾部列出允许的主机。以下示例对主目录的访问限制为单个IP地址:

使用–network和–mask限定符将NFS访问权限制到特定网络上的客户端:

熟悉斜线网络掩码表示法的人可以这样设置:

上面两个例子效果相同,根据个人喜好选择。

每个条目只能有一个网络声明,所以可使用多行导出到多个网络:

IPv6地址的工作原理完全相同:

你还可以通过主机名或NIS网络组指定主机。在行末按短名称或长名称列出每个主机,以空格隔开。下面示例中给三个主机访问共享的权限:

使用主机名会创建对名称解析的依赖关系。如果不增加NFS故障,失去域名服务(DNS)就足够令人不快了。

此外,这些名称不是动态的。当你重新启动mountd时,NFS服务器会查找每个主机的IP地址。更改客户端的IP意味着重新加载名称服务和重新启动mountd。

在导出文件中使用单个IP地址而不是主机名意味着,当你更改客户端的IP时,你必须在/etc/exports中更改其条目并重新启动mountd。

无论哪种方式,管理每台主机的NFS分配都需要更多的工作。在不损害安全性的情况下,尽可能广泛地分配NFS权限。

无效组合

一个分区(或单个分区上的目录)和一个主机(或一组主机)的组合在/etc/exports中只能有一行。只有一个文件系统的主机不能使用这些导出语句。

记住,FreeBSD的安装程序在使用UFS时默认创建一个大型文件系统。

我们正试图用不同的选项将同一分区上的两个目录导出到同一主机。这是无效的。

你可以使用相同的选项将这两个目录导出到同一台主机,也可以将两个目录移动到单独的分区。

同样,你无法将这些导出到具有不同选项的不同主机,如下实例:

如果/usr/src和/home在不同的分区上,上面的配置是可以正常工作的。

仅导出挂载点可防止尝试创建子目录和选项的无效组合。NFS服务器应该始终有多个分区,而不是FreeBSD默认UFS安装的单个大分区。如果你使用ZFS,请使用ZFS属性管理NFS。

其他服务器选项

使用-ro选项可以授予客户端对NFS共享的只读访问权限:

有时,你配置的NFS导出并不总是有效的。例如,你可以导出/media以共享你的DVD驱动器。如果重新启动mountd时安装了光盘,则一切正常。但是,如果驱动器里面没有任何介质,mountd会记录投诉。你知道问题是什么,在导出行中添加-quiet来消除常见警告:

通过删除前导连字符并用逗号分隔,你可以在一行中组合多个NFS服务器选项。不要使用空格。以下示例,我们共享设置为只读,设置maproot用户,并消除常见警告:

由于选项之间不能有空格,请使用等号(=)指定maproot选项的用户。

使用ZFS属性管理NFS

使用zfs管理NFS具有明显的优势。你可以在每个数据集的基础上配置NFS,并且不需要在每次更改后重新启动mountd。命令行配置更容易自动化,许多人也发现它更容易。

使用sharenfs属性启用、禁用和配置NFS导出。将此属性设置为on,以全局共享数据集及其后代。这相当于在/etc/exports中单独列出数据集。任何人都可以挂载它或它的任何子数据集,没有限制,也没有选择。

同样,将sharenfs属性设置为off将取消数据集的共享。

但是,你可能希望在此导出上使用一些NFS选项。将sharenfs设置为此共享所需的选项。在这里,我们创建一个maproot用户,并将客户端限制在单个网络中:

使用ZFS管理NFS导出的问题是,你必须对所有允许的主机使用相同的选项。也就是说,如果你的大多数客户端使用-maproot=nfsroot选项,但你有一个问题主机需要-maproot=root,则zfs无法提供帮助。必须通过/etc/exports配置此类导出。同样,你只能定义一个具有ZFS属性的允许网络。

强烈建议你选择一种管理NFS的方法并坚持下去。对于简单的配置可以使用ZFS属性,而对于复杂的NFS设置可能需要/etc/exports。同时使用/etc/exports和sharenfs会造成混淆。

NFS 客户端

使用NFS客户端比配置服务器简单得多。FreeBSD在/etc/rc.conf中有nfs_clinet_enable选项,但当你尝试通过网络挂载共享时,FreeBSD会自动启动所有nfs客户端功能。

使用mount命令将NFS导出附加到你的客户端,就像你对任何本地文件系统所做的那样。使用NFS服务器的主机名或IP地址以及要挂载的目录,而不是使用设备名称。

以下我们将服务器agouti导出的目录/home/mwl挂载到本地目录/mnt上:

此挂载使用服务器强制执行的任何选项。如果服务器已只读方式导出目录,则挂载是只读的。

下面介绍如何添加自己的挂载选项。

可用挂载

NFS客户端的一个明显问题是“我可以从该服务器挂载什么?”

showmount命令使用-e选项和NFS服务器的名称,可以列出客户端可用的所有导出:

showmount命令不显示任何服务器端选项,如-ro或-maproot。这些细节并不容易为客户所用,尽管只读导出很容易通过touch命令来检测。

NFS 挂载选项

FreeBSD使用保守的NFS默认值,因此它可以与任何其他类Unix操作系统互操作。如果你的环境只使用较新的系统,请尝试各种挂载选项,看看它们是否能提高系统性能。使用-o指定挂载选项,如下所示。

现代FreeBSD默认使用TCP。TCP具有各种自动缩放功能,并会自我调整以提供合理的吞吐量。然而,并非所有NFS主机都支持TCP。要为无法通过TCP进行NFS的客户端提供服务,并访问不处理TCP的NFS服务器上的共享,你需要使用UDP挂载。使用选项udp启用UDP挂载。

NFS服务器比硬盘驱动器更容易从网络中消失。使用NFS共享的程序可能会挂起,知道NFS服务器返回,这可能永远不会发生。使用interruptible(可中断)挂载,以使在文件系统消失时停止程序。你可以使用CTRL-C中断挂在不可用但可中断的NFS挂载上的进程。使用intr选项使挂载可中断。

通过使用soft挂载,FreeBSD将通知程序它们正在处理的文件不再可用。程序如何处理这些信息取决于程序,但它们将不再永远挂起。使用soft选项启用软挂载。

你还可以使用rw和ro选项进行读写和只读挂载。

把以上所有东西放在一起,你可以使用软的、可中断的TCP挂载,像这样:

也可以在/etc/fstab中添加一行:

虽然简单的NFS非常简单,但您可以花很多时间对其进行调优。

NFS性能

NFS性能是个晦涩难懂的话题,最好逐案处理。服务器和客户端的不同组合可能最适合不同的性能设置。敢于尝试,看看哪些设置和配置能提高吞吐量。

大多数NFS调优发生在客户端,服务器响应客户端的调优请求。

要调整性能,请查看rsize、wsize、wcommissionize、readahead和readdirsize的挂载选项。虽然你可以将这些选项设置为任何值,但出于互操作性或协议的原因,FreeBSD会将其中大部分设置为最大。

rsize和wsize选项决定了读写请求的大小。readdirsize值使目录读取请求的大小。对于UDP挂载,NFSv2的最大值为8K,而NFSv3允许16K。NFSv4不使用UDP。

NFSv3不能为TCP和UDP分别指定最大大小rsize、wsize和readdirsize。然而,许多服务器无法处理大于16K的请求,因此FreeBSD将这些值限制为16K。

服务器使用readahead(预读)来猜测客户端接下来会请求什么,并在客户端请求之前从磁盘加载它。如果你通常使用NFS访问大文件,请使用readahead。

要查看挂载使用的实际值,请使用nfsstat -m。然后测量性能、实验、测试,再测试。

NFSv4

现在你已经了解了如何使用NFSv2和v3,接下来看看v4是如何将所有部分抛在空中并可以不同的方式组合在一起的。

NFSv4为NFS添加了三个核心功能:访问控制列表(access control lists——ACLs)、委派(delegations)、引用(referrals)。它还添加了状态并更改了识别用户的方式。

与旧版的NFS不同,v4是有状态的。客户端将一大堆RPC调用捆绑在一个请求中,并将它们作为一个单元发送到服务器,以提高性能。服务器按顺序处理请求。如果其中一个请求失败,服务器将取消队列中的所有请求,并将结果发送给客户端。v4始终在TCP上运行。

最后,v4添加了nfsuserd守护进程来识别用户。

nfsuserd(8)

旧版本的NFS使用账户的UID号来标识用户。v4通过用户的UID以及用户名和NFSv4域名的组合来识别用户。

NFSv4域名是一个文件字符串,通常对应于主机的域名。FreeBSD独立获取主机的域名,并将其用作NFSv4域名。但是,如果主机没有域名,或你的NFS主机有不同的域名,则需要手动改设置NFSv4域。

不同的域名甚至会影响小型网络。我的家庭网络使用一个域进行生产,使用一个单独的域进行测试。即使在我家,我也需要选择一个NFSv4域名并告诉我的主机。

客户端和服务器都使用nfsuserd守护进程用户名信息。使用-domain命令行参数告诉NFSv4要使用哪个域。你的域名不应该经常更改,所以在/etc/rc.conf中设置。在这里,我既启用了nfsuserd,又告诉它使用mwl.io域名:

其他配置取决于主机是NFSv4服务器还是客户端。

NFSv4 服务器

配置NFSv4服务器需要启用所需的服务并配置要导出的文件系统。

启用NFSv4 服务器

NFSv4服务器需要以下rc.conf选项设置:

NFSv4监听端口2049,只要端口2049打开,就可以在NFSv4客户端和服务器之间放置数据包过滤器。不过,仍然需要nfsd中的通用NFS支持函数以及mountd。

主机可以同时服务和挂载所有三个版本的NFS。

NFSv4 导出

与旧版的NFS不同,NFSv4导单个文件系统及其下的所有目录。使用NFSv4无法导出多个断开连接的文件系统。每个NFSv4服务器都有一个导出根目录。客户端可以挂载该挂载点下的任何目录。如果要导出服务器目录树的断开连接的部分,可使用空挂载将它们附加到NFS层次结构。

在/etc/exports中以V4:开头的行上配置NFS导出。然后给出root目录和任何NFS选项。NFSv4接受旧版本NFS的网络限制选项以及kerberos安全选项,但其他选项(如-ro或-alldirs)无效。下面示例通过NFSv4导出/home到网上:

虽然你只得到一个根目录,但你可以有多个v4导出行来支持多个网络,只要它们都有相同的根目录。

如果导出ZFS数据集,则其所有子数据集都会自动导出。你必须明确使用sharenfs属性来取消导出子数据集。

NFSv4客户端不使用旧版本NFS所需的挂载协议,但服务器仍需要运行mountd来提供一些支持服务。要使用/etc/exports更改生效,请使用service mountd reload或pkill -1 mountd,与旧版NFS完全相同。

NFSv4 客户端

在NFS客户端上启用NFSv4只需要运行nfsuserd。所有其他依赖项都会自动启用。在/etc/rc.conf中启用nfsuserd:

NFSv4使用gethostid中的主机唯一标识符。FreeBSD在启动时会自动生成此ID,除非有人故意禁用它。如果客户端的rc.conf包含hostid_enable=NO,则无法在不损坏文件的情况下使用NFSv4。

现在,你可以通过NFSv4挂载NFS共享。FreeBSD默认尝试在v3中挂载NFS共享,如果失败,则回退到v2。自动化甚至不考虑NFSv4,因此你必须将NFS版本指定为挂载选项。

可以在不运行nfsuserd的情况下使用NFSv4,但你将获得不正确的文件权限。如果你正在运行nfsuserd,但仍然获得了不正确的权限,请检查NFSv4域名。

虽然FreeBSD的NFSv4服务器不支持委托,但客户端支持。NFSv4客户端可以在本地编辑文件,从而减少带宽并提高性能。但是,如果客户端要求访问委托给另一个客户端的文件,NFS服务器必须能够戳入委托的客户端并检索当前文件。这被称为回调(callback)。要在FreeBSD NFSv4客户端上启用委派支持,请启用NFS回调守护进程nfscbd。

虽然客户端可以在不使用nfscbd的情况下挂载NFSv4导出,但客户端不会获得任何委托。

虽然你可以调整NFS以满足几乎任何情况,但它并不是唯一的网络存储协议。接下来讨论CIFS。