For the complete documentation index, see llms.txt. This page is also available as Markdown.

构建以 ZFS 为根文件系统的 Ubuntu 22.04

概述

Ubuntu 安装器

Ubuntu 安装器仍然支持 ZFS,但在 22.04 中它被几近删除(它在 22.04 中被几近删除),并且它已经不再安装 zsys(它不再安装 zsys)。目前,这份教程仍然使用 zsys,但在不久的将来很可能会被移除。

树莓派

如果你希望在树莓派上安装,请参阅 Raspberry Pi 的 Ubuntu 22.04 Root on ZFS

注意事项

  • 本教程使用整个物理磁盘。

  • 不要将这些说明用于双系统。

  • 备份你的数据。一切既有数据都将丢失。

系统需求

在内存少于 2 GiB 的计算机运行 ZFS 会很慢。对于基本工作负载,建议使用 4 GiB 内存以获得正常性能。如果你希望使用去重(deduplication),你将需要大量的内存。启用去重是永久性的更改,无法轻易回退。

加密

本指南支持三种不同的加密方案:未加密、ZFS 原生加密、以及 LUKS。无论使用哪一种选项,所有 ZFS 功能都可以完整使用。

未加密当然不会对任何内容进行加密。在没有任何加密的情况下,该选项自然具有最佳性能。

ZFS 原生加密会对 root 存储池中的数据以及大部分元数据进行加密。它不会加密数据集或快照的名称或属性。boot 存储池完全不加密,但其中只包含 bootloader、kernel 和 initrd。(除非你在 /etc/fstab 中放入了密码,否则 initrd 不太可能包含敏感数据。)如果不在控制台输入口令,系统将无法启动。性能良好。由于加密发生在 ZFS 内部,即使使用了多个磁盘(mirror 或 raidz 拓扑),数据也只需要加密一次。

LUKS 会加密几乎所有内容。唯一未加密的数据是 bootloader、kernel 和 initrd。如果不在控制台输入口令,系统将无法启动。性能良好,但 LUKS 位于 ZFS 底层,因此如果使用了多个磁盘(mirror 或 raidz 拓扑),数据需要在每个磁盘上各加密一次。

步骤 1:准备安装环境

  1. 启动 Ubuntu Live CD。在 GRUB 启动菜单中,选择 Try or Install Ubuntu。在 Welcome 页面,选择你偏好的语言并选择 Try Ubuntu。根据需要将系统连接到互联网(例如连接你的 WiFi 网络)。打开终端(按 Ctrl-Alt-T)。

  2. 配置并更新软件仓库:

  3. 可选:在 Live CD 环境中安装并启动 OpenSSH 服务器: 如果你有第二台设备,使用 SSH 访问目标系统会比较方便:

    安装完整的 vim 包可以修复在通过 SSH 使用 Live CD 环境中自带的 vim-tiny 包时出现的终端问题。

    提示

    你可以通过 ip addr show scope global | grep inet 查找你的 IP 地址。然后,在你的主机上使用 ssh ubuntu@IP 进行连接。

  4. 禁用自动挂载:

    如果磁盘之前被使用过(并且分区位于相同的偏移位置),在未禁用的情况下,之前的文件系统(例如 ESP)会被自动挂载:

  5. 切换为 root:

  6. 在 Live CD 环境中安装 ZFS:

第 2 步:格式化磁盘

  1. 设置磁盘名称变量:

    在 ZFS 中始终使用较长别名 /dev/disk/by-id/*。直接使用设备节点 /dev/sd* 可能会造成偶发的导入失败,尤其是在具有多个存储池的系统上。

    提示:

    • ls -la /dev/disk/by-id 将列出这些别名。

    • 你是在虚拟机中操作吗?如果你的虚拟磁盘在 /dev/disk/by-id 中缺失,且你在使用 KVM + virtio,请使用 /dev/vda;否则请阅读 troubleshooting 部分。

    • 对于镜像或 raidz 拓扑,请使用 DISK1DISK2 等。

    • 在选择 boot 池大小时,请考虑你将如何使用这些空间。一个内核和 initrd 可能会占用大约 100M。如果你有多个内核并进行快照,boot 池空间可能会不足,尤其是在需要重新生成 initramfs 镜像时,每个镜像可能约为 85M。请根据你的需求合理规划 boot 池大小。

  2. 如果你在重复使用磁盘,请按需清理: 确保 swap 分区未被使用:

    如果磁盘之前用于 MD 阵列:

    如果磁盘之前用于 zfs:

    对于基于闪存的存储,如果磁盘之前被使用过,你可能希望对整块磁盘执行 discard(TRIM/UNMAP),这可以提升性能:

    清除分区表:

    如果你看到内核仍在使用旧分区表的提示信息,可以通过以下方式请求内核重新加载分区信息:

    如果新分区仍未显示,你可以重启并重新开始(但可以跳过此步骤)。

  3. 创建 bootloader 分区:

注意:

Ubuntu 安装程序在 legacy(BIOS)启动时使用 MBR 标签,而本教程在 UEFI 和 legacy(BIOS)启动两种情况下都使用 GPT 分区标签。这比提供两种不同方案更简单,同时也提供了前向兼容性(面向未来)。换言之,对于 legacy(BIOS)启动,这将允许你在将来把磁盘移动到新的系统/主板上,而无需重建存储池(也无需从备份中恢复数据)。基于类似原因,两种情况下都会创建 ESP。此外,在单磁盘安装中,ESP 还用于 /boot/grub,如下文所述

  1. 创建 swap 分区

本教程的早期版本曾将 swap 放在 zvol 上。Ubuntu 不推荐这种配置,因为可能导致死锁。 上游也有相关的缺陷报告。 将 swap 放在分区上会放弃 ZFS 校验和带来的好处(针对 swap)。考虑到关于 ZFS 与 swap 发生死锁的报告,这可能是合理的权衡。如果你对此感到疑惑,直接不要用 swap 即可。 如果你希望使用 swap,请选择以下方案之一:

  • 单块磁盘安装:

  • 镜像或 raidz 拓扑:

根据需要调整 swap 大小。如果你希望启用休眠(仅适用于未加密的系统),swap 分区的大小必须至少与系统内存相同。

  1. 创建 boot 池分区:

Ubuntu 安装程序使用磁盘空间的 5%,并限制最小为 500 MiB、最大为 2 GiB。将该分区设置得过小(而 500 MiB 可能就太小)可能会导致无法升级内核。

  1. 创建根池分区:

请选择以下方案之一:

  • 未加密或 ZFS 原生加密:

  • LUKS:

如果你要创建镜像或 raidz 拓扑,请对将成为该存储池一部分的所有磁盘重复这些分区命令。

  1. 创建 boot 池:

通常不需要为 boot 池自定义任何选项。

请忽略关于某些特性“not in specified‘compatibility’feature set”的警告。

GRUB 并不支持所有 zpool 特性。请参阅 grub-core/fs/zfs/zfs.c 中的 spa_feature_names。此步骤为 /boot 创建了一个独立的 boot 池,其特性仅限于 GRUB 支持的范围,从而能让根池使用任意/全部特性。请注意,GRUB 以只读方式打开存储池,因此所有只读且兼容的特性都被 GRUB“支持”。

提示:

  • 如果你要创建镜像(mirror)拓扑,请使用以下命令创建池:

  • 对于 raidz 拓扑,将上述命令中的 mirror 替换为 raidzraidz2raidz3,并列出来自其他磁盘的分区。

  • 启动池名称不再是任意的。它 必须bpool。如果你确实想重命名它,请在 GRUB 安装完成后编辑 /etc/grub.d/10_linux_zfs(并运行 update-grub)。

功能说明:

  • allocation_classes 功能应当是安全的。然而,除非实际使用它(即使用 special vdev),否则启用它没有意义。几乎不可能有人会在启动池中使用该功能。如果关心启动池性能,将整个池放在更快的磁盘上,比将其用作 special vdev 更合理。

  • device_rebuild 功能应当是安全的(但在 raidz 上不兼容),不过启动池很小,因此在实践中并不重要。

  • log_spacemapspacemap_v2 功能已经过测试并且是安全的。启动池很小,因此在实践中并不重要。

  • project_quota 功能已经过测试并且是安全的。该功能几乎不可能对启动池产生影响。

  • resilver_defer 应当是安全的,但启动池足够小,因此几乎没有必要。

  • 作为只读兼容功能,userobj_accounting 理论上应当兼容,但在实践中,GRUB 可能会因“invalid dnode type”错误而失败。该功能对 /boot 并不重要。

  1. 创建根池: 选择以下方案之一:

    • 未加密:

    • ZFS 原生加密:

    • LUKS:

备注:

  • 此处建议使用 ashift=12,因为如今许多硬盘具有 4 KiB(或更大)的物理扇区,尽管它们呈现为 512 B 的逻辑扇区。此外,未来更换的硬盘也可能具有 4 KiB 的物理扇区(在这种情况下,ashift=12 是理想的)或 4 KiB 的逻辑扇区(在这种情况下,ashift=12 是必需的)。

  • 设置 -O acltype=posixacl 会在全局启用 POSIX ACL。如果你不想这样做,请移除该选项,但之后需要在为 /var/log 执行 zfs create 时添加 -o acltype=posixacl(注意:小写的“o”),因为 journald 需要 ACL。另外,禁用 ACL 似乎会破坏 NFSv4 下的 umask 处理

  • 设置 xattr=sa极大地提升扩展属性的性能。在 ZFS 内部,扩展属性用于实现 POSIX ACL。扩展属性也可以被用户态应用程序使用。一些桌面 GUI 应用程序会使用它们。 Samba 可以使用它们来存储 Windows ACL 和 DOS 属性;它们对于 Samba Active Directory 域控制器是必需的。 请注意,xattr=saLinux 特有的。如果你将启用了 xattr=sa 的池移动到除 ZFS-on-Linux 之外的其他 OpenZFS 实现上,扩展属性将无法读取(但你的数据可以)。如果扩展属性的可移植性对你很重要,请省略上面的 -O xattr=sa。即使你不希望整个池都使用 xattr=sa,将其用于 /var/log 也可能是合适的。

  • 设置 normalization=formD 可以消除与 UTF-8 文件名规范化相关的一些极端情况。它还隐含了 utf8only=on,这意味着只允许 UTF-8 文件名。如果你需要支持非 UTF-8 文件名,请不要使用该选项。关于为何强制要求 UTF-8 文件名可能是个坏主意的讨论,请参见 The problems with enforced UTF-8 only filenames

  • recordsize 未设置(保持默认的 128 KiB)。如果你想进行调优(例如 -O recordsize=1M),请参阅这些 不同的 博客 文章

  • 设置 relatime=on 是在经典 POSIX atime 行为(具有显著的性能影响)与 atime=off(通过完全禁用 atime 更新来提供最佳性能)之间的折中方案。自 Linux 2.6.30 起,relatime 已成为其他文件系统的默认设置。更多信息请参阅 RedHat 的文档

  • 请务必在驱动器路径中包含 -part4 这一部分。如果你忘记这一点,你指定的将是整个磁盘,ZFS 随后会对其重新分区,你将失去 bootloader 分区。

  • ZFS 原生加密现在默认使用 aes-256-gcm

  • 对于 LUKS,所选择的密钥长度是 512 位。然而,XTS 模式需要两个密钥,因此 LUKS 密钥会被一分为二。因此,-s 512 表示 AES-256。

  • 你的口令很可能是最薄弱的环节。请谨慎选择。相关指导请参阅 cryptsetup FAQ 的第 5 节

提示:

  • 如果你要创建 mirror 拓扑,请使用以下方式创建池:

  • 对于 raidz 拓扑,请将上述命令中的 mirror 替换为 raidzraidz2raidz3,并列出额外磁盘上的分区。

  • 在将 LUKS 与 mirror 或 raidz 拓扑一起使用时,请使用 /dev/mapper/luks1/dev/mapper/luks2 等,这些需要你使用 cryptsetup 创建。

  • 池名称是任意的。如果更改,必须一致地使用新名称。在可以自动安装到 ZFS 的系统上,根池默认命名为 rpool

步骤 3:安装系统

  1. 创建用作容器的文件系统数据集:

  2. 为根文件系统和启动文件系统创建文件系统数据集:

  3. 创建数据集:

    以下数据集是可选的,取决于你的偏好和/或软件选择。 如果你希望将它们分离以将其排除在快照之外:

    如果需要(Ubuntu 安装程序会创建这些):

    如果你在此系统上使用 /srv

    如果你在此系统上使用 /usr/local

    如果此系统将安装游戏:

    如果此系统将使用 GUI:

    如果此系统将使用 Docker(其自行管理数据集和快照):

    如果此系统将在 /var/mail 中存储本地电子邮件:

    如果此系统将使用 Snap 包:

    如果你在此系统上使用 /var/www

    对于 mirror 或 raidz 拓扑,为 /boot/grub 创建一个数据集:

    稍后建议使用 tmpfs,但如果你想为 /tmp 使用单独的数据集:

此数据集布局的主要目标是将操作系统与用户数据分离。这使得可以在不回滚用户数据的情况下回滚根文件系统。 如果你不进行任何额外操作,/tmp 将作为根文件系统的一部分进行存储。或者,你可以像上面所示那样为 /tmp 创建一个单独的数据集。这可以将 /tmp 数据排除在根文件系统的快照之外。同时,如果你希望限制其使用的最大空间,这也允许你在 rpool/tmp 上设置配额。否则,你也可以在之后使用 tmpfs(RAM 文件系统)。

注意:

如果你将启动所必需的目录(例如 /etc)拆分到单独的 dataset 中,必须将其添加到 /etc/default/zfs 中的 ZFS_INITRD_ADDITIONAL_DATASETS。具有 canmount=off 的数据集(例如上面的 rpool/usr)不受此影响。

  1. /run 挂载一个 tmpfs:

  1. 安装最小系统:

debootstrap 命令会使新系统处于未配置状态。使用 debootstrap 的另一种替代方案是将一个可正常工作的系统完整复制到新的 ZFS 根中。

  1. 复制 zpool.cache:

步骤 4:系统配置

  1. 配置主机名: 将 主机名 替换为所需的主机名:

    提示:

    如果觉得 vi 难用,可以使用 nano

  2. 配置网络接口: 查找接口名称:

    将下面的 NAME 调整为你的接口名称:

    如果系统不是 DHCP 客户端,请根据情况自定义此文件。

  3. 配置软件源:

  4. 将 LiveCD 环境中的虚拟文件系统绑定到新系统并 chroot 进入:

    注意:

    这里使用的是 --rbind,而不是 --bind

  5. 配置基本系统环境:

    即使你偏好非英文系统语言,也请确保 en_US.UTF-8 可用:

    安装你喜欢的文本编辑器:

    安装完整的 vim 软件包可以修复使用 vim-tiny(由 debootstrap 安装)在 SSH 下出现的终端问题。

  6. 仅针对 LUKS 安装,设置 /etc/crypttab

    使用 initramfscryptsetup 不支持 ZFS 的解决方法

    提示:

    如果你创建的是 mirror 或 raidz 拓扑,请为 luks2 等重复 /etc/crypttab 条目,并针对每个磁盘进行调整。

  7. 创建 EFI 文件系统:

    对于 UEFI 和传统(BIOS)启动均执行以下步骤:

    对于 mirror 或 raidz 拓扑,请对额外磁盘重复执行 mkdosfs,但不重复其他命令。

    注意:

    mkdosfs-s 1 仅在驱动器呈现 4 KiB 逻辑扇区(“4Kn”驱动器)时必需,以满足 FAT32 的最小簇大小(在 512 MiB 分区情况下)。对于呈现 512 B 扇区的驱动器,同样可以正常工作。

  8. /boot/grub 放到 EFI 系统分区:

    仅适用于单磁盘安装:

    这能让 GRUB 写入 /boot/grub(因为它位于 FAT 格式的 ESP 上,而非 ZFS),这意味着 /boot/grub/grubenvrecordfail 功能可以按预期工作:如果启动失败,下次启动时将显示通常隐藏的 GRUB 菜单。对于 mirror 或 raidz 拓扑,我们不希望 GRUB 写入 EFI 系统分区,因为在安装时我们会复制它,而当 GRUB 配置更改(如内核升级)时,没有机制更新这些副本。因此,对于 mirror 或 raidz 拓扑,我们将 /boot/grub 保留在启动池中,这样可以保持正确的镜像/raidz 行为,但无法写入 /boot/grub/grubenv,因此 recordfail 行为将失效。

  9. 在新系统的 chroot 环境中安装 GRUB/Linux/ZFS: 选择以下其中一种:

    • 为传统(BIOS)启动安装 GRUB/Linux/ZFS:

      使用空格键选择池中的所有磁盘(而非分区)。

    • 为 UEFI 启动安装 GRUB/Linux/ZFS:

注意:

  • 忽略任何提示 ERROR: Couldn't resolve deviceWARNING: Couldn't determine root device 的错误信息。cryptsetup 不支持 ZFS

  • 忽略任何提示 Module zfs not foundcouldn't connect to zsys daemon 的错误信息。第一个错误似乎是由于 Live CD 内核与 chroot 环境版本不匹配造成,但这无关紧要,因为模块已加载。第二个可能由第一个引起,但无论如何也无关紧要,因为 zed 后续会手动启动。

  • 对于 mirror 或 raidz 拓扑,此步骤仅在第一块磁盘上安装 GRUB。其他磁盘稍后处理。出于某种原因,grub-efi-amd64 在此不会提示 install_devices,但重启后会提示。

  1. 可选:卸载 os-prober:

这可以避免 update-grub 的错误信息。os-prober 仅在双系统中才需要。

  1. 设置 root 密码:

  1. 配置 swap: 如果需要 swap,请选择以下选项之一:

  • 对于未加密的单块磁盘安装:

  • 对于未加密的 mirror 或 raidz 拓扑:

  • 对于加密(LUKS 或 ZFS 原生加密)的单块磁盘安装:

  • 对于加密(LUKS 或 ZFS 原生加密)的 mirror 或 raidz 拓扑:

  1. 可选(但推荐):将 /tmp 挂载为 tmpfs

    如果你在上文已创建 /tmp dataset,请跳过此步骤,因为两者互斥。否则,可以通过启用 tmp.mount 单元将 /tmp 放在 tmpfs(RAM 文件系统)上。

  1. 设置系统组:

  1. 可选:安装 SSH:

步骤 5:GRUB 安装

  1. 验证 ZFS 启动文件系统是否被识别:

  1. 刷新 initrd 文件:

注意:

忽略提示 ERROR: Couldn't resolve deviceWARNING: Couldn't determine root device 的错误信息。cryptsetup 不支持 ZFS

  1. 禁用内存清零:

此操作用于解决 性能回退问题

  1. 可选(但强烈建议):简化 GRUB 调试:

系统重启两次并确认一切正常后,可按需撤销这些更改。

  1. 更新启动配置:

注意:

如果出现 osprober 报错,请忽略。

  1. 安装启动加载器:

    选择以下方案之一:

  • 对于传统(BIOS)启动,将 GRUB 安装到 MBR:

注意

你是在将 GRUB 安装到整个磁盘,而非分区。如果创建 mirror 或 raidz 拓扑,请对池中每块磁盘重复执行 grub-install 命令。

  • 对于 UEFI 启动,将 GRUB 安装到 ESP:

  1. 禁用 grub-initrd-fallback.service 对于 mirror 或 raidz 拓扑:

这是针对 /boot/grub/grubenv 的服务,在镜像或 raidz 拓扑中无法使用。禁用它可以避免在 /boot/grub 挂载失败时阻塞后续挂载。 另一种选择是通过 drop-in 单元设置 RequiresMountsFor=/boot/grub,但在此没有必要额外操作。希望 此 Bug 上游能修复。

  1. 修复文件系统挂载顺序: 需要激活 zfs-mount-generator,让 systemd 识别单独的挂载点,这对于 /var/log/var/tmp 等目录非常重要。rsyslog.service 会通过 local-fs.target 依赖 var-log.mount,使用 systemd PrivateTmp 功能的服务会自动依赖 After=var-tmp.mount

通过确保以下文件不为空来验证 zed 是否更新了缓存:

如果任一为空,强制更新缓存并再次检查:

如果仍为空,停止 zed(如下),重新启动 zed(如上),再尝试一次。

在文件有数据后,停止 zed

修正路径以去除 /mnt

  1. chroot 环境退出,返回 LiveCD 环境:

  1. 在 LiveCD 环境中执行以下命令卸载所有文件系统:

  1. 如果 export 因繁忙错误失败,尝试终止可能正在使用它的进程:

  1. 即使如此池仍然繁忙,启动时挂载会失败,此时需要在 initramfs 提示符下执行 zpool import -f rpool,然后 exit

  2. 重启:

等待新安装的系统正常启动,使用 root 登录。

步骤 6:首次启动

  1. 将 GRUB 安装到其他磁盘: 仅针对 UEFI mirror 或 raidz 拓扑:

  1. 创建用户账户: 将 你的用户名 替换为所需用户名:

步骤 7:安装完整软件

  1. 升级最小系统:

  1. 安装常用软件集:

    选择以下方案之一:

  • 仅安装命令行环境:

  • 安装完整 GUI 环境:

提示

如果安装完整 GUI 环境,你可能希望使用 NetworkManager 管理网络:

  1. 可选:禁用日志压缩 由于 /var/log 已经由 ZFS 压缩,logrotate 的压缩通常会浪费 CPU 和磁盘 I/O(在大多数情况下收益很小)。此外,如果你对 /var/log 进行快照,logrotate 的压缩反而会浪费空间,因为未压缩的数据仍会保留在快照中。可以手动编辑 /etc/logrotate.d 中的文件,将 compress 注释掉,或者使用以下循环(强烈建议复制粘贴):

  1. 重启:

步骤 8:最终清理

  1. 等待系统正常启动。使用你创建的账户登录。确保系统(包括网络)正常工作。

  2. 可选:禁用 root 密码:

  1. 可选(强烈推荐):禁用 root SSH 登录

    如果之前安装了 SSH,请撤销临时修改:

  1. 可选:重新启用图形化启动过程 如果你希望使用图形化启动,可以现在重新启用。如果使用 LUKS,这会让提示界面更美观。

注意:

如果出现 osprober 错误,请忽略。

  1. 可选:仅针对 LUKS 安装,备份 LUKS 头部:

将备份存放在安全位置(例如云存储)。它受 LUKS 密码保护,但你也可以使用额外加密。 提示: 如果创建了 mirror 或 raidz 拓扑,请对每个 LUKS 卷(如 luks2 等)重复此操作。

故障排除

使用 Live CD 进行救援

请参照 步骤 1:准备安装环境

对于 LUKS,首先解锁磁盘:

正确挂载所有文件系统:

如有需要,可 chroot 进入已安装的环境:

执行所需操作修复系统。

完成后清理:

Areca

需要 arcsas blob 驱动的系统应将其添加到 /etc/initramfs-tools/modules 文件中,并运行 update-initramfs -c -k all

如果内核日志中出现类似 RIP: 0010:[<ffffffff8101b316>] [<ffffffff8101b316>] native_read_tsc+0x6/0x20 的信息,请升级或降级 Areca 驱动。在出现此错误信息的系统上 ZoL 并不稳定。

MPT2SAS

本教程的大多数问题报告涉及 mpt2sas 硬件,这类硬件会进行较慢的异步磁盘初始化,例如某些 IBM M1015 或刷成参考 LSI 固件的 OEM 卡。

基本问题是这些控制器上的磁盘在常规系统启动之前对 Linux 内核不可见,而 ZoL 不支持热插拔池成员。详见 https://github.com/zfsonlinux/zfs/issues/330

大多数 LSI 卡与 ZoL 完全兼容。如果你的 LSI 卡存在此问题,尝试在 /etc/default/zfs 中设置 ZFS_INITRD_PRE_MOUNTROOT_SLEEP=X。系统将在导入池之前等待 X 秒,以确保所有磁盘都可用。

QEMU/KVM/XEN

使用 libvirt 或 qemu 为每个虚拟磁盘设置唯一序列号(例如:-drive if=none,id=disk1,file=disk1.qcow2,serial=1234567890)。

为了在虚拟机中使用 UEFI(而不仅限于 BIOS 启动),在宿主机上运行:

取消对以下行的注释:

VMware

  • 在 vmx 文件或 vSphere 配置中设置 disk.EnableUUID = "TRUE"。这样可以确保在虚拟机中创建别名 /dev/disk

最后更新于