github编辑

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

概述

Ubuntu 安装器

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

树莓派

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

注意事项

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

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

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

系统需求

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

加密

本指南支持三种不同的加密方案:未加密、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;否则请阅读 troubleshootingarrow-up-right 部分。

    • 对于镜像或 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,如下文所述arrow-up-right

  1. 创建 swap 分区

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

  • 单块磁盘安装:

  • 镜像或 raidz 拓扑:

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

  1. 创建 boot 池分区:

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

  1. 创建根池分区:

请选择以下方案之一:

  • 未加密或 ZFS 原生加密:

  • LUKS:

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

  1. 创建 boot 池:

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

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

GRUB 并不支持所有 zpool 特性。请参阅 grub-core/fs/zfs/zfs.carrow-up-right 中的 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:

备注:

提示:

  • 如果你要创建 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 的解决方法arrow-up-right

    提示:

    如果你创建的是 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 不支持 ZFSarrow-up-right

  • 忽略任何提示 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 不支持 ZFSarrow-up-right

  1. 禁用内存清零:

此操作用于解决 性能回退问题arrow-up-right

  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,但在此没有必要额外操作。希望 此 Bugarrow-up-right 上游能修复。

  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:准备安装环境arrow-up-right

对于 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/330arrow-up-right

大多数 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

最后更新于