github编辑

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

有更新的发行版可用

勘误

如果你之前使用本指南进行了安装,请在适用的情况下应用以下修复:

/boot/grub 未挂载

严重程度: 普通(此前为严重)

修复时间: 2020 - 12 - 05(此前为 2020 - 05 - 30)

对于 mirror 或 raidz 拓扑,/boot/grub 位于单独的数据集上。最初为 bpool/grub,随后在 2020 - 05 - 30 更改为 bpool/BOOT/ubuntu_UUID/grub,以规避 zsys 将 canmount=off 导致 /boot/grub 无法挂载的问题。该变通方案又引发了快照恢复相关问题:https://github.com/openzfs/openzfs-docs/issues/55arrow-up-right 。底层的 zsys 问题:https://github.com/ubuntu/zsys/issues/164arrow-up-right 已被修复并回移植到 20.04,因此现在又恢复为 bpool/grub

  • 如果你从未应用过 2020 - 05 - 30 的勘误修复,那么 /boot/grub 很可能没有挂载。请检查:

    mount | grep /boot/grub

    如果已挂载,则一切正常,停止即可。否则执行:

    zfs set canmount=on bpool/grub
    update-initramfs -c -k all
    update-grub
    
    grub-install --target=x86_64-efi --efi-directory=/boot/efi \
        --bootloader-id=ubuntu --recheck --no-floppy

    对于额外磁盘,请重复以下操作,将“2”递增为“3”等,对 /boot/efi2ubuntu-2 同步递增:

    cp -a /boot/efi/EFI /boot/efi2
    grub-install --target=x86_64-efi --efi-directory=/boot/efi2 \
        --bootloader-id=ubuntu-2 --recheck --no-floppy

    检查以下文件是否包含 set prefix=($root)'/grub@'

    grep prefix= \
        /boot/efi/EFI/ubuntu/grub.cfg \
        /boot/efi2/EFI/ubuntu-2/grub.cfg
  • 如果你已经应用过 2020 - 05 - 30 的勘误修复,则需要回退数据集重命名:

    umount /boot/grub
    zfs rename bpool/BOOT/ubuntu_UUID/grub bpool/grub
    zfs set com.ubuntu.zsys:bootfs=no bpool/grub
    zfs mount bpool/grub

AccountsService 未挂载

严重程度: 普通

修复时间: 2020 - 05 - 28

此前的教程在 AccountsServiceAccounts 为复数)上存在拼写错误,写成了 AccountServicesServices 为复数)。这会导致 AccountsService 数据被写入根文件系统。仅在回滚根文件系统而未同时回滚用户数据时才会造成问题。请检查:

如果在“Accounts”上有“s” ,则正常;如果“s”在“Services”上,则按如下修复:

概述

Ubuntu 安装器

Ubuntu 安装器已经支持 root-on-ZFS:https://arstechnica.com/gadgets/2020/03/ubuntu-20-04s-zsys-adds-zfs-snapshots-to-package-management/arrow-up-right 。由于双向协作:https://ubuntu.com/blog/enhancing-our-zfs-support-on-ubuntu-19-10-an-introductionarrow-up-right ,本教程生成的结果与 Ubuntu 安装器几乎完全相同。

如果你需要单磁盘、未加密的桌面安装,请使用安装器。相比手动完成所有步骤,这要容易且快速得多。

如果你需要 ZFS 原生加密的桌面安装,你可以对安装器进行简单修改:https://linsomniac.gitlab.io/post/2020-04-09-ubuntu-2004-encrypted-zfs/arrow-up-right 。其中的 -O recordsize=1M 与加密无关;除非你理解其含义,否则请省略。请务必使用至少 8 个字符的密码,否则该修改会导致安装器崩溃。此外,系统安装完成后,你应切换为加密的 swap:

希望安装器未来能获得加密支持:https://bugs.launchpad.net/ubuntu/+source/ubiquity/+bug/1857398arrow-up-right

如果你需要设置 mirror 或 raidz 拓扑,使用 LUKS 加密,和/或安装服务器(无桌面 GUI),请使用本 HOWTO。

树莓派

如果你希望在树莓派上进行安装,请参阅 Ubuntu 20.04 Root on ZFS for Raspberry Piarrow-up-right

注意事项

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

  • 不要将这些说明用于双启动。

  • 请备份你的数据。任何现有数据都会被清除。

系统需求

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

加密

本指南支持三种加密方案:未加密、ZFS 原生加密以及 LUKS。无论选择哪种方式,所有 ZFS 功能都可完全使用。

未加密选项当然不会对任何数据进行加密。由于没有加密开销,这种方式的性能自然最佳。

ZFS 原生加密会加密根池中的数据及大部分元数据,但不会加密数据集或快照的名称及属性。启动池(bpool)不会加密,它只包含引导加载程序、内核和 initrd。(除非你在 /etc/fstab 中设置了密码,否则 initrd 通常不包含敏感数据。)系统在启动时必须在控制台输入口令才能启动。性能表现良好。由于加密在 ZFS 内部进行,即使使用多块磁盘(镜像或 raidz 拓扑),数据只需加密一次。

LUKS 会加密几乎所有内容。唯一未加密的数据是引导加载程序、内核和 initrd。系统在启动时必须在控制台输入口令才能启动。性能良好,但 LUKS 位于 ZFS 之下,因此如果使用多块磁盘(镜像或 raidz 拓扑),数据在每块磁盘上都必须加密一次。

第 1 步:准备安装环境

  1. 启动 Ubuntu Live CD,选择“尝试 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;否则请参考 故障排查arrow-up-right 部分。

    • 如果使用镜像(mirror)或 RAIDZ 拓扑,请使用 DISK1DISK2 等变量。

    • 在选择 boot 存储池大小时,要考虑使用情况。内核和 initrd 可能占用约 100M。如果保留多个内核并进行快照,boot 存储池空间可能不足,尤其是在需要重新生成 initramfs(每个约 85M)时。请根据实际需求为 boot pool 分配合适空间。

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

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

清除分区表:

如果出现内核仍在使用旧分区表的提示,可以请求内核重新加载分区信息:

如果新分区仍未显示,可以重启并重新开始(除了这一步可以跳过)。

  1. 创建引导分区:

注意:

虽然 Ubuntu 安装程序在传统(BIOS)启动时使用 MBR 标签,本教程对 UEFI 和传统(BIOS)启动都使用 GPT 分区标签。这比提供两个选项更简单,同时也提供向前兼容性(面向未来)。换句话说,对于传统(BIOS)启动,这将允许你将磁盘移动到未来的新系统/主板,而无需重建 pool(或从备份恢复数据)。ESP 在两种情况下都会创建,原因类似。此外,在单盘安装中,ESP 被用于 /boot/grub,如下文讨论arrow-up-right

  1. 创建 swap 分区: 早期版本的教程将 swap 放在 zvol 上。Ubuntu 不推荐这种配置,因为可能导致死锁arrow-up-right,上游也有相关 bug 报告arrow-up-right。 将 swap 放在分区上意味着放弃 ZFS 校验和对 swap 的保护,但考虑到 ZFS 与 swap 可能出现的死锁,这是合理的权衡。如果你对此不介意,可以选择不启用 swap。

根据需要选择一种方案:

  • 单盘安装:

  • 镜像或 raidz 拓扑:

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

  1. 创建 boot 存储池分区:

Ubuntu 安装程序使用磁盘空间的 5%,最小 500 MiB,最大 2 GiB。分配过小(500 MiB 可能太小)可能导致无法升级内核arrow-up-right

  1. 创建 root pool 分区: 选择以下方案之一:

  • 未加密或 ZFS 原生加密:

  • LUKS:

如果你要创建镜像或 raidz 拓扑,请对所有将参与存储池的磁盘重复上述分区命令。

  1. 创建 boot 存储池:

通常无需为 boot 存储池自定义选项。

GRUB 并不支持所有 zpool 特性。参考 spa_feature_namesgrub-core/fs/zfs/zfs.carrow-up-right。此步骤创建一个独立的 boot 存储池 /boot,其特性限制为 GRUB 支持的功能,从而能让 root 存储池使用任意特性。GRUB 以只读方式打开存储池,因此所有只读兼容特性都被视为“受支持”。

提示:

  • 若创建镜像(mirror)拓扑,使用:

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

  • boot pool 的名称不再是任意的。必须bpool。如果真的想重命名,请在 GRUB 安装后编辑 /etc/grub.d/10_linux_zfs 并运行 update-grub

功能说明:

  • allocation_classes 功能理论上可安全使用,但除非真的使用(例如 special vdev),否则没有必要启用。对于 boot pool,极少有人会用到此功能。如果想加快 boot pool 速度,将整个 pool 放在更快的磁盘上比作为 special vdev 更合理。

  • project_quota 功能已测试,可安全使用。对于 boot pool,几乎不会有影响。

  • resilver_defer 功能理论上安全,但由于 boot pool 足够小,通常无需使用。

  • spacemap_v2 功能已测试,可安全使用。boot pool 较小,因此实际影响不大。

  • 作为只读兼容特性,userobj_accounting 理论上兼容,但在实践中,GRUB 可能报“invalid dnode type”错误。无论如何,这个功能对 /boot 并不重要。

  1. 创建 root 存储池:

选择以下一种方式:

  • 未加密:

  • ZFS 原生加密:

  • LUKS 加密:

说明:

  • 建议使用 ashift=12,因为许多硬盘虽然显示 512 B 逻辑扇区,但物理扇区为 4 KiB 或更大。未来更换硬盘时,如果物理扇区为 4 KiB(推荐 ashift=12)或逻辑扇区为 4 KiB(必须 ashift=12),都会受益。

  • -O acltype=posixacl 全局启用 POSIX ACL。如果不想全局启用,可移除此选项,但在创建 /var/log 时仍需添加 -o acltype=posixacl(小写“o”),因为 journald 依赖 ACLarrow-up-right。禁用 ACL 会破坏 NFSv4 的 umask 处理 相关讨论arrow-up-right

  • normalization=formD 消除了 UTF-8 文件名的一些极端情况,并隐含 utf8only=on,意味着只允许 UTF-8 文件名。如果希望支持非 UTF-8 文件名,请不要使用该选项。关于 UTF-8 强制使用的问题,参见 The problems with enforced UTF-8 only filenamesarrow-up-right

  • recordsize 保持默认 128 KiB。如果希望调整(例如 -O recordsize=1M),可参考 文章 1arrow-up-right 文章 2arrow-up-right 文章 3arrow-up-right 文章 4arrow-up-right

  • relatime=onatimeatime=off 之间的折中方案。Linux 2.6.30 之后,其他文件系统默认即为 relatimeRedHat 文档arrow-up-right 说明详情。

  • xattr=sa 显著提升扩展属性性能arrow-up-right。ZFS 内部使用扩展属性实现 POSIX ACL,用户空间应用也可能使用(部分 GUI 应用、Samba 存储 Windows ACL/DOS 属性、Samba AD DC 均依赖)。xattr=sa 为 Linux 特有,如果迁移到其他 OpenZFS 实现,扩展属性不可读,但数据仍可访问。如果对扩展属性可移植性有要求,可省略 -O xattr=sa,即使不全局使用,也推荐用于 /var/log

  • 必须在设备路径中包含 -part4,否则 ZFS 会误认为整个磁盘,可能重建分区并覆盖 bootloader 分区。

  • ZFS 原生加密默认使用 aes-256-ccm,但上游已改为 aes-256-gcm,性能更好且未来更快。

  • LUKS 密钥长度为 512 位,但 XTS 模式需要两个密钥,因此实际为 AES-256。

  • 密码可能是最弱环节,请谨慎选择。参见 cryptsetup FAQ 第 5 节arrow-up-right 获取建议。

提示:

  • 如果创建镜像(mirror)拓扑,可使用如下命令创建 root pool:

  • 对于 raidz 拓扑,将上例中的 mirror 替换为 raidzraidz2raidz3,并列出所有参与的磁盘分区。

  • 使用 LUKS 时,如果是镜像或 raidz 拓扑,需要使用 /dev/mapper/luks1/dev/mapper/luks2 等路径,这些需先用 cryptsetup 创建。

  • 名称可以自定义存储池名,但一旦修改,新名称必须在整个系统中保持一致。在支持自动安装到 ZFS 的系统中,默认的 root 存储池名称为 rpool

第 3 步:系统安装

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

  2. 为 root 和 boot 文件系统创建文件系统数据集:

  3. 创建其他数据集:

    对于镜像或 raidz 拓扑,为 /boot/grub 创建 dataset:

    /run 挂载 tmpfs:

    如果希望 /tmp 拥有单独的 dataset:

    该布局的主要目标是将操作系统与用户数据分离。这样可以在不影响用户数据的情况下回滚 root 文件系统。如果不额外操作,/tmp 将存储在 root 文件系统中。也可创建独立 dataset,这样 /tmp 数据不会被 root 文件系统的快照包含,并且可以对 rpool/tmp 设置配额限制最大使用空间。否则,也可以 later 使用 tmpfs(RAM 文件系统)。

  4. 安装最小系统:

    debootstrap 会生成一个未配置的系统。另一种方法是将一个已工作的系统整体复制到新的 ZFS root。

  5. 复制 zpool.cache

第 4 步:系统配置

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

    添加一行:

    或者如果系统在 DNS 中有真实域名:

    提示:

    如果不熟悉 vi,可以使用 nano

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

    将下面 NAME 替换为你的接口名:

    内容示例:

    如果系统不是 DHCP 客户端,请根据实际情况修改此文件。

  3. 配置软件源:

    添加以下内容:

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

    注意: 这里使用 --rbind 而不是 --bind

  5. 配置基本系统环境:

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

    安装文本编辑器:

    安装完整 vim 包可以解决通过 SSH 使用 vim-tiny 时的终端问题。

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

    initramfs 是为了解决 cryptsetup 不支持 ZFS 的问题arrow-up-right提示: 如果是镜像或 raidz 拓扑,请为 luks2 等磁盘重复添加 /etc/crypttab 条目。

  7. 创建 EFI 文件系统(适用于 UEFI 和 legacy BIOS 启动):

    对于镜像或 raidz 拓扑,为其他磁盘重复 mkdosfs,但不要重复其它命令。 注意: -s 1 仅对 4Kn 盘需要,以满足 FAT32 最小簇大小要求。512B 盘可正常工作。

  8. /boot/grub 放到 EFI 系统分区(仅单盘安装):

    这样 GRUB 可以写入 /boot/grub,使 /boot/grub/grubenvrecordfail 功能正常。对于镜像或 raidz 拓扑,/boot/grub 保留在 boot pool,以保证镜像/raidz 行为正确,但无法写入 grubenv

  9. 在 chroot 环境安装 GRUB/Linux/ZFS:

    • legacy BIOS 启动:

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

    • UEFI 启动:

    注意:

    • 忽略 ERROR: Couldn't resolve deviceWARNING: Couldn't determine root device 错误。原因同上。

    • 忽略 Module zfs not foundcouldn't connect to zsys daemon 错误。

    • 镜像或 raidz 拓扑,此步骤只在第一块磁盘安装 GRUB,其余磁盘稍后处理。

  10. 可选:移除 os-prober:

    避免 update-grub 错误。os-prober 仅在双系统中必要。

  11. 设置 root 密码:

  12. 配置 swap: 选择对应情况执行:

    • 单盘未加密:

    • 镜像/raidz 未加密:

    • 单盘加密(LUKS 或 ZFS 原生加密):

    • 镜像/raidz 加密:

  13. 可选(推荐):将 /tmp 挂载到 tmpfs 如果之前创建了 /tmp dataset,则跳过。否则:

  14. 配置系统用户组:

  15. 修复依赖循环(针对 ZFS 原生加密或 LUKS):

    忽略 Hunk #2 的失败(回答 n 两次)。

    来源:Bug #1875577 Encrypted swap won’t load on 20.04 with zfs rootarrow-up-right

  16. 可选:安装 SSH:

第 5 步:GRUB 安装

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

  2. 刷新 initrd 文件:

    注意:

    忽略 ERROR: Couldn't resolve deviceWARNING: Couldn't determine root device 错误。cryptsetup 不支持 ZFSarrow-up-right

  3. 禁用内存清零:

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

  4. 可选(强烈推荐):便于调试 GRUB:

    系统重启两次确认正常后,可以恢复这些修改。

  5. 更新启动配置:

    注意: 如出现 os-prober 错误,可忽略。

  6. 安装引导加载程序:

    • legacy BIOS 启动,将 GRUB 安装到 MBR:

      注意是安装到整块磁盘,而非分区。若为镜像或 raidz 拓扑,请对池中的每块磁盘重复执行。

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

  7. 禁用 grub-initrd-fallback.service(镜像或 raidz 拓扑):

    该服务用于 /boot/grub/grubenv,在镜像或 raidz 中不工作。禁用可防止挂载 /boot/grub 失败时阻塞。 另一种方法是通过 drop-in 单元设置 RequiresMountsFor=/boot/grub,但这里不必额外操作。相关 Bugarrow-up-right

  8. 修复文件系统挂载顺序: 激活 zfs-mount-generator,使 systemd 识别独立挂载点,这对于 /var/log/var/tmp 等很重要。rsyslog.service 依赖 var-log.mount,使用 PrivateTmp 的服务自动依赖 var-tmp.mount

    验证缓存已更新:

    如果为空,强制更新:

    若仍为空,停止 zed(fg 后 Ctrl-C),重新启动 zed 再试。

    数据写入后,停止 zed

    修正路径,去掉 /mnt

  9. 退出 chroot 回到 LiveCD 环境:

  10. 在 LiveCD 环境卸载所有文件系统:

  11. 重启系统:

    等待新系统正常启动并以 root 登录。

第 6 步:首次启动

  1. 将 GRUB 安装到附加磁盘:

    仅适用于 UEFI 镜像或 raidz 拓扑:

  2. 创建用户账户: 将 你的用户名 替换为你希望使用的用户名:

第 7 步:完整软件安装

  1. 升级最小系统:

  2. 安装常规软件集:

    选择以下方案之一:

    • 仅安装命令行环境:

    • 安装完整 GUI 环境:

      提示

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

  3. 可选:禁用日志压缩

    由于 /var/log 已被 ZFS 压缩,logrotate 的压缩会消耗 CPU 和磁盘 I/O,但收益通常很小。此外,如果你对 /var/log 做快照,logrotate 的压缩反而浪费空间,因为未压缩的数据仍会存在于快照中。可以手动编辑 /etc/logrotate.d 下的文件,将 compress 注释掉,或使用以下循环(强烈建议复制粘贴):

  4. 重启:

第 8 步:最终清理

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

  2. 可选:禁用 root 密码:

  3. 可选(强烈建议):禁用 root SSH 登录

    如果之前安装过 SSH,撤销临时更改:

  4. 可选:重新启用图形化启动

    如果你希望使用图形化启动,现在可以重新启用。使用 LUKS 时,提示界面会更美观。

    注意: 如出现 osprober 错误,可忽略。

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

    将备份存放在安全位置(例如云存储)。它受 LUKS 密码保护,但你也可以使用额外加密。

    提示:

    如果创建了镜像或 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

最后更新于