22.4.zfs 管理

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

22.4.1.创建和销毁数据集

与传统的磁盘和卷管理器不同,ZFS 中的空间不是预先分配的。在传统的文件系统中,在分区和分配空间之后,如果不增加一个新的磁盘,就没有办法增加一个新的文件系统。使用 ZFS,创建新的文件系统在任何时候都是可能的。每个数据集 的属性包括压缩、重复数据删除、缓存和配额等功能,以及其他有用的属性,如只读、大小写敏感性、网络文件共享和挂载点。数据集之间的嵌套是可能的,子数据集将继承其祖先的属性。委托复制快照jail 允许将每个数据集作为一个单元进行管理和销毁。为每个不同类型或一组文件创建一个单独的数据集有其优势。拥有大量的数据集的缺点是一些命令,如 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 等协议使其对其他网络主机可用。

用任何文件系统格式化卷,或者不使用文件系统来存储原始数据。对用户来说,卷看起来就是一个普通的磁盘。把普通文件系统放在这些 zvols 上,可以提供普通磁盘或文件系统所不具备的功能。例如,在一个 250MB 的卷上使用压缩属性可以创建一个压缩的 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, -maprootnetwork。要设置数据集 NFS 共享参数,输入:

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

22.4.5.管理快照

快照功能是 ZFS 最强大的功能之一。快照提供了数据集只读和时间指针引用的复制功能。使用 写入时复制(Copy-On-Write,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 命令不会显示快照。要显示快照列表。添加 -t snapshot 参数到 zfs list-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 方式工作。他们保存更新的变化(delta)而不是完整的文件系统内容。这意味着快照在带来改变的同时只占用很少磁盘空间。通过将文件复制到数据集来更详细地观察磁盘使用情况并创建第二个快照:

# 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

这条命令列出了指定快照(如此例 mymypool/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 roolback 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 不能保留数据集不同状态之间的所有变化,因为快照是只读的。ZFS 不会删除受影响的快照除非用户输入 -r 来确认这是他想要的操作。如果那真是用户的意图并且用户知道这样会丢失所有中间快照,就执行如下命令:

# 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.管理 camino

camino 是快照的一份副本,处理起来更近似于一个普通的数据集。与快照不同,camino 可写也可挂载,有它自己的属性。在使用 zfs clone 之后,可以销毁原始快照。要对调 camino 和快照的 child/parent 关系可以使用 zfs promote 命令。child/parent 关系对调之后快照就会变成 camino 的 child,而不是最开始的父数据集。这回改变 ZFS 所占磁盘空间的方式,但不实际改变消耗的磁盘空间。把 camino 挂载到 ZFS 文件系统的任何层级中的位置都是可以的,并不只是在原快照位置之下的层级。

下面的示例数据集可以显示 camino 的一些特性:

# 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  -

camino 的典型作用是在一些特定的数据集上做实验,当出现问题时还可以使用快照进行恢复。因为快照内容是不能改变的,所以需要创建一个可读也可写的快照的 camino。在 camino 中取得想要的结果后,就可以提升 camino 让它成为一个数据集并删除老的文件系统。删除父数据集严格来说并不必要,因为 camino 和数据集可以无障碍地同时存在。

# 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

创建 camino 等于是产生了一个快照创建时的数据集的实际副本。这样就可以在不影响原数据集的情况下改变 camino 里的内容。他们之间通过快照进行关联。ZFS 在名为 origin 的属性中保存了这种关连。用 zfs promote 提升这个 camino 可以让 camino 成为一个独立的数据集。这会删除 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 到 promote 之后的 camino 对它进行改变后,例如,老的目录就失效了。然而,提升之后的 camino 可以取代它。要这样做,可以用 zfs destroy 来先销毁原来的数据集然后再用 zfs rename 命令来把 camino 命名为原来的数据集名称(也可以命名为完全不同的名称)。

# 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

快照的 camino 现在已经是一个普通的数据集。它含有源快照的所有数据和新增加的文件,如 loader.conf。camino 在不同场景下为 ZFS 用户提供了有用的功能。例如。把包含不同软件集合的快照 作为 jail 来使用。如果需要的话用户可以 camino 这些快照并加入他们自己的应用程序。如果对变更感到满意,就提升 camino 成为完成的数据集,对最终用户来说用起来就和真正的数据集别无两样。提供这些 jail 可以节约时间和管理开销。

22.4.7.数据复制

把单独的存储池的数据存放在一个地方会有被盗,自然或人为灾害的风险。定期为整个存储池创建备份至关重要。ZFS 提供了自带的序列化功能,它可以发送把数据以流的形式发送到标准输出。使用这一功能,就把数据存储在连接到本地系统的另一个存储池,例如通过网络连接的另一个系统。快照是复制的基础(参见 ZFS 快照章节)。用来复制数据的命令为 zfs sendzfs 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 的存储池是主存储池,定期会有数据读写操作发生。当主存储池不可用时,使用第二个名为 backup 的待机存储池来接替。注意发生故障时的切换并不是由 ZFS 自动完成的,而是当需要时必须经由系统管理员的人工操作。使用快照来提供复制时的一致性文件系统版本。在创建 mypool 的快照后。通过复制快照的方式把它复制到 backup 存储池。这并不会包含最近快照之后发生的改变。

# 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 sendzfs 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 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  -

增量流仅复制改变的数据而不是整个 replica1。发送差异化部分只要花少的多的时间并能够节约磁盘空间,不需要每次复制整个存储池。这个功能非常有用,尤其是在通过较慢的网络来复制或按传送的字节来收费时。

一个新的文件系统 backup/mupool,在来自存储池 mypool 的文件和数据到位之后就可以随时待命了。通过 -p 来指定复制数据集的属性,包含压缩设置,容量配额,挂载点等等。通过 -R 来指定复制数据集的所有子数据集及其属性。自动化式发送和接收可以在第二个存储池上建立定期备份。

22.4.7.2.通过 SSH 发送加密备份

通过网络发送流数据是进行远程备份的好方法,但也带来了一些缺陷。通过网络链接发送的数据是不加密的,任何人可以在数据发送方不知道的情况下截取流数据并把它们传输到其它地方。通过互联网向远程主机进行流数据传输时就非常危险,这样就需要使用 SSH 在网络传输时进行安全加密。ZFS 本来就需要从标准输出重定向流数据,使用 SSH 就很方便。如果在传输时还要对文件系统的内容在远端系统进行加密,就可以考虑使用 PEFS

先更改一些设置并做安全预防。这里介绍使用 zfs send 命令进行操作前的必要步骤,更多关于 SSH 的详细信息,请参考 OpenSSH

按下列步骤更改设置:

  • 使用 SSH 密钥来启用发送端和接收端主机的 SSH 功能。

  • ZFS 需要 root 用户权限来发送和接收流数据。用 root 账号登录接收端系统。

  • 默认情况下禁止使用 root 来登录系统。

  • 使用 ZFS 授权系统来允许系统中普通用户执行各自的发送和接收操作。在发送端:

# 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

现在普通用户就可以接收和挂载数据集了,也可以把 home 数据集复制到远端系统:

% 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 命令来把数据集和其所有子集,快照,camino,设置等等包含进需要发送的数据流。用 SSH 方式传递到远端使用 zfs receive 命令的主机 backuphost。这里也可以使用主机的 IP 地址或合格的域名。接收端主机把数据写入存储池 recvpool 中的 backup 数据集。添加 -dzfs recv 来用快照名替换接收端的存储池名称。-u 参数则不会让接收端文件系进行 mount。使用 -v 来显示传输情况的更多详细信息,包括已用时间和已传输的数据总量。

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

使用数据集配额来限制特定数据集的磁盘占用。参考配额也有类似功能,但它只计算数据集本身所占磁盘空间,并不包含快照和所有子数据集。同样地,可以使用用户 配额来防止用户或组占用存储池或数据集中的所有磁盘空间。

下例例子假设系统中已经存在这些用户。在向系统添加用户时,先确保创建用户的 home 数据集并将挂载点设为 /home/bob。然后创建用户并让 home 目录指向数据集的挂载点位置。这样就可以正确地设置用户和组相关权限而不覆盖现有可能存在的 home 目录路径。

强制设置数据集 storage/home/bob 配额为 10 GB:

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

强制设置数据集 storage/home/bob 引用配额为 10 GB:

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

删除数据集 storage/home/bob 配额:

# 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

注意

用户配额属性并不会通过 zfs get all 命令显示出来。非 root 权限用户并不会看到其他人的配额除非被通过 userquota 进行授权。获得授权的用户就可以看到并设置所有人的磁盘空间配额,

设置组配额的格式通常为: groupquota@group=size

如要设置 firstgroup 的组配额为 50 GB,可以使用:

# zfs set groupquota@firstgroup=50G

使用用户配额属性,非 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

设置引用保留空间时同样的规则也适用于 reservation 属性,通常格式为 refreservation=size

下面命令会显示和 storage/home/bob 相关的任何保留空间或引用保留空间:

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

22.4.10.压缩

ZFS 提供了透明化的压缩功能。在数据块层面的写入上进行数据压缩可以节约磁盘空间,也可以提高存盘的吞吐效率。如果数据压缩 25%,压缩后的数据会和未压缩时相同的速率被写入磁盘,有效写入速度就达到了 125%。压缩也可以成为去重功能之外的另一个好选择,因为它并不需要额外的内存。

ZFS 提供了不同的压缩算法,每种都有各自一些劣势。ZFS v5000 的 LZ4 压缩方式可以将整个存储池进行压缩,不会像其它算法那样带来大的性能损失。LZ4 的最大优势是 early abort 功能,如果 LZ4 不能在数据 header 部分获得至少 12.5% 以上的压缩率,它就不会对数据块进行压缩以避免浪费 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 的磁盘空间(属性 used)。如果不用压缩,它会占用 496 GB 磁盘空间(属性 logicalused)。最终的压缩比为 1.11:1。

在和用户配额 一起使用时压缩会带来一些意想不到的副作用。用户配额限制用户在数据集压缩之后的实际磁盘占用空间。如果一个用户有 10 GB 配额,写入 10 GB 压缩后的数据,他们还可以存储更多数据。如果之后他们要更新一个文件,如数据库,压缩后的数据变得更多或更少,可用空间的总量对他们就会改变。这会导致一些奇怪的情形出现,比如虽然用户的数据没有增加实际的磁盘空间占用(属性 logicalused),但压缩率的意外变化让他们触发了空间配额的限制。

压缩在进行备份操作时也会有类似意想不到的影响。空间配额经常被用来限制数据存储以确保有足够的备份空间,但计算配额时又不会考虑 ZFS 处理未压缩备份时可能会实际写入更多压缩数据量情形。

22.4.11.Z 标准压缩

OpenZFS 2.0 增加了一个新的压缩算法。Z 标准(Zstd) 比默认的 LZ4 提供了更高的压缩比,同时有着比其它算法如 gzip 快得多得速度。OpenZFS 2.0 从 FreeBSD 12.1-RELEASE 版本起可以通过 sysutils/openzfs 来使用,并在 FreeBSD 13.0-RELEASE 中成为了默认的 zfs 版本。

Zstd 提供了很多压缩级别供选择,在传统压缩比之外还额外提供了性能上的精细控制。Zstd 一项主要优势就是解压速度和压缩级别无关,对于一些写入一次但经常读取的数据,Zstd 允许使用最高压缩级别而不会有读取性能上的损失。

甚至在进行定期的数据更新时,启用压缩功能经常有着更高的性能,这是 ARC 压缩功能带来的最大的优势之一。ZFS 的自适应替换缓存(ARC)技术可以缓存内存中的压缩数据并每次进行解压。这可以让同样大小内存存储更多数据和元数据,增加了缓存的命中率。

ZFS 提供了 19 个 Zstd 压缩级别,每个级别都带来更多磁盘空间节约当然以更慢压缩时间为代价。默认的压缩级别为 zstd-3,它提供了优于 LZ4 的压缩率,而且压缩速度不会慢很多。级别 10 以上就需要更多内存来压缩系统中每个区块,低于 16 GB 内存就不要去使用。ZFS 也使用 Zst_fast_level 参数,获得更快的压缩速度,但压缩比会更低。ZFS 支持 zstd-fast-zstd-fast-10zstd-fast-20zstd-fast-100 (以单位 10 为步进),zstd-fast-500zstd-fast-1000 提供了最小的压缩率,但有着最高的性能。

如过使用 Zstd 后 ZFS 不能获得足够的内存来压缩数据块,它就会按未压缩的方式来存储数据块。这种情形不太可能发生,除非最高级别的 Zstd 在某些系统上被限制了内存使用。ZFS 在 kstat.zfs.misc.zstd.compress_alloc_fail 加载 ZFS 模块后就会开始计算这种情况的出现频率。

22.4.12.Z 去重

当启用后,去重功能利用每个区块数据的校验值来删除重复的区块。当有一个新的区块和已有区块重复时,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 数据集分配给 Jailzfs jail jailid 将一个数据关联到指定的 jail。zfs unjail 则取消这种关联。要在 jail 内控制数据集,可以设置 jailed 属性。ZFS 禁止在主机上挂载 jail 数据集,因为一些挂载节点会对主机安全带来威胁。