github编辑

在 Gentoo 中启用 ZFS 支持

  • 原文地址:ZFSarrow-up-right,此页面最后编辑于 2026 年 1 月 24 日 (星期六) 22:17。

ZFS 是一种由 Matthew Ahrens 和 Jeff Bonwick 创建的下一代 文件系统arrow-up-right。它围绕着几个核心理念进行设计:

  • 存储管理应当是简单的。

  • 冗余应当由文件系统处理。

  • 在修复文件系统时,不应将文件系统下线。

  • 在发布代码之前,对最坏情况进行自动化模拟是重要的。

  • 数据完整性至关重要。

ZFS 的开发始于 2001 年的 Sun Microsystems。2005 年,作为 OpenSolaris 的一部分,ZFS 在 CDDLarrow-up-right 许可下发布。2007 年,Pawel Jakub Dawidek 将 ZFS 移植到了 FreeBSD。2008 年,LLNL 的 Brian Behlendorf 启动了 ZFSOnLinux 项目,将 ZFS 移植到 Linux,用于高性能计算。2010 年,Oracle 收购了 Sun Microsystems,并在当年晚些时候终止了 OpenSolaris。

illumos 项目启动了,旨在取代 OpenSolaris。大约三分之二的 ZFS 核心团队成员辞职了,其中就有 Matthew Ahrens 和 Jeff Bonwick。许多人加入了后续开发 OpenZFS 的团队,最初是作为 illumos 项目的一部分。Oracle 内部未辞职的 1/3 ZFS 核心团队则继续在 Oracle Solaris 中开发不兼容的专有 ZFS 分支。

Solaris 的首个版本包含了一些在大规模辞职之前就已在开发中的创新改动。Solaris 的后续版本中所包含的改动则较少,英雄迟暮。如今,一个不断壮大的社区在多个平台上持续开发着 OpenZFS,如 FreeBSD、illumos、Linux 和 Mac OS X。

注意

Gentoo 的 ZFS 维护者不建议用户在 PORTAGE_TMPDIR 上使用 ZFS(或任何其他较为特殊的文件系统),因为 Portage 通常会出现 Bug。但截至 2022 年 5 月,在 ZFS 上的使用应当是没有问题的。

特性

可以在这篇 单独的文章arrow-up-right 中找到详细的特性列表。

安装

模块

有来自 ZFSOnLinux Projectarrow-up-right 的 Linux 树外内核模块支持。

自 0.6.1 版本起,OpenZFS 项目认为 ZFS 已经“准备好在从桌面到超级计算机的一切环境中进行大规模部署”,并且在大规模部署场景下是稳定的。

注意

所有对 git 仓库进行的更改都会由 LLNLarrow-up-right 进行回归测试。

USE 标志

Linux 内核模块及 ZFS 用户空间工具的可选功能:

USE 标志
说明

当与 USE=dist-kernel 结合时,可防止升级到未受支持的内核版本

在 initramfs 中内置内核模块,并重新安装内核(仅对发行版内核有效)

编译内核模块

启用从包含根文件系统的 zpool 启动所需的依赖

允许 ebuild 对特定文件执行符号剥离

使用用户指定的 CFLAGS 编译(不受支持)

启用额外的调试代码路径,如断言和额外输出。如需获得有意义的回溯信息,请参见 Gentoo Wiki: Backtracesarrow-up-right

启用发行版内核升级时的子槽重建

禁用对 sys-fs/zfs-kmod 的依赖,假设 ZFS 已包含在内核源码树中

不安装 Python 脚本(如 arcstat、dbufstat 等)并避免依赖 dev-lang/python

安装压缩的内核模块(如果内核配置启用模块压缩)

对已安装的内核模块进行加密签名(要求内核中 CONFIG_MODULE_SIG=y)

添加本地语言支持(使用 gettext - GNU locale 工具)

安装 zfs_key PAM 模块,用于自动加载 home 数据集的 ZFS 加密密钥

添加对 Python 语言的可选支持/绑定

仅内部使用,支持 Security Enhanced Linux,必须由 selinux 配置文件设置,否则会导致破坏

启用支持将 /bin/lib*/sbin/usr/sbin/usr/bin/usr/lib* 分开维护的行为

安装回归测试套件

添加调用栈展开和函数名解析支持

对 distfiles 验证上游签名

Emerge

要安装 ZFS,运行:

重要

在每次重新编译内核后,都需要重新编译 sys-fs/zfs-kmod,即使内核的改动不大,亦如此。在合并内核模块之后重新编译内核时,用户可能会遇到 ZFS 存储池进入不可中断睡眠状态(无法杀死的进程)或在执行时崩溃的问题。还可以配合 Distribution Kernelarrow-up-right 设置 USE=dist-kernel

ZFS 事件守护进程通知

ZED(ZFS Event Daemon)用于监控由 ZFS 内核模块生成的事件。当某 zevent(ZFS Event)被触发时,ZED 会运行为对应 zevent 类别启用的 ZEDLET(ZFS Event Daemon Linkage for Executable Tasks, ZFS 事件守护进程可执行任务关联)。

文件 /etc/zfs/zed.d/zed.rc

OpenRC

将 zfs 脚本加入运行级别,以便在启动时初始化:

注意

对大多数配置来说,只需要前两条命令。zfs-load-key 用于 ZFS 加密。zfs-share 适用于使用 NFS 共享的用户,而 zfs-zed 用于 ZFS 事件守护进程,负责通过热备盘处理磁盘更换以及通过电子邮件发送故障通知。

注意

若要将 ZFS 用作根文件系统,以及使用 ZFS swap,请将 zfs-import 和 zfs-mount 添加到 sysinit 运行级别,以便在启动或关机过程中访问文件系统。

systemd

启用服务,使其在启动时自动运行:

手动启动守护进程:

为了在启动时自动挂载 ZFS 存储池,需要启用以下服务和目标:

内核

sys-fs/zfs 需要内核支持 Zlib(模块/内建)。

内置

模块

注意

每当内核发生变化时,都必须重新构建内核模块。

注意

在安装内核模块时,必须始终选择稳定版本。你可以在 这里arrow-up-right 查看最新的稳定版本及其对应的内核版本,同时也应确认 Portage 是否提供了该特定的 内核版本arrow-up-right

你还应当在 package.mask 目录中添加一些文件,用于屏蔽特定版本的 zfs-kmod 和 zfs。这可以避免拉取这两个包的不同版本。此外,sys-fs/zfs 的版本也必须与 zfs-kmod 的版本保持一致。

如果使用的是由 dracut 或 genkernel 生成的 initramfs,请在(重新)编译模块后重新生成 initramfs。如果你使用的是自定义 initramfs,请参见下一节。

自定义 INITRAMFS

注意

本节不适用于 dracut、ugrd 或 genkernel 的用户。

注意

每次升级到新内核时,都必须在 initramfs 目录中为其创建一个对应的文件夹。

复制二进制文件,以便能在 init 中使用 modprobe

你还需要以下这些库文件,系统才能正确启动:

注意

如果在 modules 目录中存在多个内核文件夹,可以在 find 命令中使用 -not -path 来排除特定文件夹。

以下示例,演示为 6.6.2 创建 initramfs,并排除 6.1.118:

现在 initramfs 已创建完成,接下来请更新你的引导加载程序。

高级

关于改进 ZFS 在一些边缘场景下需求的高级技巧合集,请参阅以下子文章:

ZFS/Advancedarrow-up-right

使用

ZFS 已经内置了管理硬件和文件系统所需的一切程序,无需任何其他工具。

准备

ZFS 支持使用块设备或文件。这两种情况下的管理方式是相同的,但在生产环境中,ZFS 开发者建议使用块设备(最好是整块磁盘)。在试验本文所述的各种命令时,用户可以选择最方便的那种方式。

重要

为了充分利用高级格式化磁盘上的块设备(即具有 4K 扇区的驱动器),强烈建议用户在创建池之前阅读 ZFS on Linux FAQarrow-up-right

ZFS 存储池(zpool)是由一到多个虚拟设备(vdev)聚合而成的,而每个 vdev 又由一到多个底层物理设备按特定几何结构组合而成。下面是三条非常重要的规则:

  1. ZFS 存储池会跨 vdev 进行条带化,只要其中某个 vdev 不可用,整个 ZFS 存储池都将不可用

  2. 只要创建了 vdev,其几何结构就被固定,无法修改。重新配置 vdev 的唯一方式是销毁,再重新创建

  3. 要修复受损的 ZFS 存储池,每个底层 vdev 都必须具备某种程度的冗余

硬件考量

  • ZFS 需要 直接 看到并控制支撑 zpool 的物理设备(它有自己的 I/O 调度器)。强烈不建议使用硬 RAID,即使是多个单盘 RAID-0 虚拟磁盘,因为这种方式会削弱 ZFS 带来的优势。应始终使用 JBOD。有些控制器提供相关功能,而另一些(如较老的 LSI HBA)则需要刷入特定固件(在 LSI 的情况下称为“IT 模式固件”)才能实现 JBOD。不幸的是,有些 RAID 控制器只提供 RAID 级别而没有其他选择,这类控制器并不适合 ZFS。

  • ZFS 可能具有极高的 I/O 强度:

    • 在 NAS 设备中,非常流行的方案是使用二手 SAS HBA 连接多块 SATA 机械硬盘(在更大规模下,推荐使用近线 SATA 硬盘)

    • 如果 ZFS 存储池使用 NVMe 设备创建,请确保这些设备不是无 DRAM 的(请查看数据手册)。原因有二:既可以避免明显的瓶颈,也可以避免硬件崩溃,因为某些无 DRAM 模块在 I/O 请求过载时容易卡死 https://wiki.gentoo.org/wiki/ZFS#cite_note-1arrow-up-right

    • 不要在 ZFS 存储池中使用所谓的 SMR(Shingled Magnetic Recording, 叠瓦式磁记录)磁盘,这类磁盘写入速度极慢,并且在 ZFS 存储池 resilver 过程中可能发生超时,从而妨碍恢复。应始终在 ZFS 中使用 CMR(传统磁记录)磁盘。

  • 除非存储由不间断电源(UPS)保护或电力来源极其可靠,否则应考虑使用具备掉电保护的 NVMe/SSD(即带有板载电容)

  • 应避免使用 SATA 端口倍增器,除非没有其他选择,因为它会在已连接的磁盘之间复用流量,造成严重瓶颈,实际上会分割可用带宽,同时也会增加 I/O 延迟

  • ZFS 能够利用 X86/64 处理器中的 SSE/AVX 向量化指令来加速校验和计算,并会自动选择最快的实现。在 Linux 下,可以在 /proc/spl/kstat/zfs/fletcher_4_benchvdev_raidz_bench 中查看微基准测试结果。例如:

冗余方面的考量

重要

虽然一个 ZFS 存储池可以包含多个几何结构和容量各不相同的 vdev,但在同一个 vdev 内,所有驱动器必须是同一类型并且具有相同的特性,尤其是相同的容量和相同的扇区大小。不建议在同一个 vdev 中混合使用 SSD 和“老旧”机械硬盘,因为性能差异会带来问题。

与 RAID 类似,ZFS 提供了多种方式来创建具有(或不具有)冗余的 vdev,具体包括:

  • striped 条带化(“RAID-0”):速度最快的方案,但未提供任何磁盘损坏保护。如果一块磁盘损坏,不仅该 vdev 会失效,整个 zpool 也会失效(请记住:zpool 本质上始终是条带化的)。

  • mirror 镜像(“RAID-1”):一块磁盘是另一块的镜像。通常使用两块磁盘,但也可以使用三重(或更多)镜像,不过会牺牲空间利用效率。可容忍一块磁盘损坏(三重镜像则可容忍两块)。

  • raidz1,或称 raidz(“RAID-5”):使用单一校验并在 vdev 内分布,因此只能容忍一块磁盘损坏。vdev 的总容量等于所有单块磁盘容量之和(减去一块磁盘的容量)。

  • raidz2(“RAID-6”):与 raidz1 相同的概念,但使用两个校验。vdev 的总容量等于所有单块磁盘容量之和(减去两块磁盘的容量)。可容忍两块磁盘损坏。

  • raidz3:与 raidz1 相同的概念,但使用三个校验。vdev 的总容量等于所有单块磁盘容量之和(减去三块磁盘的容量)。可容忍三块磁盘损坏,适用于对数据安全性要求极高的场景。

准备存储池

提示

推荐使用物理设备来使用 ZFS。对于“空白”设备,并不需要进行分区,ZFS 会自动进行处理。

警告

作为后续说明的一种替代方案,如果没有可用的物理设备,也可以使用位于现有文件系统之上的文件。这种方式仅适合用于实验,绝不能在生产系统中使用

以下命令会在 /var/lib/zfs_img 中创建四个 2GB 的稀疏镜像文件,用作硬盘。它们最多会占用 8GB 的磁盘空间,但在实际使用中,由于只有写入的区域才会被分配,因此占用空间会很小:

注意

在导出池之后,所有这些文件都会被释放,并且 /var/lib/zfs_img 目录本身也可以被删除。

如果使用内存盘更为方便,上述操作的等效方式为:

这种方案需要在 Linux 内核配置中启用 CONFIG_BLK_DEV_RAM=m。作为最后的手段,可以使用回环设备并结合存放在 tmpfs 文件系统中的文件。第一步是创建用于存放原始镜像文件的 tmpfs:

然后创建原始镜像文件:

接着为上面创建的每个文件都分配一个回环设备:

ZFS 存储池

每个 ZFS 的使用场景都始于程序 /usr/sbin/zpool,它用于创建和调整 ZFS 存储池(zpool)。zpool 是一种逻辑存储空间,用于将一到多个 vdev 组合在一起。该逻辑存储空间可以同时拥有以下两种内容:

  • 数据集:一组文件和目录,可挂载到 VFS 中,就像 xfs 或 ext4 这样的传统文件系统一样

  • zvolume(或 zvol):虚拟卷,可以作为虚拟块设备访问,像机器上的任何物理硬盘一样使用,位于 /dev

在创建了 zpool 后,就可以对其中不同的 zvolume 和数据集创建时间点快照。这些快照可以被浏览,甚至可以回滚,用于将内容恢复到过去的某个时间点。ZFS 的另一个重要理念是:可以通过一系列属性来管理 zpool、数据集和 zvolume,例如加密、数据压缩、使用配额等,这些都只是属性可调内容的示例。

切记:zpool 是在其 vdev 之间进行条带化的,因此哪怕只丢失了一个 vdev,也意味着整个 ZFS 存储池的丢失。冗余发生在 vdev 层面,而不是 zpool 层面。万一 zpool 丢失,唯一的选择就是从头重建,并从备份中恢复其内容。仅仅创建快照并不足以保证在崩溃后的恢复,还必须将这些快照发送到其他地方。后续章节将对此进行进一步说明。

注意

ZFS 不只是个简单的文件系统(按通常被接受的定义),它更像是文件系统和卷管理器的混合体。关于这一点的讨论超出了本文范围,只需记住这一事实,并在本文其余部分中继续使用这一常见术语即可。

创建 ZFS 存储池

通用语法如下:

提示

如果未指定 striped、mirror、raidz、raidz2、raidz3 中的任何一种,则默认使用 striped

一些示例:

  • zpool create zfs_test /dev/sda:创建一个仅由单个物理设备组成的 zpool,包含一个 vdev(完全没有冗余)

  • zpool create zfs_test /dev/sda /dev/sdb:创建一个由两个物理设备条带化组成的单一 vdev 的 zpool(完全没有冗余)

  • zpool create zfs_test mirror /dev/sda /dev/sdb:创建一个包含一个镜像 vdev 的 zpool

  • zpool create zfs_test mirror /dev/sda /dev/sdb mirror /dev/sdc /dev/sdd:创建一个由两个 vdev 组成的“条带化镜像”zpool,每个 vdev 都是由两个物理设备构成的镜像(每个 vdev 中可以损坏一块磁盘而不丢失整个 zpool)

  • zpool create zfs_test raidz /dev/sda /dev/sdb /dev/sdc:创建一个包含三个物理设备、采用 RAID-Z1 的单一 vdev 的 zpool(可容忍一块磁盘损坏)

提示

还有一些其他用于 vdev 类型的关键字,例如 sparecacheslogmetadatadraid(分布式 RAID)。

如果使用的是文件而不是块设备,只需将块设备路径替换为所使用文件的完整路径即可。例如:

  • zpool create zfs_test /dev/sda 变为 zpool create zfs_test /var/lib/zfs_img/zfs0.img

  • zpool create zfs_test /dev/sda /dev/sdb 变为 zpool create zfs_test /var/lib/zfs_img/zfs0.img /var/lib/zfs_img/zfs1.img

以此类推。

在 zpool 创建完成后,无论其架构如何,都会自动创建一个与 zpool 同名的数据集,并直接挂载到 VFS 根目录下:

此时,该数据集只是个“空壳”,尚未包含任何数据。

显示 zpool 的统计信息和状态

可以通过以下命令查看当前已知的 zpool 列表以及一些使用情况统计信息:

下面是对显示内容的一些简要说明:

  • NAME:存储池的名称

  • SIZE:存储池的总大小。在 RAID-Z{1,3} vdev 的情况下,该大小已计入奇偶校验。由于 zpool_test 是一个镜像 vdev,这里显示的大小是单块磁盘的大小。

  • ALLOC:当前已存储数据的大小(对于 RAID-Z{1,3} vdev 同样适用上述说明)

  • FREE:可用的空闲空间(FREE = SIZE - ALLOC)

  • CKPOINT:可能存在的 checkpoint(即全局 zpool 快照)所占用的空间

  • EXPANDSZ:可用于扩展 zpool 的空间。通常这里不会显示任何内容,仅在特定场景下才会出现

  • FRAG:空闲空间碎片率,以百分比表示。碎片化对于 ZFS 来说并不是问题,因为它在设计时就考虑了这一点。目前也没有对 zpool 进行碎片整理的方法,除非从头重新创建并从备份中恢复数据

  • CAP:已使用空间的百分比

  • DEDUP:数据去重因子。除非某个 zpool 启用了数据去重,否则始终为 1.00x

  • HEALTH:当前 pool 的状态,“ONLINE”表示 pool 处于最佳状态(即未降级,或更糟的是不可用)

  • ALTROOT:备用根路径(重启后不会持久保留)。当需要在 live media 环境中处理 zpool 时非常有用:所有数据集都会相对于该路径挂载,该路径可以通过 zpool import 命令的选项 -R 来设置

也可以使用选项 -v 查看更多细节:

另一个非常有用的命令是 zpool status,它可以提供有关各个 zpool 当前状况的详细报告,以及是否存在需要修复的问题:

ZFS 存储池导入、导出

技巧

有关所有可用选项的详细说明,请参阅 zpool-import(8) 手册页。

zpool 并不会自动出现,必须先行 导入,系统才能识别,使用它。在 zpool 被导入后,将会发生以下事情:

  1. 所有数据集(包括根数据集)都会挂载到 VFS 中,除非 mountpoint 属性另有指定(例如 mountpoint=none)。如果数据集之间存在嵌套关系并且需要按层级方式挂载,ZFS 会自动处理这种情况。

  2. 将在 /dev/zvol/<存储池名称> 下创建 zvolume 的块设备条目。

要搜索并列出系统中所有可用(且尚未导入)的 zpool,可以执行以下命令:

要实际导入某个 zpool,只需指定其名称,ZFS 会自动在所有已连接的磁盘上进行探测:

或者使用选项 -a 一次性导入所有可用的 ZFS 存储池:

如果组成某个 ZFS 存储池的所有 vdev 都可用,无论是处于干净状态还是已降级但仍可接受的状态,导入操作都应当能够成功。如果哪怕只缺失了一个 vdev,也无法导入该 ZFS 存储池,此时只能通过备份来恢复。

现在来演示前面提到过的 ALTROOT:假设需要导入 zfs_test,但希望其所有可挂载的数据集都相对于 /mnt/gentoo,而不是 VFS 根目录。这可以通过如下方式实现:

可以通过以下方式确认:

与导入 zpool 相反的操作称为存储池 导出。如果没有任何对象正在使用该存储池(即数据集上没有打开的文件,且没有 zvolume 正在使用),则可以通过以下命令简单地导出 zpool:

此时,会从 VFS 中卸载所有已挂载的数据集,与 zvolume 对应的所有块设备条目也会从 /dev 中被清除。

编辑 Zpool 属性

zpool 具有若干属性(也称为 properties)。其中一部分是可以修改的,用来作为“开关”控制诸如数据压缩、加密等方面;而另一些属性则是内在属性,只能读取,不能修改。可以通过 zpool 命令的两个选项来与这些属性交互:

  • zpool get 属性名称 zpoolname 用于查看指定的属性名称(特殊的属性名称 all 可用于查看全部属性)

  • zpool set 属性名称=属性值 zpoolname 用于将属性名称设置为 属性值

输出采用表格格式,其中最后两列含义如下:

  • VALUE:该属性的当前值。对于布尔属性,值可以是 on / off;对于 zpool feature,值可以是 active(已启用且 当前 正在使用)、enabled(已启用但当前未在使用)以及 disabled(未启用,未在使用)。根据系统所使用的 OpenZFS 版本以及 zpool 上启用的 feature 情况,该 zpool 可能可以被导入,也可能无法导入。稍后会进一步解释这一点。

  • SOURCE:标明该属性值是被覆盖设置的(local),还是使用默认值(default)。如果某个属性不可更改、只能读取,则会显示一个短横线。

虽然不会对所有属性逐一说明,但有一点会立刻引起注意:有一些属性名称以 feature@ 开头。这是什么?是历史原因 https://wiki.gentoo.org/wiki/ZFS#cite_note-2arrow-up-right

传统的 zpool 版本号编号方案在 2012 年被废弃(大致是在 Oracle 停止 OpenSolaris 并转为闭门开发 ZFS 之后),并切换为 feature flag 机制(zpool 的版本号同时被设置为 5000,自此不再变化)。因此,才会在上述命令输出中看到各种 feature@ 条目。

技巧

某个 ZFS 实现所支持的最新 legacy 版本号,可以通过 zpool upgrade -v 查看。对于 OpenZFS/ZFS,其支持的最新 legacy 版本号是 28。

在版本号编号方案仍然使用的时期,当创建 zpool 时,操作系统会用其所支持的最高 zpool 版本号来标记该 zpool https://wiki.gentoo.org/wiki/ZFS#cite_note-3arrow-up-right。这些版本号从 1(ZFS 初始发布)开始,到 Solaris 11.4 SRU 51 所支持的 49 为止。由于 zpool 版本号在 Solaris 的各个版本之间是单调递增的,因此较新的 Solaris 版本总是可以导入由较旧 Solaris 版本创建的 zpool。但这里有个前提:为了获得较新 Solaris 版本所带来的各种 ZFS 新特性和增强功能,必须手动升级 zpool 的版本号。在 zpool 版本号被升级之后,旧版的 Solaris 将无法再导入该 zpool。

例如,在 Solaris 10 9/10(支持的 zpool 版本号最高为 22)下创建的 zpool,可以在 Solaris 10 1/13(支持的 zpool 版本号最高为 32)下导入,并且之后仍然可以再次在 Solaris 10 1/13 下导入——前提是该 zpool 的版本号尚未在 Solaris 10 1/13 下被升级(否则会将 zpool 标记为版本 32)。

同样的逻辑也适用于 feature flag:如果某个 zpool 启用了当前系统所使用的 ZFS 版本并不支持的 feature flag,则该 zpool 将无法被导入。与(legacy)版本号机制类似,为了获得后续 OpenZFS/ZFS 版本提供的新 feature flags,必须对 zpool 进行升级(手动操作),使其支持这些新的 feature flags。在完成这一操作后,较旧的 OpenZFS/ZFS 版本将无法再导入该 zpool。

重要

仅就 zpool 导入而言,feature flag 处于 disabled 还是 enabled 状态并不重要。仅在处于 active 状态时,才需要遵循 Feature Flag 对照表arrow-up-right 中关于只读或完全不可导入的规则。

警告

Feature flags 可能相当棘手:有些在被 enabled 后会 立刻 变为 active,有些只要 enabled 就再也无法回到 disabled,还有一些在进入 active 状态后就再也无法回到 enabled。更多细节请参阅 zpool-features (7)arrow-up-right。为了向后兼容,建议仅在确有需要时有选择地启用这些 feature。

如果 zpool 位于 SSD 或 NVMe 设备上,启用 feature autotrim 可能会比较方便,这样可以让 ZFS 自动处理相关的“日常维护”工作:

另一个非常有用的 feature 是数据压缩。ZFS 默认使用 LZ4,如果希望切换到 ZSTD,一个看似直接但实际上行不通的做法是:

确实如此,即便该 feature 在 zpool 上已经被 enabled,也不能通过这种方式直接将其激活。真正的激活方式是:在数据集或 zvolume 层级,通过某个“魔法命令”修改相关属性。一旦这个“魔法命令”被执行,ZSTD 压缩算法才会真正投入使用(即变为 active):

重要

该变更 只会 作用于之后新写入的数据,zpool 中的既有数据保持不变。

如前所述,为了使用更新版本 OpenZFS/ZFS(或操作系统所使用的其他 ZFS 实现)所提供的新 feature,zpool 必须进行 upgrade。当需要执行 upgrade 时,zpool status 会给出提示,并建议运行 zpool upgrade(在本例中完整命令为 zpool upgrade zpool_test):

在完成 zpool upgrade 之后:

要查看当前操作系统所使用的 ZFS 版本支持哪些 feature flags,可以使用:

Zpool 故障与 spare vdev

硬件并非完美,有时磁盘可能因为某些原因发生故障,或者其上的数据可能发生损坏。ZFS 提供了检测损坏并防止甚至“双比特翻转”错误的对策。如果底层 vdev 具有冗余能力,受损数据可以被修复,仿佛什么都没有发生过一样。

请记住,zpool 是跨所有 vdev 进行条带化的,当设备发生故障时可能出现两种情况:

  • 要么冗余度足够,zpool 仍然可用,但处于“DEGRADED”状态

  • 要么冗余度不足,zpool 进入“SUSPENDED”状态(此时必须从备份中恢复数据)

可以通过 zpool status 查看当前情况:

技巧

可以通过对块设备执行 echo offline > /sys/block/<block device>/device/state 来模拟磁盘故障(执行 echo running > /sys/block/<块设备>/device/state 可将磁盘恢复为在线状态)。

存在故障的磁盘(/dev/sde)需要被物理替换为另一块磁盘(此处为 /dev/sdg)。该操作首先需要将故障磁盘从 zpool 中下线:

然后执行替换操作(ZFS 会自动对新磁盘进行分区):

在磁盘被替换后,ZFS 会在后台启动一个称为 resilvering 的重建过程。根据 zpool 中存储的数据量不同,该过程可能需要数小时甚至数天。在此示例中数据量很小,因此 resilvering 只需几秒钟。在大型 zpool 上,resilvering 的耗时甚至可能不可接受,这正是 draid 发挥作用的场景(这是一种与 raidz 完全不同的机制,后文将介绍)。

如果不希望进行手动操作,那么是否可以为 zpool 配置热备盘,使其在磁盘故障时自动完成替换?这正是名为 spare 的特殊 vdev 的用途。向 zpool 添加 spare vdev 有两种方式:在创建 zpool 时指定,或在现有 zpool 上动态添加。作为热备的磁盘应当与故障磁盘保持一致。

要添加一个 spare vdev,只需执行 zpool add 命令(同样,ZFS 会自动对热备磁盘进行分区)。添加到 zpool 后,热备盘将处于待命状态,直到发生磁盘故障:

此时热备盘处于可用状态(AVAIL)。如果 /dev/sdh(或 /dev/sdg)发生故障,/dev/sdd 会立即接管故障磁盘(其状态变为 INUSE),并在后台触发 zpool 的 resilvering 过程,整个过程无需 zpool 停机:

原始磁盘会被自动、异步地移除。如果没有自动完成,可能需要使用 zpool detach 命令手动分离:

技巧

故障磁盘物理替换后,可以将其重新添加到 zpool 作为新的热备盘。同样,也可以让同一个热备盘在多个 zpool 间共享使用。

在当前示例中,如果已有一个镜像 vdev,也可以将第三块磁盘直接加入到该镜像 vdev 中,而非作为热备盘。这可以通过将新磁盘附加到现有镜像 vdev 的某块磁盘上来实现:

注意

zpool attach zpool_test /dev/sdd /dev/sdg 也会得到相同结果。

定期对 zpool 执行 scrub 是一种良好做法,可以确保其内容健康。如果发现数据损坏且 zpool 具有足够的冗余,损坏的数据会被自动修复。手动启动 scrub 命令:

如果发现数据损坏,输出示例如下:

技巧

要故意破坏磁盘内容,可以执行类似 dd if=/dev/urandom of=/dev/sdd1 的命令(等待几秒钟以破坏足够的数据)。

数据修复完成后,执行 zpool clear 命令,如提示所示:

技巧

要从三盘镜像 vdev 中分离磁盘,只需使用 zpool detach,如前所示。即便是两盘镜像,也可以分离其中一盘,但会失去冗余保护。

对于三盘镜像,即使仅有一块磁盘健康,zpool 仍然可以正常运行:

zpool 销毁与恢复

当不再需要某个 zpool 时,可以使用 zpool destroy 销毁它。如果 zpool 不为空并包含 zvol 或数据集,需要加选项 -r。无需先导出 zpool,ZFS 会自动处理。

如果 zpool 刚被销毁,且其所有 vdev 仍完好,可以通过 zpool import 再加选项 -D 重新导入:

通过这种方式导入已销毁的 zpool,也会将其“复原”:

警告

技术上讲,该操作只是将 zpool 标记为“已销毁状态”,底层 vdev 上的数据并未清除。因此,仍可读取所有未加密的数据

浏览 zpool 历史

有时查看 zpool 的操作历史非常有用,这时 zpool history 就派上用场了。由于 zpool_test 是全新创建的,暂时没什么有趣的记录,我们来看一个最近从远程 TrueNAS 备份恢复到工作站的生产环境 zpool 的示例:

也可以使用全格式显示完整信息(谁/做了什么/在哪/何时):

注意事项:

  1. 该 zpool 是使用 Linux live 环境(此处为 Gentoo LiveCD)创建的,当时环境设置了主机名为 livecd

  2. 用户 root 在该 live 环境下创建了两个占位(不可挂载)数据集 rpool/ROOTrpool/HOME

  3. 数据集 rpool/ROOT/gentoo-gcc13-avx512(显然是 Gentoo 系统)由用户 root 从远程快照恢复。

  4. 数据集 rpool/HOME/user111(显然是用户主目录)由用户 root 从远程快照恢复。

  5. 某个时刻,工作站在恢复的 Gentoo 环境下重启(注意主机名变为 wks.myhomenet.lan),导入了该 zpool。

  6. 依此类推。

Zpool 技巧与注意事项

  • zpool 创建后,有时无法缩小 — 如果 池没有 raidz vdev,并且所有 vdev 的 ashift 相同,则可在 0.8 及以上版本使用“设备移除”功能。但此操作可能影响性能,因此 在创建池或添加 vdev/磁盘时必须谨慎

  • 可以在 MIRROR 创建后添加更多磁盘。示例命令(/dev/loop0 为 MIRROR 中的第一块磁盘):

  • 有时,一个更宽的 RAIDZ vdev 不如两个(或更多)小型 RAIDZ vdev 合适。建议在最终确定前先测试预期用途。

  • RAIDZ vdev 创建后无法调整大小,只能添加额外热备。硬盘可以逐块替换为更大容量,例如用 2T 硬盘替换 1T 硬盘,从而增加 zpool 可用空间。

  • 可以在同一 zpool 中混合 MIRROR 和 RAIDZ vdev。例如,要在已有 RAIDZ1 vdev 的 zpool zfs_test 中添加两个磁盘作为 MIRROR vdev:

警告

通常没有充分理由这样做。特殊 vdev 或 log vdev 也许合理,但通常会导致两者性能一并下降。

注意

添加不匹配的 vdev 时,需要使用选项 -f

来自其他架构的 Zpool

可以导入来自其他平台的 zpool,即使它们使用不同的位序,只要满足以下条件:

  • 版本为 28(legacy)或更早

  • 对于版本号为 5000 的 zpool,所使用的 ZFS 实现至少支持该 zpool 指定的特性。

下面的例子中,存储池 oldpool 由使用 app-emulation/qemu 模拟的 FreeBSD 10/SPARC64 环境所创建。该 zpool 使用 legacy 版本 28 创建(zpool create -o version=28),这与运行 Solaris 10 的 SPARC64 机器所做的基本吻合:

注意所有 feature@ 属性(均为 disabled)和 version 属性(28)的值:

注意

FreeBSD 9、10、11 和 12 使用的是来自 illumos/OpenSolaris 的 ZFS 实现。FreeBSD 13 则使用 OpenZFS 2.x。

要让该 zpool 能够使用系统上 OpenZFS/ZFS 或 ZFS 实现的最新功能,可按照前面章节所述升级 zpool:

作为测试,对 zpool 进行 scrub:

关于其中的数据:

即便 zpool 来自完全不同的架构(SPARC64 是 MSB 位序,而 ARM/X86_64 是 LSB 位序架构),它仍然可以被正常导入。要清理测试环境(如本节后半部分所创建):

现在 zpool 已升级,如果尝试在旧的 FreeBSD 10/SPARC64 环境导入,会发生以下情况:

以只读方式导入:

系统能够识别 zpool,但拒绝以读写方式导入。执行 zpool statuszpool list 不会显示该 zpool,但 zpool get all 可以查看所有属性:

注意属性 [email protected]:spacemap_v2[email protected]:log_spacemap 的值。所有不受支持的功能都以 unsupported@ 开头。

为了完整性,这里展示尝试导入完全不兼容 zpool 的情况。例如,当 Linux 系统(OpenZFS 2.1.x)尝试导入在 Solaris 11.4 下创建的 zpool(Solaris 仍使用 legacy 版本号方案,zpool 版本号为 42):

提示

在这种情况下还有一条“救生索”:虽然 zpool 对 OpenZFS 来说是未知版本,但 Solaris 11.4 和 OpenZFS 2.1.x 报告的 ZFS 版本都是 5(不要与 zpool 版本混淆),因此 zpool 上的数据集可以先在 Solaris 环境下通过 zfs send 导出,然后在 Linux 环境下通过 zfs receive 导入。

重要

为了可重复性,本节剩余部分详细说明了如何在 FreeBSD/SPARC64 环境下创建上文使用的 zpool oldpool。如果不关心该过程,其余内容可以跳过。

创建上述 zpool oldpool 的步骤如下:

  • 安装 app-emulation/qemu(本例使用 QEMU 8.0),确保变量 QEMU_SOFTMMU_TARGETS 至少包含 sparc64,以构建完整的 SPARC64 系统模拟器:

  • 从镜像站下载 FreeBSD 10/SPARC64 DVD ISO 镜像(FreeBSD 9 在 QEMU 下启动会崩溃,因此最小版本为 FreeBSD 10;FreeBSD 12 在加载所需模块时可能有问题)

  • 为未来的 vdev 创建虚拟磁盘镜像(本例三个,每个 4G):

  • 启动 QEMU(使用 -nographic 避免 FreeBSD 卡住,同时 Sun4u 机器有 2 条 IDE 通道,因此分配 3 个硬盘 + 1 个 ATAPI CD/DVD ROM 驱动器):

  • 在 OpenBIOS 提示符 0 > 下输入:

  • FreeBSD 应该开始启动,由于模拟较慢,启动过程可能需要一些时间。启动完成后,检查所有虚拟磁盘(ada*X*)是否被识别。

  • 当内核启动完成后,FreeBSD 会询问控制台类型,直接按 ENTER 接受默认选项(VT100)

  • FreeBSD 安装程序启动后,使用 TAB 键两次将 LiveCD 高亮,然后按 ENTER 退出安装程序并进入登录提示符

  • 在登录提示符下输入 root(无需密码),即可进入 BASH shell 提示符

  • 依次执行命令:kldload opensolaris 然后 kldload zfs(部分警告可以忽略,因为 kldstat 显示已加载模块)

  • 创建使用 legacy 版本方案(version 28)的 zpool,名为 oldpool

  • 检查 zpool 状态,应显示 oldpoolONLINE(ZFS 会提示使用 legacy 格式,不要升级!)

  • 由于 zpool 已临时挂载在 /tmp/oldpool 下,可通过如下命令将一些数据拷贝到其中:

  • 导出 zpool:

  • 按 Ctrl+A X 终止 QEMU(无需正常关机,因为这是 live 环境),返回 Gentoo 主机 shell

  • 最后,将原始镜像映射为 loop 设备:

ZFS 数据集

概念

就像 /usr/sbin/zpool 用于管理 zpool 一样,第二个 ZFS 工具是 /usr/sbin/zfs,用于处理与数据集相关的任何操作。理解 zfsconcepts(7)arrow-up-rightzfs(8)arrow-up-right 中的概念非常重要。

  • 数据集(datasets)是逻辑数据容器,由 /usr/sbin/zfs 命令作为单一实体进行管理。每个数据集在给定 zpool 内具有唯一名称(路径),可以是文件系统(filesystem)、快照(snapshot)、书签(bookmark)或 zvolume。

  • 文件系统是 POSIX 文件系统,可以挂载到系统 VFS 中,并像 ext4 或 XFS 文件系统一样以文件和目录的形式显示。支持扩展形式的 POSIX ACL(NFSv4 ACL)和扩展属性。

注意

哪怕文件位于同一 zpool 内,移动文件集之间的文件也是“复制后删除”(cp-then-rm)操作,而非瞬间移动。

  • 快照(snapshot)是文件系统或 zvolume 的冻结只读时间点状态。ZFS 是写时复制(copy-on-write)系统,快照只保留自上一次快照以来发生的变化,因此如果没有变化,快照几乎是“零成本”的。与 LVM 快照不同,ZFS 快照不需要空间预留,并且数量可以无限。快照可以通过“魔法路径”浏览,查看快照时的文件系统或 zvolume 内容,也可以用于回滚相关文件系统或 zvolume,命名格式为:zpoolname/datasetname@snapshotname

警告

关于快照的重要事项:

  • 务必备份到外部系统! 如果 zpool 损坏/不可用或数据集被销毁,数据将永久丢失。

  • 快照不是事务性的,正在进行的写入不能保证完整存储在快照中(例如,当快照创建时,若 40GB 文件仅写入了 15GB,那么快照只会记录这 15GB)。

  • 对于设置了保留(reservation)的 zvolume,其首次快照可能会产生意外的空间占用,因为它会为覆盖整个卷预留足够空间,同时考虑已有的已分配空间。

  • 书签(bookmark)类似快照,但不保存数据,仅存储时间点引用。这对于对备份系统进行增量传输非常有用,而无需在源系统保留该数据集的所有历史快照。就像书签标记在书页上一样。另一个用途是创建经过编辑的数据集,命名格式为 zpoolname/datasetname#bookmarkname

  • 克隆(clone)是快照的可写“视图”,可以像文件系统一样挂载到系统 VFS。其特点是引入了父子依赖关系:当克隆被创建后,原始数据集在克隆存在期间不能被销毁。可以通过 promote 操作将克隆提升为父数据集,从而使原始文件系统成为子数据集(同时影响原始文件系统的快照,也会被“反转”)。

  • zvolume 是模拟块设备,可像任何物理硬盘一样使用,在目录 /dev/zvol/zpoolname 下作为特殊块设备条目访问。

文件系统数据集

每当创建一个 zpool 时,同名的第一个文件系统数据集也会被自动创建。除非销毁整个 zpool,否则无法删除该根数据集。之后创建的所有数据集都通过“路径”唯一标识,并始终位于该根数据集的某个位置。之所以说“某个位置”,是因为后续创建的文件系统数据集可以嵌套,从而形成层级结构。

举例说明,重新创建 zpool zpool_test

可以看到,根数据集的名称(NAME 列)是 zpool_test,与 zpool 名称相同。该数据集定义了挂载点(MOUNTPOINT 列),所以它是一个文件系统数据集。验证挂载情况:

提示

文件系统类型显示为 zfs,说明它是一个文件系统数据集。

查看内容:

空的! 现在创建额外的文件系统数据集(注意路径不带前导斜杠):

zfs list 可以查看结果。默认情况下,挂载点层级与文件系统数据集的层级相同。挂载点可以根据需要自定义,甚至完全不同。文件系统数据集的层级也可通过重命名(zfs rename)更改。例如,将 zpool_test/fds1/fds1_1/fds1_1_2 改为 zpool_test/fds3

注意

ZFS 会相应地调整挂载点。

更复杂的情况:重命名带有子数据集的文件系统,例如将 zpool_test/fds2 改为 zpool_test/fds3/fds3_1

提示

如果想在层级中创建“占位符”(即不包含数据,仅用作定义子数据集的文件系统),先创建完整层级,然后将 canmount 属性设置为 false。ZFS 会相应调整挂载点,但子数据集名称保持不变,需要单独修改。

与使用 zfs create 不同的是,直接使用 mkdir 也能创建层级,但每个文件系统数据集是独立的“盒子”,具有独立的属性集合。ZFS 无法压缩、加密或共享单个文件,也不能对单个文件进行快照/回滚,只能针对整个文件系统数据集操作。换句话说,对 zpool_test/fds1 的操作(属性更改、快照、回滚等)只影响它本身,而不影响子数据集或其他数据集。盒子里的内容,只影响盒子内部。

提示

运行 zpool history zpool_test 可以查看操作历史。当前结构存在但基本为空。属性将在下一节详细讲解。

在写入数据前,可为新的数据集启用压缩,例如创建 zpool_test/fds4 并复制大量易压缩数据(如 Linux 内核源码):

查看使用情况:

  • logicalreferenced:逻辑数据量(未考虑压缩前的实际大小)

  • referenced:物理存储实际占用大小

压缩开启时,logicalreferenced 通常大于 referenced。另一个验证方式:

销毁文件系统数据集

基础知识掌握后,现在可以销毁 zpool_test/fds4

警告

一旦销毁,数据集无法恢复,请确保有多份备份!

提示

只有“子”数据集可以被销毁。如果数据集有子数据集(如快照或其他文件系统数据集),可以使用递归选项:

务必 先用 -nv 查看将被删除的内容:

文件系统数据集属性

与 zpool 一样,数据集也有属性。在文件系统数据集的情况下,属性分为两类:

  1. 固有属性(intrinsic):无法修改,如创建时间、压缩比或剩余可用空间等。

  2. 可设置属性:可根据上下文需求进行调整。

数据集遵循层级结构,因此属性值会被继承:

  • 子数据集继承父数据集的属性值,父数据集又继承自身父级的属性,以此类推直到根数据集。

  • 根数据集部分属性会继承自所在的 zpool。

  • 如果某个数据集在其层级中覆盖了某个属性值,这个值会向下传播给它下方的所有子数据集,直到另一个子数据集再次覆盖该属性。

查看数据集属性的逻辑类似 zpool:

例如,查看根数据集:

重要

这里未讲解所有属性,只列举常用的。详细信息参考 zfsprops(7)arrow-up-right

举例:属性 type 描述数据集的类型,本例中为 filesystem,可以确认数据集性质。

SOURCE 列说明属性来源:

  • defaults:默认值,可修改

  • -(短横线):固有属性,不可修改

例如,数据集的创建时间、压缩比、剩余可用空间都是固有属性,无法更改。

接下来可重点关注一些固有属性,以便了解数据集基本情况。

属性
功能

type

描述数据集类型。本例中为 filesystem,其他可能类型包括 volumesnapshot 等。

used

数据集当前在物理存储上的占用大小(包括所有子数据集),无论类型如何。目前数据集仅包含一些元数据(尚未存放文件),所以显示的数值很小。如果启用了压缩,used 的值可能小于 logicaldf 也会显示相应结果)。

logical

数据集当前的表观大小(“真实”大小,包括所有子数据集),无论类型如何。目前数据集仅包含一些元数据,显示值很小。如果启用了压缩,logical 可能大于 useddf --apparent-size 也会显示相应结果)。

available

当前可用于存储数据的空间大小。如果数据集配置了多份副本或快照保留了大量数据,可用空间可能会快速减少。

compressratio

“真实”数据大小与物理存储大小的比率。未压缩数据集显示为 1,压缩数据集可能大于 1。例如,110MB 数据压缩后占用 100MB,则显示为 1.10X。压缩率高的数据集会显示更高的数值。

mounted

指示该文件系统数据集当前是否已挂载到 VFS 中(可通过 mount 命令查看)。

referenced

数据集在 zpool 上可访问的数据量(物理大小)。当前数据集仅有元数据,数值与 used 类似。随着克隆和快照的出现,该值会变化。如果启用压缩,referenced 可能小于 logicalreferenced

logicalreferenced

数据集在 zpool 上可访问的表观数据量(表观大小)。当前数据集仅有元数据,数值与 used 类似。随着克隆和快照的出现,该值会变化。如果启用压缩,logicalreferenced 可能大于 referenced

version

指示数据集使用的 ZFS 磁盘格式版本,而非数据集自身版本。十多年来,该值一直为 5,即使在 Solaris 11.4(2017 年 9 月退役)中亦是如此。后续,若 ZFS 磁盘格式规范升级,版本号将递增。请勿与 zpool 旧版本号混淆。

written

自上一个快照以来写入的数据量,即前一个数据集快照所引用的数据量。

属性
可选值
功能

mountpoint

path

legacy

canmount

yes

no

quota

quota_value

none

recordsize

recordsize_size

指定文件块大小为 recordsize_size。此属性在数据库等对文件系统 I/O 对齐有严格要求的场景中非常有用。默认块大小为 128K。

compression

on

off

sharenfs

on

off

sharesmb

on

off

exec

on

off

setuid

on

off

readonly

on

off

atime

on

off

xattr

on

off

encryption

on

off

提示

如果某属性会改变存储数据的状态(例如加密、压缩等),则该属性值的修改只会影响新添加或被修改的数据。如果属性值的修改不改变存储数据的状态(例如 NFS/SMB 共享、quota 等),则新属性值会立即生效。

首先,在根数据集上启用数据压缩,观察效果:

下面是继承关系的概览:

对于根数据集 zpool_testlocal 表示这里覆盖了默认值。而 zpool_test/fds1 及其所有子数据集继承了该值,ZFS 会智能地显示继承来源。

zpool_test/fds1/fds1_1 的 compression 属性设置为 off

注意

这里出现了两个覆盖。

若要将某个层级及以下的数据集重置为继承属性,可以使用命令:

提示

此时 SOURCE 不再是 local,而是重新继承自父级。

另一个示例:设置使用配额。为 zpool_test/fds1zpool_test/fds1/fds1_1 设置配额:

重要

quota 属性不仅定义了文件系统数据集及其子集的硬性空间限制,还包括快照保留的数据。如果只想限制“活跃”数据集的空间(不计快照),应使用 refquota 属性。

测试:在 zpool_test/fds1/fds1_1_1 下创建一个大于 2GB 的文件:

由于继承关系,zpool_test/fds1/fds1_1 zpool_test/fds1/fds1_1/fds1_1_1 都受 2GB 配额限制。

调整配额,将 zpool_test/fds1/fds1_1/fds1_1_1 设置为无限制:

至此,本章节对创建与重命名文件系统数据集,以及属性设置与继承机制的介绍已完成。

释放空间以便下一节操作:

通过 NFS/CIFS 共享 ZFS 数据集

可以直接通过 NFS、SMBFS/CIFS 共享 ZFS 文件系统数据集。没什么魔法,ZFS 会在后台自动调整 /etc/exports.d/zfs.exports(NFS)和 /etc/samba/smb.conf(Samba)文件的内容,让管理员无需手动修改。要共享数据集,只需设置 sharenfs/sharesmb 属性为所需协议即可。可以同时使用两种协议,但要注意:如果文件在读写模式下被打开,NFS 和 SMBFS/CIFS 的锁机制不会互相感知。

首先创建一个共享文件系统数据集 zpool_test/share_test 并通过 NFS 共享:

注意

属性 SOURCE 显示为 local,说明此层级覆盖了默认继承值。

在本机上,可以看到 /etc/exports.d/zfs.exports 已被添加了如下内容:

列出本机向 NFS 客户端导出的共享:

提示

可以传递 NFS 选项而不是仅使用 on,例如:

参考 exports(5) 手册页了解更多选项。

如果文件系统数据集还要作为 CIFS/SMB Samba 共享可见,前提是系统上已有正常工作的 Samba 配置。确保至少在 /etc/samba/smb.conf 中设置如下参数,否则 ZFS 将静默失败:

同时确保 /var/lib/samba/usershares 目录存在,并且拥有合适的所有权(如 root:root)和权限(01770)。

警告

如果执行 zfs set sharesmb=on 报错 SMB share creation failed,请检查 /var/lib/samba/usershares 目录是否存在且权限正确。

定义共享后,ZFS 会在 /var/lib/samba/usershares 下添加一个与数据集同名的文件:

文件内容示例:

提示

可以通过委托(delegation)让普通用户创建并共享数据集,这是更高级的概念,本节不再深入。

当然,也可以不使用这些属性,通过手动编辑配置文件来定义 NFS 或 Samba 共享。但使用属性方式更方便,管理起来也更简洁。

Zvol 数据集

Zvol 只是 ZFS 上的一个模拟块设备,可以像任何其他块设备一样使用:可以用 fdisk 分区,然后在上面创建文件系统(如 ext4、xfs 等),再进行挂载。技术上甚至可以在 zvol 上创建另一个 zpool。

与文件系统数据集类似,zvol 也有一套可修改的属性。其真正的强大之处在于数据管理:与文件系统数据集一样,zvol 可以被快照,从而进行回滚或克隆。

创建 zvol 使用 zfs create 命令:

  • 普通 zvol(空间全部预分配):

  • 稀疏 zvol(薄分配):

示例:创建一个 1G 的 zvol zpool_test/zvol1(全部预分配),以及一个 2G 的稀疏 zvol zpool_test/zvol2

注意

USED 列显示了实际占用空间。

重要

与文件系统数据集不同,zvol 不能嵌套 zvol,必须创建在文件系统数据集下。

可以使用“占位符”(不可挂载的文件系统数据集)来组织 zvol:

无论 zvol 的组织方式如何,都会在 /dev/dev/zvol/<ZFS存储池_名称> 下创建多个块设备节点:

对应的特殊设备节点为:

可以使用 fdiskparted 查看分区信息:

重要 zvol 总是报告物理扇区大小为 512 字节,而逻辑扇区大小由属性 volblocksize 决定(OpenZFS 2.1 默认 8K)。volblocksize 类似于文件系统数据集中的 blocksize。有关性能调优详情,请参考官方 OpenZFS 文档的 Workload Tuning/zvol-volblocksizearrow-up-right

parted 中创建一个用于 ext4 文件系统的主分区,步骤如下:先创建 GPT(或 MSDOS)分区表,然后创建分区:

  • mklabel gpt

  • mkpart primary ext4 2048s -1

  • q(退出 parted)

示例操作:

接下来像平常一样格式化新分区:

挂载到所需位置:

ZFS 快照(及回滚数据集)

就像关系型数据库可以在精确时间点进行备份和恢复一样,ZFS 数据集的状态也可以被“快照”,然后恢复,就好像自快照创建以来什么都没发生过一样。这种功能并非 ZFS 独有:BTRFS 和 NILFS2 也提供类似功能,LVM 也支持快照。Copy-on-Write 文件系统(如 ZFS、BTRFS 或 NILFS2)中的快照与 LVM 快照的主要区别在于无需预先设定大小。这不会因数据变化过多而失效,而且可以拥有几乎无限数量的快照,每个快照仅保存与前一个快照的差异。因此,如果两个快照之间没有变化,实际占用空间仅为少量元数据(几 KB 的量级)。

重要

与 BTRFS 快照不同,ZFS 快照是只读的。ZFS 有可写快照的概念,称为克隆(clone),稍后会详细介绍。

创建快照使用 ZFS 命令 zfs snapshot,后跟快照名称,遵循格式:zpool/the/dataset/to/be/snapshotted@快照名称。快照名称可以是任意字母数字序列,例如 mysnap20221214test123 都是有效的。唯一需要遵守的规则是使用 @ 字符作为数据集名称与快照名称的分隔符。

警告

只有文件系统数据集和 zvol 可以创建快照。无法对快照或书签进行快照操作。

快照创建完成之后,无需执行其他操作。ZFS 会在幕后透明地管理差异。只要快照未被销毁,所有变更历史都会被保留,无论是新增、删除还是修改数据。在快照被销毁后,ZFS 会检查数据集的快照时间线,然后释放不再被引用的块。

快照的配额管理可能比较棘手:如果一个文件系统包含大量小文件,但快照之间发生了大量变化,快照时间线的总大小可能会很大,即使当前数据集状态远低于阈值,也可能很快就触碰配额上限。若仅有少量大文件在快照间发生变化,情况也类似。

好吧,我们来演示一下。首先创建新的数据集,再复制一些文件进去:

此时没有什么特别需要说明的,只注意根数据集的已用大小与 zpool_test/testfsds1 的大小相同(433M)。由于根数据集仅包含少量元数据而没有文件,它只占用几 KB。

现在,我们创建第一个快照 snap1

乍一看,快照使用了 0 字节(USED 显示 0),因为数据没有变化。换句话说,文件系统数据集zpool_test/testfsds1 与其快照 zpool_test/testfsds1@snap1 指向完全相同的数据块。

换一个角度来看:

*文件系统数据集 zpool_test/testfsds1 的大小(USED 列)为 4.50G。 *文件系统数据集 zpool_test/testfsds1 引用的数据量(REFER 列)也是 4.50G。在此时 USED 和 REFER 两列显示相同的值,这是完全正常的。

  • 快照也引用着完全相同的 4.50G 数据,因此它的大小为零(快照看到的数据与文件系统数据集 看到的完全相同,因此两者差异为零)。

还记得之前看到的 zfs get 命令吗?我们在快照上查看:

不用解释每个属性,最重要的是:

  • type:这是一个快照(数据集),类型自然显示为 snapshot

  • creation:快照创建的日期/时间

  • used:几乎为零,因为快照和对应的文件系统数据集 指向相同内容(8.24M 为快照元数据)

  • referenced:前面看到的同样值 4.50G(如上解释)

  • createtxg:快照创建时所在的事务组(TXG)编号(TXG 编号随时间递增)。这有助于追溯某个文件系统数据集 的快照创建顺序,以便应对命名不一致的情况(例如 zpool_test/testfsds1@snap1zpool_test/testfsds1@mytestzpool_test/testfsds1@bug412 等)。

现在快照已存在,我们准备删除一些文件,但首先确定要删除的数据量:

然后删除这些文件并创建第二个快照:

乍一看,zpool_test/testfsds1@snap1 的快照大小大约从 0 字节增加到删除数据的大小。换句话说:

  • zpool_test/testfsds1@snap2 与当前 zpool_test/testfsds1 状态无差异,因此 USED 为 0。该快照引用的数据量为 4.20G(4.50G-307M ≈ 4.20G),即 REFER 列显示的值。

  • zpool_test/testfsds1 创建以来删除了 307M 数据,因此 zpool_test/testfsds1@snap1zpool_test/testfsds1@snap2 的差异也是 307M(ZFS 与 df 的计算略有差异)。

  • zpool_test/testfsds1 删除前的原始大小为 4.50G,因此 zpool_test/testfsds1@snap1 仍显示原始状态,REFER 列为 4.50G。

进一步操作前,有个(神奇的)技巧:虽然快照显示为不可挂载(MOUNTPOINT 列为 -),但可以在 /zpool_test/testfsds1/.zfs/snapshot/snap1 下以只读方式访问其内容(快照不可写):

可以看到快照创建后被 rm -rf {a,b,c}* 删除的文件和目录。这样就可以在快照创建的精确时间点复制或检查文件,非常有用来恢复被修改或删除的文件。

检查第二个快照时,由于在创建 zpool_test/testfsds1@snap2 前删除了文件,所以没有文件或目录以“a”、“b”、“c”开头:

很好,但还不够方便。如果能直接知道两个快照之间的精确差异,而不是手动检查每个文件,就更便捷了。幸运的是,ZFS 提供了解决方案,可以对快照进行 diff(输出已截断):

命令显示:所有以“a”、“b”、“c”开头的文件和目录已被删除(注意左边的 -),因此 /zpool_test/testfsds1 路径发生了变化(注意最后一行左边的 M)。

此时,我们进一步删除更多文件并再次创建快照 (zpool_test/testfsds1@snap3):

注意:

  1. 由于 zpool_test/testfsds1@snap3 反映当前 zpool_test/testfsds1 状态,其大小为零(或仅有可忽略的元数据),相对于 当前数据集状态。

  2. zpool_test/testfsds1@snap2 相对于 zpool_test/testfsds1@snap3 的差异对应删除的 Makefile(186K,包括元数据)。

  3. zpool_test/testfsds1@snap2 相对于 zpool_test/testfsds1@snap1 的大小保持不变(307M)。

再次使用 zfs diff(中间 grep 清理列表):

提示

非连续的快照也可以进行 diff。试试:zfs diff zpool_test/testfsds1@snap1 zpool_test/testfsds1@snap3 并观察结果。

现在,删除 /zpool_test/testfsds1 下的所有内容,并观察每个快照大小的变化:

于是:

哎呀……是空目录!

恢复删除前内容的一种方式是复制 /zpool_test/testfsds1/.zfs/snapshot/snap3 下的内容,但这样会重复数据块(在 OpenZFS 2.2 及其块克隆功能下可能不是这样),总之这是一种不优雅的数据恢复方式。此时更合适的方法是回滚。

回滚会将数据集的状态恢复到某个快照的状态。若要将 zpool_test/testfsds1 恢复到 zpool_test/testfsds1@snap3 快照创建时的状态,可使用命令 zfs rollback

瞧! 注意 zpool_test/testfsds1@snap3 相对于 zpool_test/testfsds1 的大小:由于数据集已回滚,二者“看到”的内容相同,因此差异为 0 字节。列出 /zpool_test/testfsds1 也会再次显示文件。

"是否可以回滚到更早的快照?" 答案是:可以。

回到 zpool_test/testfsds1@snap1 看似像前面演示的操作一样简单,但这次会有额外的提示:

ZFS 会智能地提示操作影响:如果在此回滚,zpool_test/testfsds1@snap2zpool_test/testfsds1@snap3 将失去意义,需要被销毁。继续操作:

回到了本节开始时的状态!

最后,可以通过命令 zfs destroy 销毁 zpool_test/testfsds1@snap1,就像文件系统数据集或 zvolume 一样:

注意

如果存在后续快照,ZFS 会要求显式使用 -r,就像前面回滚到第一个快照的示例中一样。

关于 ZFS 快照的重要注意事项

在继续之前,关于快照还有一些重要注意事项:

  • 对 zvolume 快照的操作遵循完全相同的思路,此处不再演示。

  • 在回滚数据集之前:

    • 确保对应的文件系统数据集 上没有打开的文件

    • 对于 zvolume,确保它已先卸载

  • 完全可以销毁中间快照,而不仅仅是最新的快照:在这种情况下,ZFS 会判断并释放所有剩余快照(以及对应的文件系统数据集)中不再被引用的数据块。zvolumes 快照亦同理。

  • 在快照被销毁后,除非通过备份进行 zfs send/receive,否则无法恢复该快照。

警告

在对底层数据集使用 zfs send/receive 后,不要删除中间快照。ZFS 会检测到接收端存在异常,并会直接拒绝传输。可以在不破坏 zfs send/receive 的前提下,在发送端销毁不再使用的快照的方法是:使用书签(bookmark),将在后文讨论。

ZFS 克隆

ZFS 克隆是 ZFS 快照的延续。在 ZFS 的世界中,快照是不可变对象,只能读取。不过,有一种方法可以获得可写的快照:那就是 ZFS 克隆。

直观地说,克隆是从快照派生出来的可写快照。克隆的创建方式也遵循这一思路。

首先,重新为上一节中用于快照管理的测试文件系统数据集 创建一个快照,因为在上节结束时它已被销毁:

看到 0 字节快照在此不应感到新奇或意外。同样不应感到意外的是,无法挂载快照(尽管可以通过伪文件系统 /zpool_test/testfsds1/.zfs/snapshot/snap1 访问)也无法修改快照:

可以使用以下命令为该快照创建克隆:

注意 zpool_test/testfsds1@snap1 的轻微大小变化,这是由于克隆创建所需的一些额外元数据,以及挂载点的定义所致。这里没有什么新奇之处。

接下来查看创建结果,这里不对所有属性都进行注释:

clone 作为 type 是错误的!虽然它本质上是克隆,但在行为上与普通文件系统数据集 完全一致,ZFS 也将其视为普通文件系统数据集。因此看到 type 为 filesystem 是完全正常的。

那么如何区分克隆和普通文件系统数据集呢?查看属性 origin 的设置:它指向克隆所派生的快照。作为对比,查看 zpool_test/testfsds1origin 设置:

一个破折号(-)!也就是没有任何来源。

zpool_test/testfsds1 是独立的文件系统数据集,而 zpool_test/testfsds1@snap1zpool_test/testfsds1_clone 之间存在父子关系。由于这种关系,除非先销毁 zpool_test/testfsds1_clone(及其可能存在的所有快照),否则无法销毁 zpool_test/testfsds1@snap1zpool_test/testfsds1

此时的结构可以用下图总结:

ZFS 克隆工作流 1.png

由于克隆是可写的,为什么不向其中添加一些内容呢?

可以看到大小变化约为 20MB。由于克隆现在独立存在,新增的数据不会影响其派生的快照大小。

接下来尝试销毁克隆派生的快照:

由于克隆与其源文件系统数据集 之间存在父子关系,ZFS 无法直接删除该快照。有几种方式可以解决:

  • 方案一:打破克隆与其父快照的关系,使它们各自成为独立的 ZFS 实体。这需要:

    • 使用 zfs sendzpool_test/testfsds1_clone 做完整备份

    • 销毁 zpool_test/testfsds1_clone

    • 使用 zfs receive 从备份恢复 zpool_test/testfsds1_clone

  • 方案二:反转父子关系,使 zpool_test/testfsds1_clone 成为 zpool_test/testfsds1@snap1 的父级

方案一在此无太大意义,因为会重复数据块而不是仅使用引用。因此选择方案二,并引入新的 ZFS 命令来操作克隆:zfs promote。该命令正如其名:将克隆“提升”(promote)为原父级的父对象,换句话说:“原本的子对象成为父对象,原本的父对象成为子对象”。

注意变化了吗?一切都被“颠倒”了,现在就像 zpool_test/testfsds1_clone 最初被创建,然后快照为 zpool_test/testfsds1_clone@snap1,再克隆出 zpool_test/testfsds1。重新整理后,组织结构如下:

  1. 克隆 zpool_test/testfsds1_clone 现在承担了原 zpool_test/testfsds1 的责任,所以其大小为 4.50GB(原 zpool_test/testfsds1 的大小)加上刚添加的 20MB,总计 4.52GB

  2. 快照 zpool_test/testfsds1@snap1 已从 zpool_test/testfsds1 “迁移”到 zpool_test/testfsds1_clone,并且成为 zpool_test/testfsds1 的父对象

查看类型和来源:

这一结构同样可以通过下图示意说明:

ZFS 克隆工作流 2.png

尽管由于克隆提升导致了层级关系发生变化,但所有这些实体本身依然完全相同。两个 ZFS 实体在生命周期上保留了相互的链接,并且可以各自拥有独立快照。

为了演示:如果删除 /zpool_test/testfsds1_clone/test.dd 会发生什么?为了验证,我们先为 zpool_test/testfsds1_clone 创建快照,然后再删除该文件:

解释起来有些复杂,但可以这样理解:由于 /zpool_test/testfsds1_clone/test.dd 仍被 zpool_test/testfsds1_clone@snap2 引用,其数据块依然保留(因此该快照显示 4.52GB)。而在 zpool_test/testfsds1_clone@snap1(原 zpool_test/testfsds1@snap1)中,该文件不存在,因此显示 4.50GB。演示中将 zpool_test/testfsds1 的内容设置为 zpool_test/testfsds1_clone 的子集,因此 ZFS 只需少量元数据即可标识各实体的内容。

另一个满分的问题:"是否可以恢复到原始状态?"(或换句话说:"能提升 zpool_test/testfsds1 吗?")。首先回滚,销毁 zpool_test/testfsds1_clone@snap2

瞧! 恢复到原始状态了!观察父子关系:

在 Gentoo 环境中,快照的典型用法是实验性地测试 "如果发生这种情况会怎样?":克隆生产环境的文件系统数据集,然后挂载所需内容(就像克隆的是未压缩的 Gentoo stage 3 tar 文件),再 chroot 进入其中,提供一个可随意操作的测试环境。克隆避免了来回复制数百 GB 数据占满本地磁盘空间(ZFS 若直接复制而非克隆,将复制所有数据块)。由于性能影响,ZFS 去重通常未被广泛使用。

作为文件系统数据集,可以使用已知命令销毁克隆:

维护

scrub(数据校验)

要对 zpool zfs_test 启动一次 scrub:

注意

这可能需要一些时间,并且对 I/O 的压力较大。ZFS 会尽量减小对系统整体的影响,但有时系统即便是最低优先级的 I/O 也无法很好处理。

监控 I/O

监控所有 zpool 的 I/O 活动(每 6 秒刷新一次):

技术细节

ARC

OpenZFS 使用 ARCarrow-up-right(自适应替换缓存)页面替换算法,而不是其他文件系统使用的最近最少使用(LRU)算法。ARC 命中率更高,因此提供了更好的性能。ZFS 中 ARC 的实现与原论文有所不同:作为缓存使用的内存量是可变的。将能在系统内存紧张时通过内核的 shrinker 机制回收 ARC 内存,并在系统有空闲内存时扩展。ARC 分配的最小和最大内存量随系统内存而异。默认最小值为总内存的 1/32 或 64MB(取较大者),默认最大值为系统内存的 1/2 或 64MB(取较大者)。

Linux 对 ARC 使用的内存计量方式与页缓存不同。具体来说,ARC 使用的内存显示在 free 输出中的“used”项下,而非“cached”。这并不妨碍系统在内存不足时释放这些内存,但可能让人误以为 ARC(及 ZFS)会尽可能使用全部系统内存。

调整 ARC 内存占用

可分别通过 zfs_arc_minzfs_arc_max 调整 ARC 的最小和最大内存使用量,有三种设置方式。第一种是在运行时设置(0.6.2 版本新增):

注意

此 sysfs 值从 ZFSOnLinux 0.6.2 开始可写。通过 sysfs 设置不会在重启后保留。如果未手动配置,该值在 sysfs 中为 0。可通过查看 /proc/spl/kstat/zfs/arcstats 中的 c_max 得知当前设置。

第二种是通过 /etc/modprobe.d/zfs.conf 进行设置:

注意

如果使用 genkernel 加载 ZFS,必须在运行 genkernel 之前设置此值,以确保文件被复制到 initramfs。

第三种是在内核命令行上指定:

zfs.zfs_arc_max=536870912(对应 512MB)。同样方法还可调整 zfs_arc_min

ZFS 根文件系统

本文为 stubarrow-up-right。请帮助 扩展内容arrow-up-right - 入门指南arrow-up-right

要从 ZFS 文件系统启动为根文件系统,需要一个支持 ZFS 的 内核arrow-up-right 和包含 ZFS 用户空间工具的初始 ramdisk(initramfs)。最简单的设置方法如下。

引导加载器

GRUBarrow-up-right 应在编译时启用 USE 标志 libzfs,以便从 ZFS 数据集中启动系统:

准备磁盘

虽然较新的 GRUBarrow-up-right 可以直接从 ZFS 存储池启动(即 /boot 位于 ZFS 上),但在维护 ZFS 本身时存在一些严重限制。此外,不建议将 swap 分区放在 ZFS 中,因为这可能导致死锁 https://wiki.gentoo.org/wiki/ZFS#cite_note-4)arrow-up-right。总体而言,推荐使用 Handbook 的默认分区方案arrow-up-right(boot 和 swap 分区,其余空间用于 zpool):

分区
意义
文件系统

/dev/sda1

EFI 系统(及 boot)分区

FAT32

/dev/sda2

swap 分区

swap

/dev/sda3

ZFS

根 ZFS pool 的空间

按照 Handbook 指南arrow-up-right 创建分区。准备好磁盘后,格式化 boot 分区并启用 swap,然后创建 ZFS 根数据集。OpenZFS 推荐使用唯一存储标识符(WWN)而非系统设备名。可通过运行以下命令获取 WWN:

注意

WWN 仅适用于 SATA/SAS 硬盘,NVMe 设备使用不同的命名,形如 eui.6479a776500000b8

/dev/sda3 的完整名称为 /dev/disks/by-id/wwn-0x50014ee2b3b4d492-part3。创建 ZFS 存储池 rpool:

注意

可参考 OpenZFS 文档arrow-up-right 获得各参数的含义。

现在创建 ZFS 数据集:

接下来创建一些可选的数据集(例如用于 /homevar/log):

至此,ZFS 设置完成,目前新的根文件系统挂载在 /mnt/gentoo。最后一步是复制 zpool.cache

对于新系统,请按照 Gentoo Handbook 安装指南arrow-up-right 操作至 Linux 内核配置arrow-up-right。下一章将提供内核所需的额外配置。

配置内核

发行版内核

在 chroot 后准备发行版内核。

文件 /etc/portage/package.use

安装内核:

Dracutarrow-up-right 会自动检测根文件系统上的 ZFS,并将必要模块添加到 initramfs 中。

Genkernel

在 chroot 环境下使用 genkernelarrow-up-right 时,为了在 initrd 中嵌入 /etc/hostid,先生成 /etc/hostid

/etc/genkernel.conf 中启用 ZFS="yes"

文件 /etc/genkernel.conf

随后重新生成 initramfs(并编译内核本身):

配置 GRUB 使用 ZFS 并指定启动的数据集:

文件 /etc/default/grub(指定 root 设备)

最后生成 grub.cfg

完成安装

如果希望避免再次导出和导入 ZFS 存储池,可在内核命令行使用 "zfs_force=1"

别忘了将 zfs-import 和 zfs-mount 服务添加到启动运行等级:

Chroot 环境

在 LiveCD 或其他系统中修复 ZFS,请参照 openzfs #1078arrow-up-right

如果 ZFS 文件系统已加密,则导入密钥:

然后挂载文件系统:

注意事项

另请参阅

外部资源

参考文献

最后更新于