22.4.zfs 管理

zfs 实用程序可以在池中创建、销毁和管理所有现有的 ZFS 数据集。要管理池本身,请使用 zpool。

创建和销毁数据集

与传统磁盘和卷管理器不同,ZFS 中的空间并非预分配。在传统文件系统中,在分区和分配空间之后,没有办法添加新的文件系统而无需添加新的磁盘。使用 ZFS,可以随时创建新的文件系统。每个数据集都有诸如压缩、重复消除、缓存和配额等功能的属性,以及其他有用属性,如只读、区分大小写、网络文件共享和挂载点。可以在彼此之间嵌套数据集,并且子数据集将继承其祖先的属性。委派、复制、快照,能让将每个数据集作为一个单元进行管理和销毁。为每种不同类型或一组文件创建单独的数据集具有优势。拥有大量数据集的缺点是一些命令(例如 zfs list )将变慢,并且挂载数百甚至数千个数据集将减慢 FreeBSD 启动过程。

创建一个新数据集并在其中启用 LZ4 压缩:

# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
mypool                781M  93.2G   144K  none
mypool/ROOT           777M  93.2G   144K  none
mypool/ROOT/default   777M  93.2G   777M  /
mypool/tmp            176K  93.2G   176K  /tmp
mypool/usr            616K  93.2G   144K  /usr
mypool/usr/home       184K  93.2G   184K  /usr/home
mypool/usr/ports      144K  93.2G   144K  /usr/ports
mypool/usr/src        144K  93.2G   144K  /usr/src
mypool/var           1.20M  93.2G   608K  /var
mypool/var/crash      148K  93.2G   148K  /var/crash
mypool/var/log        178K  93.2G   178K  /var/log
mypool/var/mail       144K  93.2G   144K  /var/mail
mypool/var/tmp        152K  93.2G   152K  /var/tmp
# zfs create -o compress=lz4 mypool/usr/mydataset
# zfs list
NAME                   USED  AVAIL  REFER  MOUNTPOINT
mypool                 781M  93.2G   144K  none
mypool/ROOT            777M  93.2G   144K  none
mypool/ROOT/default    777M  93.2G   777M  /
mypool/tmp             176K  93.2G   176K  /tmp
mypool/usr             704K  93.2G   144K  /usr
mypool/usr/home        184K  93.2G   184K  /usr/home
mypool/usr/mydataset  87.5K  93.2G  87.5K  /usr/mydataset
mypool/usr/ports       144K  93.2G   144K  /usr/ports
mypool/usr/src         144K  93.2G   144K  /usr/src
mypool/var            1.20M  93.2G   610K  /var
mypool/var/crash       148K  93.2G   148K  /var/crash
mypool/var/log         178K  93.2G   178K  /var/log
mypool/var/mail        144K  93.2G   144K  /var/mail
mypool/var/tmp         152K  93.2G   152K  /var/tmp

销毁数据集比删除数据集上的文件要快得多,因为它不涉及扫描文件并更新相应的元数据。

销毁已创建的数据集:

# zfs list
NAME                   USED  AVAIL  REFER  MOUNTPOINT
mypool                 880M  93.1G   144K  none
mypool/ROOT            777M  93.1G   144K  none
mypool/ROOT/default    777M  93.1G   777M  /
mypool/tmp             176K  93.1G   176K  /tmp
mypool/usr             101M  93.1G   144K  /usr
mypool/usr/home        184K  93.1G   184K  /usr/home
mypool/usr/mydataset   100M  93.1G   100M  /usr/mydataset
mypool/usr/ports       144K  93.1G   144K  /usr/ports
mypool/usr/src         144K  93.1G   144K  /usr/src
mypool/var            1.20M  93.1G   610K  /var
mypool/var/crash       148K  93.1G   148K  /var/crash
mypool/var/log         178K  93.1G   178K  /var/log
mypool/var/mail        144K  93.1G   144K  /var/mail
mypool/var/tmp         152K  93.1G   152K  /var/tmp
# zfs destroy mypool/usr/mydataset
# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
mypool                781M  93.2G   144K  none
mypool/ROOT           777M  93.2G   144K  none
mypool/ROOT/default   777M  93.2G   777M  /
mypool/tmp            176K  93.2G   176K  /tmp
mypool/usr            616K  93.2G   144K  /usr
mypool/usr/home       184K  93.2G   184K  /usr/home
mypool/usr/ports      144K  93.2G   144K  /usr/ports
mypool/usr/src        144K  93.2G   144K  /usr/src
mypool/var           1.21M  93.2G   612K  /var
mypool/var/crash      148K  93.2G   148K  /var/crash
mypool/var/log        178K  93.2G   178K  /var/log
mypool/var/mail       144K  93.2G   144K  /var/mail
mypool/var/tmp        152K  93.2G   152K  /var/tmp

在现代版本的 ZFS 中,zfs destroy 是异步的,空闲空间可能需要几分钟才会出现在池中。使用 zpool get freeing poolname 来查看 freeing 属性,显示后台正在释放块的数据集。如果存在子数据集,如快照或其他数据集,则销毁父数据集是不可能的。要销毁数据集及其子数据集,请使用 -r 递归销毁数据集及其子数据集。使用 -n -v 列出此操作销毁的数据集和快照,而不实际销毁任何内容。销毁快照释放的空间也会显示。

22.4.2. 创建和销毁卷

卷是一种特殊的数据集类型。与挂载为文件系统不同,将其公开为块设备位于 /dev/zvol/poolname/dataset 下。这能让将卷用于其他文件系统,用于支持虚拟机的磁盘,或通过诸如 iSCSI 或 HAST 的协议使其可用于其他网络主机。

使用任何文件系统或不使用文件系统格式化卷以存储原始数据。对用户而言,卷看起来像是常规磁盘。在这些 zvol 上放置普通文件系统提供了普通磁盘或文件系统所没有的功能。例如,在 250 MB 卷上使用压缩属性可以创建一个压缩的 FAT 文件系统。

# zfs create -V 250m -o compression=on tank/fat32
# zfs list tank
NAME USED AVAIL REFER MOUNTPOINT
tank 258M  670M   31K /tank
# newfs_msdos -F32 /dev/zvol/tank/fat32
# mount -t msdosfs /dev/zvol/tank/fat32 /mnt
# df -h /mnt | grep fat32
Filesystem           Size Used Avail Capacity Mounted on
/dev/zvol/tank/fat32 249M  24k  249M     0%   /mnt
# mount | grep fat32
/dev/zvol/tank/fat32 on /mnt (msdosfs, local)

销毁卷与销毁常规文件系统数据集几乎相同。该操作几乎是瞬间完成的,但在后台重新获取空闲空间可能需要几分钟。

22.4.3. 重命名数据集

要更改数据集的名称,请使用 zfs rename。要更改数据集的父级,请同时使用此命令。将数据集重命名为具有不同父数据集将更改从父数据集继承的这些属性的值。将数据集重命名为在新位置(从新父数据集继承)会卸载然后重新装入它。要防止这种行为,请使用 -u。

重命名数据集并将其移动到不同父数据集下:

# zfs list
NAME                   USED  AVAIL  REFER  MOUNTPOINT
mypool                 780M  93.2G   144K  none
mypool/ROOT            777M  93.2G   144K  none
mypool/ROOT/default    777M  93.2G   777M  /
mypool/tmp             176K  93.2G   176K  /tmp
mypool/usr             704K  93.2G   144K  /usr
mypool/usr/home        184K  93.2G   184K  /usr/home
mypool/usr/mydataset  87.5K  93.2G  87.5K  /usr/mydataset
mypool/usr/ports       144K  93.2G   144K  /usr/ports
mypool/usr/src         144K  93.2G   144K  /usr/src
mypool/var            1.21M  93.2G   614K  /var
mypool/var/crash       148K  93.2G   148K  /var/crash
mypool/var/log         178K  93.2G   178K  /var/log
mypool/var/mail        144K  93.2G   144K  /var/mail
mypool/var/tmp         152K  93.2G   152K  /var/tmp
# zfs rename mypool/usr/mydataset mypool/var/newname
# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
mypool                780M  93.2G   144K  none
mypool/ROOT           777M  93.2G   144K  none
mypool/ROOT/default   777M  93.2G   777M  /
mypool/tmp            176K  93.2G   176K  /tmp
mypool/usr            616K  93.2G   144K  /usr
mypool/usr/home       184K  93.2G   184K  /usr/home
mypool/usr/ports      144K  93.2G   144K  /usr/ports
mypool/usr/src        144K  93.2G   144K  /usr/src
mypool/var           1.29M  93.2G   614K  /var
mypool/var/crash      148K  93.2G   148K  /var/crash
mypool/var/log        178K  93.2G   178K  /var/log
mypool/var/mail       144K  93.2G   144K  /var/mail
mypool/var/newname   87.5K  93.2G  87.5K  /var/newname
mypool/var/tmp        152K  93.2G   152K  /var/tmp

重命名快照使用相同命令。由于快照的特性,重命名无法更改其父数据集。要重命名递归快照,请指定 -r ;这也将重命名所有具有相同名称的子数据集中的快照。

# zfs list -t snapshot
NAME                                USED  AVAIL  REFER  MOUNTPOINT
mypool/var/newname@first_snapshot      0      -  87.5K  -
# zfs rename mypool/var/newname@first_snapshot new_snapshot_name
# zfs list -t snapshot
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/newname@new_snapshot_name      0      -  87.5K  -

22.4.4. 设置数据集属性

每个 ZFS 数据集都有控制其行为的属性。大多数属性会自动从父数据集继承,但可以在本地覆盖。使用 zfs set property=value dataset 在数据集上设置属性。大多数属性具有一组有限的有效值,zfs get 会显示每个可能的属性和有效值。使用 zfs inherit 可以将大多数属性恢复为其继承的值。还可以使用用户定义的属性。它们成为数据集配置的一部分,并提供有关数据集或其内容的更多信息。为了将这些自定义属性与作为 ZFS 的一部分提供的属性区分开,可以使用冒号( : )为属性创建自定义命名空间。

# zfs set custom:costcenter=1234 tank
# zfs get custom:costcenter tank
NAME PROPERTY           VALUE SOURCE
tank custom:costcenter  1234  local

要删除自定义属性,请使用 zfs inherit 和 -r。如果自定义属性未在任何父数据集中定义,则此选项会删除它(但池的历史记录仍会记录更改)。

# zfs inherit -r custom:costcenter tank
# zfs get custom:costcenter tank
NAME    PROPERTY           VALUE              SOURCE
tank    custom:costcenter  -                  -
# zfs get all tank | grep custom:costcenter
#

22.4.4.1. 获取和设置共享属性

两个常用且有用的数据集属性是 NFS 和 SMB 共享选项。设置这些选项将定义 ZFS 如何在网络上共享数据集。目前,FreeBSD 仅支持设置 NFS 共享。要获取共享的当前状态,请输入:

# zfs get sharenfs mypool/usr/home
NAME             PROPERTY  VALUE    SOURCE
mypool/usr/home  sharenfs  on       local
# zfs get sharesmb mypool/usr/home
NAME             PROPERTY  VALUE    SOURCE
mypool/usr/home  sharesmb  off      local

要启用数据集共享,请输入:

#  zfs set sharenfs=on mypool/usr/home

通过 NFS 设置共享数据集的其他选项,如 -alldirs,-maproot 和 -network。要在通过 NFS 共享的数据集上设置选项,请输入:

#  zfs set sharenfs="-alldirs,-maproot=root,-network=192.168.1.0/24" mypool/usr/home

22.4.5. 管理快照

快照是 ZFS 最强大的功能之一。快照提供数据集的只读、时间点的副本。使用写时复制(COW),ZFS 通过在磁盘上保留旧版本的数据来快速创建快照。如果不存在快照,ZFS 在数据重写或删除时回收空间以供将来使用。快照通过记录当前数据集与先前版本之间的差异来保留磁盘空间。快照可以应用于整个数据集,而不是单个文件或目录。数据集的快照包含其中的所有内容,包括文件系统属性、文件、目录、权限等。创建快照时不占用额外空间,但随着引用的块发生变化,会占用空间。使用 -r 创建递归快照,该快照在数据集及其子数据集上以相同的名称创建快照,提供文件系统的一致的时间点快照。当应用程序在相关数据集上有文件或相互依赖时,这一点非常重要。没有快照时,备份会有来自不同时间点的文件副本。

ZFS 中的快照提供了各种功能,即使其他具有快照功能的文件系统也缺少。快照的典型用途是在执行风险操作(如软件安装或系统升级)时,作为快速备份文件系统当前状态的方法。如果操作失败,可以通过回滚到快照将系统恢复到创建快照时的状态。如果升级成功,请删除快照以释放空间。没有快照,失败的升级通常需要恢复备份,这是繁琐、耗时的,并且可能需要停机期间系统无法使用。即使系统在正常运行中,回滚到快照也是快速的,几乎没有或没有停机时间。考虑到从备份复制数据所需的时间,在多 TB 存储系统中,节省的时间是巨大的。快照不能替代池的完整备份,但提供了一种在特定时间存储数据集副本的快速简便方法。

22.4.5.1. 创建快照

要创建快照,请使用 zfs snapshot dataset@snapshotname。添加 -r 可以递归创建快照,并在所有子数据集上使用相同的名称。

创建整个池的递归快照:

# zfs list -t all
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool                                 780M  93.2G   144K  none
mypool/ROOT                            777M  93.2G   144K  none
mypool/ROOT/default                    777M  93.2G   777M  /
mypool/tmp                             176K  93.2G   176K  /tmp
mypool/usr                             616K  93.2G   144K  /usr
mypool/usr/home                        184K  93.2G   184K  /usr/home
mypool/usr/ports                       144K  93.2G   144K  /usr/ports
mypool/usr/src                         144K  93.2G   144K  /usr/src
mypool/var                            1.29M  93.2G   616K  /var
mypool/var/crash                       148K  93.2G   148K  /var/crash
mypool/var/log                         178K  93.2G   178K  /var/log
mypool/var/mail                        144K  93.2G   144K  /var/mail
mypool/var/newname                    87.5K  93.2G  87.5K  /var/newname
mypool/var/newname@new_snapshot_name      0      -  87.5K  -
mypool/var/tmp                         152K  93.2G   152K  /var/tmp
# zfs snapshot -r mypool@my_recursive_snapshot
# zfs list -t snapshot
NAME                                        USED  AVAIL  REFER  MOUNTPOINT
mypool@my_recursive_snapshot                   0      -   144K  -
mypool/ROOT@my_recursive_snapshot              0      -   144K  -
mypool/ROOT/default@my_recursive_snapshot      0      -   777M  -
mypool/tmp@my_recursive_snapshot               0      -   176K  -
mypool/usr@my_recursive_snapshot               0      -   144K  -
mypool/usr/home@my_recursive_snapshot          0      -   184K  -
mypool/usr/ports@my_recursive_snapshot         0      -   144K  -
mypool/usr/src@my_recursive_snapshot           0      -   144K  -
mypool/var@my_recursive_snapshot               0      -   616K  -
mypool/var/crash@my_recursive_snapshot         0      -   148K  -
mypool/var/log@my_recursive_snapshot           0      -   178K  -
mypool/var/mail@my_recursive_snapshot          0      -   144K  -
mypool/var/newname@new_snapshot_name           0      -  87.5K  -
mypool/var/newname@my_recursive_snapshot       0      -  87.5K  -
mypool/var/tmp@my_recursive_snapshot           0      -   152K  -

快照不能通过正常 zfs list 操作显示。要列出快照,请在 zfs list 后附加 -t snapshot。-t all 显示文件系统和快照。

快照不会直接挂载,因此在 MOUNTPOINT 列中不显示路径。ZFS 不在 AVAIL 列中提及可用磁盘空间,因为快照在创建后是只读的。将快照与原始数据集进行比较:

# zfs list -rt all mypool/usr/home
NAME                                    USED  AVAIL  REFER  MOUNTPOINT
mypool/usr/home                         184K  93.2G   184K  /usr/home
mypool/usr/home@my_recursive_snapshot      0      -   184K  -

显示数据集和快照一起,揭示了快照如何以 COW 方式工作。它们保存所做的更改(增量),而不是再次保存完整的文件系统内容。这意味着在进行更改时,快照占用的空间很小。通过将文件复制到数据集,然后创建第二个快照,可以更加观察空间使用情况:

# cp /etc/passwd /var/tmp
# zfs snapshot mypool/var/tmp@after_cp
# zfs list -rt all mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp                         206K  93.2G   118K  /var/tmp
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp                   0      -   118K  -

在复制操作后,第二个快照包含对数据集的更改。这带来了巨大的空间节省。请注意,快照的大小 mypool/var/tmp@my_recursive_snapshot 在 USED 列中也发生了变化,以显示其与随后拍摄的快照之间的变化。

22.4.5.2. 比较快照

ZFS 提供了一个内置命令,用于比较两个快照之间内容的差异。当用户想要查看文件系统随时间变化的方式很多快照时,这非常有帮助。例如,zfs diff 能让用户找到仍包含意外删除文件的最新快照。对上一节创建的两个快照执行此操作产生以下输出:

# zfs list -rt all mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp                         206K  93.2G   118K  /var/tmp
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp                   0      -   118K  -
# zfs diff mypool/var/tmp@my_recursive_snapshot
M       /var/tmp/
+       /var/tmp/passwd

该命令列出了指定快照(在本例中为 mypool/var/tmp@my_recursive_snapshot )和实时文件系统之间的更改。第一列显示更改类型:

+添加路径或文件。

-

删除路径或文件。

M

修改路径或文件。

R

重命名路径或文件。

与表格输出进行比较,很明显 ZFS 在创建快照 mypool/var/tmp@my_recursive_snapshot 后添加了 passwd。这也导致了挂载在 /var/tmp 上的父目录的修改。

当使用 ZFS 复制功能将数据集传输到不同主机进行备份时,比较两个快照是有帮助的。

通过提供两个数据集的完整名称和快照名称来比较两个快照:

# cp /var/tmp/passwd /var/tmp/passwd.copy
# zfs snapshot mypool/var/tmp@diff_snapshot
# zfs diff mypool/var/tmp@my_recursive_snapshot mypool/var/tmp@diff_snapshot
M       /var/tmp/
+       /var/tmp/passwd
+       /var/tmp/passwd.copy
# zfs diff mypool/var/tmp@my_recursive_snapshot mypool/var/tmp@after_cp
M       /var/tmp/
+       /var/tmp/passwd

备份管理员可以比较从发送主机接收到的两个快照,并确定数据集中的实际变化。有关更多信息,请参阅复制部分。

22.4.5.3. 快照回滚

只要有至少一个快照可用,随时可以回滚到它。通常情况下,当数据集的当前状态不再有效或者更喜欢旧版本时,会发生这种情况。诸如本地开发测试出错、系统更新失败影响系统功能、或者需要恢复已删除的文件或目录等场景都很常见。要回滚到一个快照,使用 zfs rollback snapshotname。如果存在大量变更,此操作将需要很长时间。在此期间,数据集始终保持一致状态,就像遵循 ACID 原则的数据库正在执行回滚操作一样。这一切都是在数据集处于在线和可访问状态时进行的,而无需停机。快照回滚完成后,数据集的状态将与最初拍摄快照时的状态相同。回滚到快照会丢弃数据集中不属于快照的所有其他数据。在回滚到先前的快照时,建议先对当前数据集状态进行快照,以便稍后可能需要的数据。这样,用户可以在快照之间来回切换,而不会丢失仍然有价值的数据。

在第一个示例中,回滚快照,因为一个粗心的 rm 操作移除了比预期更多的数据。

# zfs list -rt all mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp                         262K  93.2G   120K  /var/tmp
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp               53.5K      -   118K  -
mypool/var/tmp@diff_snapshot              0      -   120K  -
# ls /var/tmp
passwd          passwd.copy     vi.recover
# rm /var/tmp/passwd*
# ls /var/tmp
vi.recover

此时,用户注意到额外文件被删除并想要把它们找回来。ZFS 提供了一种简单的方式来通过定期对重要数据进行快照来找回它们。要找回文件并从最后一个快照重新开始,请发出以下命令:

# zfs rollback mypool/var/tmp@diff_snapshot
# ls /var/tmp
passwd          passwd.copy     vi.recover

回滚操作将数据集恢复到最后一个快照的状态。还可以回滚到之前拍摄的快照,然后再拍摄其他快照。当尝试这样做时,ZFS 将发出此警告:

# zfs list -rt snapshot mypool/var/tmp
AME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp               53.5K      -   118K  -
mypool/var/tmp@diff_snapshot              0      -   120K  -
# zfs rollback mypool/var/tmp@my_recursive_snapshot
cannot rollback to 'mypool/var/tmp@my_recursive_snapshot': more recent snapshots exist
use '-r' to force deletion of the following snapshots:
mypool/var/tmp@after_cp
mypool/var/tmp@diff_snapshot

此警告意味着在数据集的当前状态与用户想要回滚到的快照之间存在快照。要完成回滚操作,请删除这些快照。ZFS 无法跟踪数据集不同状态之间的所有更改,因为快照是只读的。除非用户指定 -r 来确认这是期望的操作,否则 ZFS 不会删除受影响的快照。如果这是意图,并理解丢失所有中间快照的后果,请发出以下命令:

# zfs rollback -r mypool/var/tmp@my_recursive_snapshot
# zfs list -rt snapshot mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp@my_recursive_snapshot     8K      -   152K  -
# ls /var/tmp
vi.recover

zfs list -t snapshot 的输出证实中间快照的删除是由于 zfs rollback -r 的结果。

22.4.5.4. 从快照中恢复单个文件

快照位于父数据集下的隐藏目录中:.zfs/snapshots/snapshotname。默认情况下,即使执行标准 ls -a,这些目录也不会显示出来。虽然目录不显示,但可以像普通目录一样访问它。名为 snapdir 的属性控制这些隐藏目录是否出现在目录列表中。将属性设置为 visible 可以使它们出现在 ls 等处理目录内容的命令的输出中。

# zfs get snapdir mypool/var/tmp
NAME            PROPERTY  VALUE    SOURCE
mypool/var/tmp  snapdir   hidden   default
# ls -a /var/tmp
.               ..              passwd          vi.recover
# zfs set snapdir=visible mypool/var/tmp
# ls -a /var/tmp
.               ..              .zfs            passwd          vi.recover

通过将单个文件从快照复制回父数据集来恢复到以前的状态。.zfs/snapshot 下的目录结构中有一个目录,类似于以前拍摄的快照的目录,以便更容易识别它们。下一个例子展示了如何从包含文件最新版本的快照中复制文件来从隐藏的.zfs 目录中恢复文件:

# rm /var/tmp/passwd
# ls -a /var/tmp
.               ..              .zfs            vi.recover
# ls /var/tmp/.zfs/snapshot
after_cp                my_recursive_snapshot
# ls /var/tmp/.zfs/snapshot/after_cp
passwd          vi.recover
# cp /var/tmp/.zfs/snapshot/after_cp/passwd /var/tmp

即使 snapdir 属性设置为隐藏,运行 ls .zfs/snapshot 仍会列出该目录的内容。管理员决定是否显示这些目录。这是每个数据集的设置。从这个隐藏的.zfs/snapshot 目录中复制文件或目录很简单。尝试反过来会导致这个错误出现:

# cp /etc/rc.conf /var/tmp/.zfs/snapshot/after_cp/
cp: /var/tmp/.zfs/snapshot/after_cp/rc.conf: Read-only file system

错误提醒用户快照是只读的,在创建后不能更改。将文件复制到快照目录中以及从中删除文件均不能让,因为这会更改它们所代表的数据集的状态。

快照消耗空间取决于自从快照时父文件系统发生了多少变化。快照的 written 属性跟踪快照使用的空间。

要销毁快照并回收空间,请使用 zfs destroy dataset@snapshot。添加 -r 会递归地删除父数据集下具有相同名称的所有快照。将 -n -v 添加到命令中会显示要删除的快照列表以及估算将要回收的空间,而不执行实际的销毁操作。

22.4.6. 管理克隆

克隆是快照的副本,更像是常规数据集。与快照不同,克隆是可写和可挂载的,并且具有自己的属性。使用 zfs clone 创建克隆后,将无法销毁原始快照。要颠倒克隆和快照之间的子/父关系,请使用 zfs promote。提升克隆将使快照成为克隆的子代,而不是原始父数据集的子代。这将改变 ZFS 对空间的计算方式,但实际上不会改变所消耗的空间量。可以将克隆挂载到 ZFS 文件系统层次结构内的任何位置,而不仅限于快照的原始位置下方。

要显示克隆功能,请使用此示例数据集:

# zfs list -rt all camino/home/joe
NAME                    USED  AVAIL  REFER  MOUNTPOINT
camino/home/joe         108K   1.3G    87K  /usr/home/joe
camino/home/joe@plans    21K      -  85.5K  -
camino/home/joe@backup    0K      -    87K  -

克隆的典型用途是在保留快照的同时对特定数据集进行实验,以防发生问题时可以回退到快照。由于快照无法更改,因此创建快照的读/写克隆。在克隆中实现所需结果后,将克隆提升为数据集并删除旧文件系统。严格来说,不必删除父数据集,因为克隆和数据集可以共存而不会出现问题。

# zfs clone camino/home/joe@backup camino/home/joenew
# ls /usr/home/joe*
/usr/home/joe:
backup.txz     plans.txt

/usr/home/joenew:
backup.txz     plans.txt
# df -h /usr/home
Filesystem          Size    Used   Avail Capacity  Mounted on
usr/home/joe        1.3G     31k    1.3G     0%    /usr/home/joe
usr/home/joenew     1.3G     31k    1.3G     0%    /usr/home/joenew

创建克隆会使其成为在拍摄快照时数据集所处状态的精确副本。现在可以独立于其来源数据集更改克隆。两者之间的连接是快照。ZFS 在属性 origin 中记录此连接。使用 zfs promote 提升克隆会使克隆成为独立数据集。这将删除 origin 属性的值并断开新独立数据集与快照的连接。这个例子展示了这一点:

# zfs get origin camino/home/joenew
NAME                  PROPERTY  VALUE                     SOURCE
camino/home/joenew    origin    camino/home/joe@backup    -
# zfs promote camino/home/joenew
# zfs get origin camino/home/joenew
NAME                  PROPERTY  VALUE   SOURCE
camino/home/joenew    origin    -       -

在进行一些更改,例如将 loader.conf 复制到提升的克隆中后,旧目录在这种情况下变得过时。相反,提升的克隆可以取代它。为此,首先 zfs destroy 旧数据集,然后 zfs rename 克隆到旧数据集名称(或完全不同的名称)。

# cp /boot/defaults/loader.conf /usr/home/joenew
# zfs destroy -f camino/home/joe
# zfs rename camino/home/joenew camino/home/joe
# ls /usr/home/joe
backup.txz     loader.conf     plans.txt
# df -h /usr/home
Filesystem          Size    Used   Avail Capacity  Mounted on
usr/home/joe        1.3G    128k    1.3G     0%    /usr/home/joe

克隆的快照现在是一个普通数据集。它包含原始快照中的所有数据以及添加到其中的文件,如 loader.conf。克隆为 ZFS 用户提供了在不同场景下使用的有用功能。例如,提供jail作为包含不同安装应用程序集的快照。用户可以克隆这些快照并添加他们自己的应用程序,使其符合需求。如果对修改满意,将克隆提升为完整数据集,并提供给最终用户使用,就像他们使用真实数据集一样。这在提供这些jail时节省时间和管理开销。

22.4.7. 复制

将数据保留在一个位置的单个池中会使其面临盗窃、自然或人为灾害等风险。定期备份整个池是至关重要的。ZFS 提供了一个内置的序列化功能,可以将数据的流表示发送到标准输出。使用此功能,将这些数据存储在连接到本地系统的另一个池上是可能的,也可以将其发送到另一个系统上的网络。快照是这种复制的基础(请参阅有关 ZFS 快照的部分)。用于复制数据的命令是 zfs send 和 zfs receive。

这些例子展示了这两个池子的 ZFS 复制:

# zpool list
NAME    SIZE  ALLOC   FREE   CKPOINT  EXPANDSZ   FRAG   CAP  DEDUP  HEALTH  ALTROOT
backup  960M    77K   896M         -         -     0%    0%  1.00x  ONLINE  -
mypool  984M  43.7M   940M         -         -     0%    4%  1.00x  ONLINE  -

名为 mypool 的池子是主要的池子,正常情况下会在其上写入和读取数据。在主要池子不可用时,使用第二个备用池。请注意,这种故障切换不是由 ZFS 自动完成的,而是需要系统管理员在必要时手动执行。使用快照来提供一致的文件系统版本进行复制。在创建 mypool 的快照之后,通过复制快照将其复制到备用池。这不包括自最近快照以来所做的更改。

# zfs snapshot mypool@backup1
# zfs list -t snapshot
NAME                    USED  AVAIL  REFER  MOUNTPOINT
mypool@backup1             0      -  43.6M  -

现在存在一个快照,使用 zfs send 创建代表快照内容的流。将此流存储为文件,或者接收到另一个池中。将流写入标准输出,但将其重定向到文件、管道,否则将出现错误:

# zfs send mypool@backup1
Error: Stream can not be written to a terminal.
You must redirect standard output.

使用 zfs send 备份数据集,重定向到位于挂载的备份池中的文件。确保池具有足够的可用空间来容纳发送快照的大小,这意味着快照中包含的数据,而不是前一个快照的更改。

# zfs send mypool@backup1 > /backup/backup1
# zpool list
NAME    SIZE  ALLOC   FREE   CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
backup  960M  63.7M   896M         -         -     0%     6%  1.00x  ONLINE  -
mypool  984M  43.7M   940M         -         -     0%     4%  1.00x  ONLINE  -

zfs send 将名为 backup1 的快照中的所有数据传输到名为 backup 的池中。要自动创建并发送这些快照,请使用 cron(8) 作业。

ZFS 可以将备份存储为实时文件系统,而不是存储为存档文件,从而能让直接访问备份数据。要访问这些流中实际包含的数据,请使用 zfs receive 将流转换回文件和目录。下面的示例结合了 zfs send 和 zfs receive,使用管道将数据从一个池复制到另一个池。在传输完成后,直接在接收池上使用数据。只能将数据集复制到空数据集。

# zfs snapshot mypool@replica1
# zfs send -v mypool@replica1 | zfs receive backup/mypool
send from @ to mypool@replica1 estimated size is 50.1M
total estimated size is 50.1M
TIME        SENT   SNAPSHOT

# zpool list
NAME    SIZE  ALLOC   FREE   CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
backup  960M  63.7M   896M         -         -     0%     6%  1.00x  ONLINE  -
mypool  984M  43.7M   940M         -         -     0%     4%  1.00x  ONLINE  -

22.4.7.1. 增量备份

zfs send 也可以确定两个快照之间的差异,并发送两者之间的单独差异。这样可以节省磁盘空间和传输时间。例如:

# zfs snapshot mypool@replica2
# zfs list -t snapshot
NAME                    USED  AVAIL  REFER  MOUNTPOINT
mypool@replica1         5.72M      -  43.6M  -
mypool@replica2             0      -  44.1M  -
# zpool list
NAME    SIZE  ALLOC   FREE   CKPOINT  EXPANDSZ   FRAG   CAP  DEDUP  HEALTH  ALTROOT
backup  960M  61.7M   898M         -         -     0%    6%  1.00x  ONLINE  -
mypool  960M  50.2M   910M         -         -     0%    5%  1.00x  ONLINE  -

创建第二个名为 replica2 的快照。这第二个快照包含自上次快照 replica1 以来对文件系统所做的更改。使用 zfs send -i 并指示快照对生成一个增量复制流,其中包含已更改的数据。如果接收端已存在初始快照,则此操作成功。

# zfs send -v -i mypool@replica1 mypool@replica2 | zfs receive /backup/mypool
send from @replica1 to mypool@replica2 estimated size is 5.02M
total estimated size is 5.02M
TIME        SENT   SNAPSHOT

# zpool list
NAME    SIZE  ALLOC   FREE   CKPOINT  EXPANDSZ   FRAG  CAP  DEDUP  HEALTH  ALTROOT
backup  960M  80.8M   879M         -         -     0%   8%  1.00x  ONLINE  -
mypool  960M  50.2M   910M         -         -     0%   5%  1.00x  ONLINE  -

# zfs list
NAME                         USED  AVAIL  REFER  MOUNTPOINT
backup                      55.4M   240G   152K  /backup
backup/mypool               55.3M   240G  55.2M  /backup/mypool
mypool                      55.6M  11.6G  55.0M  /mypool

# zfs list -t snapshot
NAME                                         USED  AVAIL  REFER  MOUNTPOINT
backup/mypool@replica1                       104K      -  50.2M  -
backup/mypool@replica2                          0      -  55.2M  -
mypool@replica1                             29.9K      -  50.0M  -
mypool@replica2                                 0      -  55.0M  -

增量流复制了更改的数据,而不是整个 replica1。仅发送差异需要的时间更少,通过不每次复制整个池节省了磁盘空间。在通过缓慢网络或按传输字节计费时,这非常有用。

一个新的文件系统,备份/mypool,可用于存储来自池 mypool 的文件和数据。指定 -p 会复制数据集属性,包括压缩设置、配额和挂载点。指定 -R 会复制数据集的所有子数据集以及它们的属性。自动发送和接收以在第二个池上创建定期备份。

22.4.7.2. 通过 SSH 发送加密备份

通过网络发送流是保持远程备份的好方法,但它确实有一个缺点。通过网络链路发送的数据未加密,能让任何人拦截并将流转换回数据,而发送用户并不知情。当将流通过互联网发送到远程主机时,这是不可取的。请使用 SSH 安全加密通过网络连接发送的数据。由于 ZFS 需要将流重定向到标准输出,因此通过 SSH 进行管道传输很容易。为了在传输过程中和在远程系统上保持文件系统内容加密,请考虑使用 PEFS。

首先更改一些设置并采取安全预防措施。这说明了 zfs send 操作所需的必要步骤;有关 SSH 的更多信息,请参阅 OpenSSH。

更改配置如下:

  • 使用 SSH 密钥在发送和接收主机之间实现无密码 SSH 访问

  • ZFS 需要 root 用户的权限来发送和接收流。这需要作为 root 登录到接收系统。

  • 出于安全原因,默认情况下阻止 root 登录。

  • 使用 ZFS 委派系统能让每个系统上的非 root 用户执行相应的发送和接收操作。在发送系统上:

    # zfs allow -u someuser send,snapshot mypool
  • 要挂载池,非特权用户必须拥有该目录,并且常规用户需要权限来挂载文件系统。

在接收系统上:

# sysctl vfs.usermount=1
vfs.usermount: 0 -> 1
# echo vfs.usermount=1 >> /etc/sysctl.conf
# zfs create recvpool/backup
# zfs allow -u someuser create,mount,receive recvpool/backup
# chown someuser /recvpool/backup

非特权用户现在可以接收和挂载数据集,并将主数据集复制到远程系统。

% zfs snapshot -r mypool/home@monday
% zfs send -R mypool/home@monday | ssh someuser@backuphost zfs recv -dvu recvpool/backup

在名为 mypool 的池上为文件系统数据集 home 创建名为 monday 的递归快照。然后 zfs send -R 包括数据集、所有子数据集、快照、克隆和设置在流中。通过 SSH 将输出通过管道传输到等待的 zfs receive 主机 backuphost。使用 IP 地址或完全合格的域名是一个好的做法。接收机将数据写入 recvpool 池上的备份数据集。将 -d 添加到 zfs recv 上会用接收端的池名称覆盖快照的名称。-u 会导致文件系统在接收端不挂载。使用 -v 显示有关传输的更多细节,包括经过的时间和传输的数据量。

22.4.8. 数据集、用户和组配额

使用数据集配额来限制特定数据集消耗的空间量。参考配额的工作方式与此类似,但计算数据集本身使用的空间,不包括快照和子数据集。同样地,使用用户和组配额来防止用户或组在池或数据集中使用完所有空间。

以下示例假定用户已存在于系统中。在将用户添加到系统之前,请确保先创建他们的主目录数据集并设置 mountpoint 到 /home/bob。然后,创建用户并将主目录指向数据集的 mountpoint 位置。这将正确设置所有者和组权限,而不会覆盖可能存在的任何预先存在的主目录路径。

对存储/home/bob 强制执行 10GB 的数据集配额:

# zfs set quota=10G storage/home/bob

实施关于 storage/home/bob 存储引用配额为 10 GB 的政策:

# zfs set refquota=10G storage/home/bob

移除 storage/home/bob 的 10 GB 配额:

# zfs set quota=none storage/home/bob

通用格式为 userquota@user=size,且用户名必须符合以下任一格式之一:

  • POSIX 兼容名称,如 joe。

  • POSIX 数字 ID,如 789。

  • SID 名称,如 joe.bloggs@example.com。

  • SID 数字 ID,例如 S-1-123-456-789。

例如,要为名为 joe 的用户强制执行 50 GB 的用户配额:

# zfs set userquota@joe=50G

要移除任何配额:

# zfs set userquota@joe=none

设置组配额的一般格式为: groupquota@group=size

要将第一个组的配额设置为 50 GB,请使用:

# zfs set groupquota@firstgroup=50G

删除 firstgroup 组的配额,或确保未设置任何配额,请使用:

# zfs set groupquota@firstgroup=none

与用户配额属性一样,非 root 用户可以查看与其所属组关联的配额。拥有 groupquota 权限或 root 权限的用户可以查看并设置所有组的所有配额。

要显示文件系统或快照中每个用户使用的空间量以及任何配额,请使用 zfs userspace。要获取组信息,请使用 zfs groupspace。有关支持的选项或如何仅显示特定选项的更多信息,请参阅 zfs(1)。

特权用户和 root 可以使用以下命令列出 storage/home/bob 的配额:

# zfs get quota storage/home/bob

22.4.9. 预留空间

预留空间可保证数据集上始终可用的空间量。保留的空间将不对任何其他数据集可用。这个有用的功能确保为重要的数据集或日志文件提供可用的空间。

reservation 属性的一般格式为 reservation=size,因此要在 storage/home/bob 上设置 10 GB 的预留空间,请使用:

# zfs set reservation=10G storage/home/bob

要清除任何预留空间:

# zfs set reservation=none storage/home/bob

对于设置参考预留空间的 refreservation 属性,同样的原则适用,一般格式为 refreservation=size

此命令显示存储/home/bob 上存在的任何保留或 refreservations。

# zfs get reservation storage/home/bob
# zfs get refreservation storage/home/bob

22.4.10. 压缩

ZFS 提供透明压缩。在块级别写入的数据进行压缩可节省空间,还可增加磁盘吞吐量。如果数据压缩了 25%,则压缩数据以与未压缩版本相同的速率写入磁盘,结果是有效的写入速度为 125%。压缩也可以成为去重的一个很好替代方案,因为它不需要额外的内存。

ZFS 提供了不同的压缩算法,每种算法都有不同的权衡。ZFS v5000 引入了 LZ4 压缩,可以在不像其他算法那样牺牲大量性能的情况下对整个池进行压缩。LZ4 的最大优势在于提前中止功能。如果 LZ4 未在数据的标题部分实现至少 12.5%的压缩,ZFS 会以未压缩的方式写入块,以避免浪费 CPU 周期尝试压缩已经压缩或不可压缩的数据。有关 ZFS 中提供的不同压缩算法的详细信息,请参阅术语部分中的“压缩”条目。

管理员可以使用数据集属性查看压缩的有效性。

# zfs get used,compressratio,compression,logicalused mypool/compressed_dataset
NAME        PROPERTY          VALUE     SOURCE
mypool/compressed_dataset  used              449G      -
mypool/compressed_dataset  compressratio     1.11x     -
mypool/compressed_dataset  compression       lz4       local
mypool/compressed_dataset  logicalused       496G      -

数据集正在使用 449 GB 的空间(已使用属性)。如果不压缩,它将占用 496 GB 的空间( logicalused 属性)。这导致了 1.11:1 的压缩比。

压缩在与用户配额结合时可能会产生意想不到的副作用。用户配额限制了用户在压缩后数据集上实际占用的空间量。如果用户的配额为 10 GB,并写入了 10 GB 的可压缩数据,则他们仍然可以存储更多数据。如果他们稍后更新文件,比如数据库,使用更多或更少可压缩的数据,可用空间量将发生变化。这可能导致一个奇怪的情况,即用户并未增加实际数据量( logicalused 属性),但压缩的变化导致他们达到配额限制。

压缩与备份之间可能会产生类似的意外交互作用。配额通常用于限制数据存储,以确保有足够的备份空间可用。由于配额不考虑压缩,ZFS 可能会写入比未压缩备份所需空间更多的数据。

22.4.11. Zstandard 压缩

OpenZFS 2.0 增加了一个新的压缩算法。Zstandard(Zstd)提供比默认的 LZ4 更高的压缩比,同时比替代方案 gzip 速度要快得多。OpenZFS 2.0 从 FreeBSD 12.1-RELEASE 开始提供,可通过 sysutils/openzfs 获得,并且自 FreeBSD 13.0-RELEASE 起已成为默认选项。

Zstd 提供了大量的压缩级别,可在性能与压缩比之间提供精细控制。Zstd 的主要优点之一是解压速度与压缩级别无关。对于数据写入一次但经常读取的情况,Zstd 能让使用最高的压缩级别而无需读取性能惩罚。

即使频繁更新数据,启用压缩通常也会提供更高的性能。其中最大的优点之一来自压缩 ARC 特性。ZFS 的自适应替换缓存(ARC)将数据的压缩版本缓存在 RAM 中,每次解压缩。这使得相同数量的 RAM 可以存储更多数据和元数据,从而提高缓存命中率。

ZFS 提供 19 个级别的 Zstd 压缩,每个级别都可以在更慢的压缩速度的情况下提供更多的空间节省。默认级别是 zstd-3,比 LZ4 提供更好的压缩而几乎不会更慢。超过 10 级的级别需要大量内存来压缩每个块,内存少于 16GB 的系统不应使用它们。ZFS 还使用 Zstd_fast_级别的选择,这些级别压缩速度更快,但支持较低的压缩比。ZFS 支持 zstd-fast-1 到 zstd-fast-10,zstd-fast-20 到 zstd-fast-100,每次增加 10,以及 zstd-fast-500 和 zstd-fast-1000,这些级别提供最小的压缩,但提供高性能。

如果 ZFS 无法获得所需的内存来使用 Zstd 压缩块,它将退回到存储未压缩的块。除了在内存受限的系统上的 Zstd 的最高级别之外,这种情况不太可能发生。ZFS 会计算自加载 ZFS 模块以来发生这种情况的频率,次数为 kstat.zfs.misc.zstd.compress_alloc_fail。

22.4.12. 数据去重

启用去重功能时,ZFS 使用每个数据块的校验和来检测重复块。当新数据块是现有数据块的副本时,ZFS 会写入对现有数据的新引用,而不是整个重复的数据块。如果数据中包含大量重复文件或重复信息,可以实现巨大的空间节省。警告:去重功能需要大量内存,并且启用压缩可以在不增加额外成本的情况下实现大部分的空间节省。

要启用去重功能,请在目标池上设置 dedup 属性:

# zfs set dedup=on pool

去重仅影响写入池中的新数据。仅仅激活此选项不会对已写入池中的数据进行去重。一个刚启用去重属性的池将看起来像这个例子:

# zpool list
NAME  SIZE ALLOC  FREE   CKPOINT  EXPANDSZ   FRAG   CAP   DEDUP   HEALTH   ALTROOT
pool 2.84G 2.19M 2.83G         -         -     0%    0%   1.00x   ONLINE   -

该 DEDUP 列显示池的实际重复数据删除率。值为 1.00x 表示数据尚未进行重复数据删除。接下来的示例将一些系统二进制文件复制三次到在上面创建的重复数据删除池的不同目录中。

# for d in dir1 dir2 dir3; do
> mkdir $d && cp -R /usr/bin $d &
> done

要观察冗余数据的重复删除,请使用:

# zpool list
NAME SIZE  ALLOC  FREE   CKPOINT  EXPANDSZ   FRAG  CAP   DEDUP   HEALTH   ALTROOT
pool 2.84G 20.9M 2.82G         -         -     0%   0%   3.00x   ONLINE   -

该 DEDUP 列显示了 3.00x 的因子。检测和删除数据的副本使用了三分之一的空间。节省空间的潜力是巨大的,但代价是需要足够的内存来跟踪重复数据删除块。

去重并不总是有益的,当池中的数据不冗余时。ZFS 可以通过在现有池上模拟去重来显示潜在的节省空间。

# zdb -S pool
Simulated DDT histogram:

bucket              allocated                       referenced
______   ______________________________   ______________________________
refcnt   blocks   LSIZE   PSIZE   DSIZE   blocks   LSIZE   PSIZE   DSIZE
------   ------   -----   -----   -----   ------   -----   -----   -----
     1    2.58M    289G    264G    264G    2.58M    289G    264G    264G
     2     206K   12.6G   10.4G   10.4G     430K   26.4G   21.6G   21.6G
     4    37.6K    692M    276M    276M     170K   3.04G   1.26G   1.26G
     8    2.18K   45.2M   19.4M   19.4M    20.0K    425M    176M    176M
    16      174   2.83M   1.20M   1.20M    3.33K   48.4M   20.4M   20.4M
    32       40   2.17M    222K    222K    1.70K   97.2M   9.91M   9.91M
    64        9     56K   10.5K   10.5K      865   4.96M    948K    948K
   128        2   9.50K      2K      2K      419   2.11M    438K    438K
   256        5   61.5K     12K     12K    1.90K   23.0M   4.47M   4.47M
    1K        2      1K      1K      1K    2.98K   1.49M   1.49M   1.49M
 Total    2.82M    303G    275G    275G    3.20M    319G    287G    287G

dedup = 1.05, compress = 1.11, copies = 1.00, dedup * compress / copies = 1.16

在 zdb -S 完成对池的分析后,它会显示激活去重会实现的空间缩减比例。在这种情况下,1.16 是一个主要由压缩提供的较差的节省空间比例。在这个池上激活去重将不会节省任何空间,并且不值得启用去重所需的内存量。系统管理员可以使用公式 ratio = *dedup ** compress / copies 来规划存储分配,决定工作负载是否包含足够的重复块以证明内存需求。如果数据可以被合理地压缩,那么空间节省可能会很好。良好的做法是先启用压缩,因为压缩也可以显著提高性能。在节省可观且有足够可用内存用于 DDT 的情况下启用去重。

22.4.13. ZFS 和 Jail

使用 zfs jail 和相应的 jailed 属性将 ZFS 数据集委托给Jail。zfs jail jailid 将数据集附加到指定的jail,zfs unjail 将其分离。要从jail内部控制数据集,请设置 jailed 属性。ZFS 禁止在主机上挂载受监禁的数据集,因为它可能具有会危害主机安全的挂载点。

最后更新于

FreeBSD 中文社区