NFS 介绍
NFS(Network File System)即网络文件系统,是 FreeBSD 支持的文件系统中的一种,它允许网络中的计算机之间通过 TCP/IP 网络共享资源。在 NFS 的应用中,本地 NFS 的客户端应用可以透明地读写位于远端 NFS 服务器上的文件,就像访问本地文件一样。
NFS 是基于 UDP/IP 或 TCP/IP 协议的应用,其实现主要是采用远程过程调用 RPC 机制,RPC 提供了一组与机器、操作系统以及低层传送协议无关的存取远程文件的操作。RPC 采用了 XDR 的支持。XDR 是一种与机器无关的数据描述编码的协议,他以独立与任意机器体系结构的格式对网上传送的数据进行编码和解码,支持在异构系统之间数据的传送。
RPC:全称 Remote Procedure Call protocol,译为远程过程调用协议,也称为函数(远程主机上的函数)调用,它的特点是一部分功能由本地程序完成,另一部分功能由远程主机上的函数完成。
好处
以下是 NFS 最显而易见的好处:
- 节省本地存储空间,将常用的数据存放在一台 NFS 服务器上且可以通过网络访问,那么本地终端将可以减少自身存储空间的使用;
- 用户不需要在网络中的每个机器上都建有 Home 目录,Home 目录可以放在 NFS 服务器上且可以在网络上被访问使用;
- 一些存储设备如软驱、CDROM 和 Zip(一种高储存密度的磁盘驱动器与磁盘)等都可以在网络上被别的机器使用。这可以减少整个网络上可移动介质设备的数量;
组成
NFS 体系至少有两个主要部分:
- 一台 NFS 服务器;
- 若干台客户机;
如下图所示:
客户机通过 TCP/IP 网络远程访问存放在 NFS 服务器上的数据。
在 NFS 服务器正式启用前,需要根据实际环境和需求,配置一些 NFS 参数。
应用
NFS 有很多实际应用。下面是比较常见的一些:
- 多个机器共享一台 CDROM 或者其他设备。这对于在多台机器中安装软件来说更加便宜跟方便;
- 在大型网络中,配置一台中心 NFS 服务器用来放置所有用户的 Home 目录可能会带来便利。这些目录能被输出到网络以便用户不管在哪台工作站上登录,总能得到相同的 Home 目录;
- 不同客户端可在 NFS 上观看影视文件,节省本地空间;
- 在客户端完成的工作数据,可以备份保存到 NFS 服务器上用户自己的路径下;
NFS 是运行在应用层的协议。随着 NFS 多年的发展和改进,NFS 既可以用于局域网也可以用于广域网,且与操作系统和硬件无关,可以在不同的计算机或系统上运行。
NFS 服务结构
NFS 的完整功能是由多个服务共同实现的,如下:
- idmapd(不占用端口):实现了用户账号的集中映射(都映射为 nobody 用户),但实际访问时能够以本地用户的身份去访问;
我们知道,当用户在本地文件系统创建一个文件时,文件的属主就为该用户,属组则为该用户的基本组。
而 NFS 文件系统是可以被挂载到一个目录使用的,此时用户在 NFS 文件系统下创建一个文件,文件的属主和属组是什么呢?
先看一个例子,如果 NFS 与本地文件系统规则相同时,一个客户端的 root 用户在 NFS 中创建一个文件,那么此时其它挂载了该 NFS 的客户端的 root 用户岂不是也能对这个文件做任意操作?显然此种方式是不安全的。
idmapd 服务就是为了解决这个问题:
- 当一个用户在 NFS 中创建文件时,文件的属主位会记录该用户的 UID ,属组则该用户基本组的 GID(即如果是 root 用户,则文件的属主 UID 为 0,属组 GID 为 0);
- 当使用 root 账户访问 NFS 时,idmapd 会将 root 用户的权限做挤压,让其映射为 nfs nobody 用户(最低权限的用户),然后 root 用户在 NFS 中就仅拥有文件的读权限(除非文件的其它用户权限位可写);
- 当使用普通账户访问 NFS 时,该普通账户的权限不做挤压,对文件的权限取决于对应文件的属主位 uid 和属组位 gid 与该普通账户的 uid 和组的 gid 是否相同,与用户名无关(例:两个终端都挂载了相同的 NFS,终端 1 中的 tom 用户的 UID 为 501,终端 2 中的 jerry 用户的 UID 也为 501,那么这两个用户在该 NFS 中针对文件的属主位就具有相同的权限)。
- mountd:挂载守护进程服务,提供用户认证功能(基于 IP 的认证);
- nfsd:仅提供 rpc 函数调用服务;
NFS 是基于 RPC 工作的,RPC 本身在 Linux 上工作也是需要监听某个端口上(RPC 是一个公共共享的功能,除了 NFS 还有一些其他功能借助 RPC 完成一些功能)。
客户端请求 NFS 必须要通过 mountd 服务,通过 mountd 服务的认证后,mountd 服务会返回一个令牌然后客户端会携带这个令牌再访问 nfsd 服务。
要找到 mountd 和 nfsd 服务,它们必须监听在某端口上。
nfsd 监听在 TCP 或 UDP 的 2049 端口上,而 mountd 是一个基于 RPC 的一个辅助性服务,所以 mountd 监听的端口是由 RPC 服务(RPC 本身也是一个服务)随机分配的(mountd 服务的端口分配模式也叫半随机分配),提供 RPC 服务的服务进程通常叫做 portmapper(提供 portmapper 服务支持的组件为 rpcbind),可以通过rpcinfo -p
查看,portmapper 固定监听在 111 端口上,因此客户端连接 nfsd 服务的过程如下:
- 客户端请求监听在 111 端口上的 portmapper 服务询问 mountd 服务的监听端口(mountd 服务启动时需要向 portmapper 注册自己使用的端口);
- 客户端拿到返回的 mountd 服务的端口再次发起对 mountd 服务的请求;
- 通过 mountd 服务的认证后 mountd 服务返回 token,客户端携带 token 访问 nfsd 服务;
所以要想启动 NFS 功能,必须保证 portmapper 处于正常工作状态。
rpcinfo
命令用来探测某一个主机上 portmapper 服务是否正常工作、监听在哪个端口上、有哪些服务在 portmapper 中注册。
使用
安装启动
NFS 服务的程序包名称为 nfs-utils
,查看本机是否已安装:
$ rpm -q nfs-utils
nfs-utils-1.2.3-39.el6.x86_64
如果没有安装执行使用 yum 安装即可:
$ yum install nfs-utils -y
启动 NFS 服务:
$ service nfs start
Starting NFS services: [ OK ]
Starting NFS quotas: [ OK ]
Starting NFS mountd: [ OK ]
Starting NFS daemon: [ OK ]
Starting RPC idmapd: [ OK ]
查看 rpc 服务占用的端口:
$ rpcinfo -p
program vers proto port service
100000 4 tcp 111 portmapper
100000 3 tcp 111 portmapper
100000 2 tcp 111 portmapper
100000 4 udp 111 portmapper
100000 3 udp 111 portmapper
100000 2 udp 111 portmapper
100024 1 udp 39777 status
100024 1 tcp 59276 status
100011 1 udp 875 rquotad
100011 2 udp 875 rquotad
100011 1 tcp 875 rquotad
100011 2 tcp 875 rquotad
100005 1 udp 43997 mountd
100005 1 tcp 48177 mountd
100005 2 udp 59859 mountd
100005 2 tcp 33497 mountd
100005 3 udp 40469 mountd
100005 3 tcp 43235 mountd
100003 2 tcp 2049 nfs
100003 3 tcp 2049 nfs
100003 4 tcp 2049 nfs
100227 2 tcp 2049 nfs_acl
100227 3 tcp 2049 nfs_acl
100003 2 udp 2049 nfs
100003 3 udp 2049 nfs
100003 4 udp 2049 nfs
100227 2 udp 2049 nfs_acl
100227 3 udp 2049 nfs_acl
100021 1 udp 58549 nlockmgr
100021 3 udp 58549 nlockmgr
100021 4 udp 58549 nlockmgr
100021 1 tcp 54426 nlockmgr
100021 3 tcp 54426 nlockmgr
100021 4 tcp 54426 nlockmgr
可以看到 portmapper、mountd、nfs 服务进程已经处于监听状态。
配置
NFS 服务的主配置文件为 /etc/exports
,该文件中的项的格式相当简单。要共享一个文件系统,只需要编辑 /etc/exports
并使用下面的格式给出这个文件系统(和选项)即可:
directory (or file system) client1(option1, option2) client2(option1, option2)
有几个常用的选项(option
)可以对 NFS 实现进行定制。这些选项包括:
secure
:使用 1024 以下的 TCP/IP 端口实现 NFS 的连接。指定insecure
可以禁用这个选项。rw
:这个选项允许 NFS 客户机进行读/写访问。缺省选项是只读(ro
)的。sync
:同步写入到磁盘。async
:这个选项可以改进性能,但是如果没有完全关闭 NFS 守护进程就重新启动了 NFS 服务器,这也可能会造成数据丢失。no_wdelay
:这个选项关闭写延时。如果设置了async
,那么 NFS 就会忽略这个选项。nohide
:如果将一个目录挂载到另外一个目录之上,那么原来的目录通常就被隐藏起来或看起来像空的一样。要禁用这种行为,需启用 hide 选项。no_subtree_check
:这个选项关闭子树检查,子树检查会执行一些不想忽略的安全性检查。缺省选项是启用子树检查。no_auth_nlm
:这个选项也可以作为insecure_locks
指定,它告诉 NFS 守护进程不要对加锁请求进行认证。如果关心安全性问题,就要避免使用这个选项。缺省选项是auth_nlm
或secure_locks
。mp (mountpoint=path)
:通过显式地声明这个选项,NFS 要求挂载所导出的目录。fsid=num
:这个选项通常都在 NFS 故障恢复的情况中使用。如果希望实现 NFS 的故障恢复,请参考 NFS 文档。
客户端(client
)可有如下几个常用取值:
- single host:主机地址,如
10.0.1.201
; - wildcards:域名,可使用通配符,如
*.zze.xyz
; - IP networks:IP 网络地址,如
10.0.1.0/24
;
用户映射
通过 NFS 中的用户映射,可以将伪或实际用户和组的标识赋给一个正在对 NFS 卷进行操作的用户。这个 NFS 用户具有映射所允许的用户和组的许可权限。对 NFS 卷使用一个通用的用户/组可以提供一定的安全性和灵活性,而不会带来很多管理负荷。
在使用 NFS 挂载的文件系统上的文件时,用户的访问通常都会受到限制,这就是说用户都是以匿名用户的身份来对文件进行访问的,这些用户缺省情况下对这些文件只有只读权限。这种行为对于 root 用户来说尤其重要。然而,实际上的确存在这种情况:希望用户以 root 用户或所定义的其他用户的身份访问远程文件系统上的文件。NFS 允许指定访问远程文件的用户——通过用户标识号(UID)和组标识号(GID),可以禁用正常的 squash 行为。
用户映射的选项包括:
root_squash
:指定 root 用户访问挂载的 NFS 卷时进行权限挤压;no_root_squash
:指定 root 用户访问挂载的 NFS 卷不进行权限挤压;all_squash
:它会限制所有的 UID 和 GID,只使用匿名用户,即挤压所有用户的权限;no_all_squash
:默认值,仅挤压 root 用户权限为 nobody,非 root 用户不挤压;anonuid
和anongid
:这两个选项将匿名 UID 和 GID 修改成特定用户和组帐号;
默认情况下用户被挤压后的权限为 NFS 主机中 nfsnobody 用户所拥有的权限,该用户在 NFS 安装完成后会自动创建。
导出 NFS 文件系统
以将 NFS 服务端(172.16.1.31)的 /data
目录共享给 172.16.1.0/24
网络中的主机使用为例,权限要求如下:
- 所有客户端主机都拥有读写权限;
- 同步写入硬盘,即在讲数据写入到 NFS 服务器中的硬盘后才会结束操作,最大限度保证数据不丢失;
- 将所有用户都映射为 NFS 服务端的匿名用户(nfsnobody)。
1、在服务端主机编辑 /etc/exports
文件内容如下:
$ cat /etc/exports
/data 172.16.1.0/24(rw,sync,all_squash)
2、在服务端主机创建用于 NFS 文件共享的目录,并设置其属主属组。
$ mkdir /data
$ chown -R nfsnobody.nfsnobody /data
在 NFS 服务共享成功的情况下,NFS 的共享目录状态信息会保存在
/var/lib/nfs/etab
中,如果该目录不存在共享信息,请检查/etc/exports
是否配置错误。
设置开机自启
前文中有提到,客户端在真正访问到 NFS 服务之前需要向 RPC 服务询问 mounted 服务地址信息,因此,在启动 NFS 之前,也要保证 RPC 服务正常支持。这里对 RPC 服务提供支持的服务名为 rpcbind,所以它和 NFS 服务需要同时加入开机自启。
# 加入开机自启
$ systemctl enable rpcbind nfs-server
# 重启
$ systemctl restart rpcbind nfs-server
# 检测 2049 和 111 端口是否正常监听
$ ss -tanl | egrep '111|2049'
LISTEN 0 64 *:2049 *:*
LISTEN 0 128 *:111 *:*
LISTEN 0 64 :::2049 :::*
LISTEN 0 128 :::111 :::*
# 检查 NFS 目录是否正常导出
$ cat /var/lib/nfs/etab
/data 172.16.1.0/24(rw,sync,wdelay,hide,nocrossmnt,secure,root_squash,all_squash,no_subtree_check,secure_locks,acl,no_pnfs,anonuid=65534,anongid=65534,sec=sys,rw,secure,root_squash,all_squash)
挂载/卸载 NFS 文件系统
1、在客户端主机(172.16.1.41)安装客户端组件:
$ yum install -y nfs-utils rpcbind
2、在客户端主机使用 showmount -e <服务端地址>
查看 服务端主机(172.16.1.31)NFS 服务器端共享的文件系统:
$ showmount -e 172.16.1.31
Export list for 172.16.1.31:
/data 172.16.1.0/24
3、将服务端主机导出的 /data
目录挂载到客户端主机的 /nfsdir
目录,在客户端主机中执行以下操作:
$ mkdir /nfsdir
$ mount -t nfs 172.16.1.31:/data /nfsdir
这里需要使用
mount
命令的-t
选项指定挂载的文件系统类型为nfs
。
Mac OS 中挂载略有不同:
$ sudo mount -o resvport 172.16.1.31:/data nfsdir
客户端挂载远程 NFS 文件系统前要保证本地已开启 rpc.statd 和 rpcbind 服务:
- 查看 rpc.statd 服务是否开启:
ps -aux | grep rpc.statd
;- 查看 rpcbind 服务是否开启:
service rpcbind status
;
4、客户端主机中查看挂载信息:
$ df -h | tail -1
172.16.1.31:/data 20G 1.7G 18G 9% /nfsdir
$ mount | tail -1
172.16.1.31:/data on /nfsdir type nfs4 (rw,relatime,vers=4.1,rsize=131072,wsize=131072,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.16.1.41,local_lock=none,addr=172.16.1.31)
5、卸载操作和卸载普通文件系统一样,直接在客户端主机执行 umount <挂载点>
即可:
$ umount /nfsdir
测试创建文件
1、在客户端主机的 /nfsdir
目录创建 1.txt
文件:
$ cd /share && touch 1.txt
2、在服务端主机的 /data
目录下查看是否存在 1.txt
文件:
$ ls
1.txt
由于此时客户端主机的操作相当于服务端主机的 nfsnobody 用户在服务端本地做操作,服务端中
nfsnobody
用户对/data
目录也有读写权限,所以可以在其中创建、删除文件。
不重启服务导出新的 NFS 文件系统
在 10.0.1.201 主机修改 /etc/exports
文件内容如下:
$ cat /etc/exports
/share 10.0.1.0/24(rw)
/sharedata 10.0.1.0/24(rw)
可以看到上面新添加了一个共享的目录,要让它生效不需要重启 NFS 服务,只需要使用 exportfs
命令重新导出即可
$ export -ar
exportfs
的常用选项如下:
-ar
:表示重新导出所有 NFS 文件系统;-au
:关闭导出的所有文件系统;
查看导出的文件系统:
$ showmount -e 10.0.1.201
Export list for 10.0.1.201:
/sharedata 10.0.1.0/24
/share 10.0.1.0/24
开机自动挂载 NFS
要实现 NFS 文件系统的开机自动挂载依旧是修改 /etc/fstab
文件,以开机自动挂载 172.16.1.31:/data
到本机 /nfsdir
为例,在 /etc/fstab
中添加如下行:
10.0.1.201:/data /nfsdir nfs defaults,_netdev 0 0
客户端挂载时可以使用的特殊选项:
rsize
:从服务器读取缓冲区大小,单位为字节,默认值为 1024;wsize
:写入到服务器缓冲区大小,单位为字节。默认值为 1024;timeo
:timeo 值是在 RPC 超时后到重新发送传输等待的时间(以十分之一秒为单位)。第一次超时后,每次重试的超时值都会加倍,最长不超过 60 秒,或者直到发生严重超时为止。如果连接到速度较慢的服务器或繁忙的网络,则可以增加此超时值;_netdev
:标识挂载的设备是一个网络设备,/etc/fstab
文件会在开机过程中被读取从而挂载其中定义的文件系统,如果开机过程中 NFS 出现故障连接不上,此选项会告知内核这是一个网络设备从而跳过该设备继续执行开机过程;
rsize
和wsize
如果使用比较高的值,如 8192,可以提高传输速度。
提高安全性
在实际工作场景中,通常情况 NFS 服务器共享的只是普通静态数据(图片、附件、视频),不需要执行 suid、exec 等权限,所以我们最好能控制挂载的这个文件系统只能作为数据存取之用,无法执行程序,从而提高了安全性。
如: 很多木马篡改站点文件都是由上传入口上传的程序到存储目录,然后执行的。
看如下客户端挂载示例:
$ mount -t nfs -o nosuid,noexec,nodev 172.16.1.31:/data /nfsdir
这里通过 mount
命令的 -o
选项指定额外的挂载参数:
nosuid
:对该目录中的文件关闭 suid 权限位;noexec
:禁止该目录中所有文件的执行权限;nodev
:不允许读取文件系统上的字符或块设备;
提高性能
有时也需要考虑性能问题,看如下挂载示例:
$ mount ‐t nfs ‐o async,noatime,nodiratime 172.16.1.31:/data /nfsdir
其中挂载参数含义如下:
async
:异步写入,可能会丢失数据,对数据安全性要求不高可以开启;noatime
:不更新文件的访问时间戳;nodiratime
:不更新目录的访问时间戳;
手动指定匿名用户
1、NFS 服务端配置:
$ cat /etc/exports
/data 172.16.1.0/24(rw,sync,all_squash,anonuid=666,anongid=666)
2、服务端创建对应的用户作为匿名用户:
$ groupadd -g 666 www
$ useradd -u 666 -g 666 www
$ id www
uid=666(www) gid=666(www) groups=666(www)
3、重启 NFS 服务:
$ systemctl restart nfs-server
4、服务端修改共享目录的属主属组为对应匿名用户和其基本组:
$ chown www:www /data
$ ll /data -d
drwxr-xr-x 2 www www 19 Feb 25 16:26 /data
5、客户端重新挂载:
$ umount /nfsdir
$ mount -t nfs 172.16.1.31:/data /nfsdir/
6、客户端测试创建文件并查看:
$ cd /nfsdir && touch 1.txt
$ ll
total 0
-rw-r--r-- 1 666 666 0 Feb 25 17:20 1.txt
7、可以看到创建的文件的属主与属组直接显示的是 ID 号,这是因为客户端并没有一个 uid 和 gid 都为 666 的用户,这样虽然不影响操作,但还是建议创建一个和服务端匿名用户 uid 和 gid 对应的用户,客户端执行如下操作:
$ groupadd -g 666 www
$ useradd -g 666 -u 666 www
8、重新查看 NFS 目录会发现属主属组正常显示了:
$ ll
total 0
-rw-r--r-- 1 www www 0 Feb 25 17:20 1.txt
小结
1、NFS 存储优点。
- NFS 文件系统简单易用、方便部署、数据可靠、服务稳定、满足中小企业需求;
- NFS 文件系统内存放的数据都在文件系统之上,所有数据都是能看得见;
2、NFS 存储局限
- 存在单点故障, 构建高可用维护麻烦;
- NFS 并不对数据做任何校验;
- 客户端挂载 NFS 服务没有密码验证, 安全性一般(内网使用);
评论区