构建以 ZFS 为根文件系统的 Ubuntu 20.04
有更新的发行版可用
新安装请参阅《Ubuntu 22.04 Root on ZFS》。本教程已不再接收大多数更新,仅保留,用于参考此前按本指南完成的现有安装。
勘误
如果你之前使用本指南进行了安装,请在适用的情况下应用以下修复:
/boot/grub 未挂载
/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/55 。底层的 zsys 问题:https://github.com/ubuntu/zsys/issues/164 已被修复并回移植到 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/efi2与ubuntu-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
此前的教程在 AccountsService(Accounts 为复数)上存在拼写错误,写成了 AccountServices(Services 为复数)。这会导致 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/ 。由于双向协作:https://ubuntu.com/blog/enhancing-our-zfs-support-on-ubuntu-19-10-an-introduction ,本教程生成的结果与 Ubuntu 安装器几乎完全相同。
如果你需要单磁盘、未加密的桌面安装,请使用安装器。相比手动完成所有步骤,这要容易且快速得多。
如果你需要 ZFS 原生加密的桌面安装,你可以对安装器进行简单修改:https://linsomniac.gitlab.io/post/2020-04-09-ubuntu-2004-encrypted-zfs/ 。其中的 -O recordsize=1M 与加密无关;除非你理解其含义,否则请省略。请务必使用至少 8 个字符的密码,否则该修改会导致安装器崩溃。此外,系统安装完成后,你应切换为加密的 swap:
希望安装器未来能获得加密支持:https://bugs.launchpad.net/ubuntu/+source/ubiquity/+bug/1857398 。
如果你需要设置 mirror 或 raidz 拓扑,使用 LUKS 加密,和/或安装服务器(无桌面 GUI),请使用本 HOWTO。
树莓派
如果你希望在树莓派上进行安装,请参阅 Ubuntu 20.04 Root on ZFS for Raspberry Pi。
注意事项
本教程使用整个物理磁盘。
不要将这些说明用于双启动。
请备份你的数据。任何现有数据都会被清除。
系统需求
Ubuntu 20.04.4(“Focal”)Desktop CD(不是任何 server 镜像)
安装到提供 4 KiB 逻辑扇区(“4Kn”)的磁盘上只能使用 UEFI 启动。这并非 ZFS 独有的问题。GRUB 在 4Kn 磁盘上无法也不会支持 legacy(BIOS)启动。
在内存少于 2 GiB 的计算机上运行 ZFS 存在严重瓶颈。对于基本工作负载,建议使用 4 GiB 内存以获得正常性能。如果你希望使用去重(deduplication),则需要 大量的内存。启用去重是一项永久性更改,无法轻易撤销。
加密
本指南支持三种加密方案:未加密、ZFS 原生加密以及 LUKS。无论选择哪种方式,所有 ZFS 功能都可完全使用。
未加密选项当然不会对任何数据进行加密。由于没有加密开销,这种方式的性能自然最佳。
ZFS 原生加密会加密根池中的数据及大部分元数据,但不会加密数据集或快照的名称及属性。启动池(bpool)不会加密,它只包含引导加载程序、内核和 initrd。(除非你在 /etc/fstab 中设置了密码,否则 initrd 通常不包含敏感数据。)系统在启动时必须在控制台输入口令才能启动。性能表现良好。由于加密在 ZFS 内部进行,即使使用多块磁盘(镜像或 raidz 拓扑),数据只需加密一次。
LUKS 会加密几乎所有内容。唯一未加密的数据是引导加载程序、内核和 initrd。系统在启动时必须在控制台输入口令才能启动。性能良好,但 LUKS 位于 ZFS 之下,因此如果使用多块磁盘(镜像或 raidz 拓扑),数据在每块磁盘上都必须加密一次。
第 1 步:准备安装环境
启动 Ubuntu Live CD,选择“尝试 Ubuntu”。根据需要将系统连接到网络(例如连接 WiFi)。打开终端(按 Ctrl-Alt-T)。
设置并更新软件源:
可选:在 Live CD 环境中安装并启动 OpenSSH 服务器: 如果你有第二台系统,通过 SSH 访问目标系统会很方便:
安装完整的
vim包可以解决通过 SSH 使用 Live CD 环境自带的vim-tiny时可能出现的终端问题。提示:
你可以通过
ip addr show scope global | grep inet查看 IP 地址,然后从主机通过ssh ubuntu@IP连接。禁用自动挂载:
如果磁盘以前被使用过(分区位置相同),之前的文件系统(例如 ESP)可能会自动挂载,如果不禁用可能会干扰安装:
切换为 root 用户:
在 Live CD 环境中安装 ZFS:
第 2 步:磁盘格式化
设置磁盘变量:
使用 ZFS 时,始终使用长格式别名
/dev/disk/by-id/*。直接使用设备节点/dev/sd*可能会导致导入失败,尤其是在系统存在多个存储池时。提示:
ls -la /dev/disk/by-id可以列出别名。你是在虚拟机中操作吗?如果虚拟磁盘在
/dev/disk/by-id中缺失,KVM+virtio 下可以使用/dev/vda;否则请参考 故障排查 部分。如果使用镜像(mirror)或 RAIDZ 拓扑,请使用
DISK1、DISK2等变量。在选择 boot 存储池大小时,要考虑使用情况。内核和 initrd 可能占用约 100M。如果保留多个内核并进行快照,boot 存储池空间可能不足,尤其是在需要重新生成 initramfs(每个约 85M)时。请根据实际需求为 boot pool 分配合适空间。
如果你要重复使用磁盘,请根据需要清理它: 确保 swap 分区未被使用:
如果磁盘以前用于 MD 阵列:
清除分区表:
如果出现内核仍在使用旧分区表的提示,可以请求内核重新加载分区信息:
如果新分区仍未显示,可以重启并重新开始(除了这一步可以跳过)。
创建引导分区:
注意:
虽然 Ubuntu 安装程序在传统(BIOS)启动时使用 MBR 标签,本教程对 UEFI 和传统(BIOS)启动都使用 GPT 分区标签。这比提供两个选项更简单,同时也提供向前兼容性(面向未来)。换句话说,对于传统(BIOS)启动,这将允许你将磁盘移动到未来的新系统/主板,而无需重建 pool(或从备份恢复数据)。ESP 在两种情况下都会创建,原因类似。此外,在单盘安装中,ESP 被用于
/boot/grub,如下文讨论。
创建 swap 分区: 早期版本的教程将 swap 放在 zvol 上。Ubuntu 不推荐这种配置,因为可能导致死锁,上游也有相关 bug 报告。 将 swap 放在分区上意味着放弃 ZFS 校验和对 swap 的保护,但考虑到 ZFS 与 swap 可能出现的死锁,这是合理的权衡。如果你对此不介意,可以选择不启用 swap。
根据需要选择一种方案:
单盘安装:
镜像或 raidz 拓扑:
根据需要调整 swap 大小。如果你希望启用休眠(仅适用于未加密安装),swap 分区必须至少与系统内存大小相同。
创建 boot 存储池分区:
Ubuntu 安装程序使用磁盘空间的 5%,最小 500 MiB,最大 2 GiB。分配过小(500 MiB 可能太小)可能导致无法升级内核。
创建 root pool 分区: 选择以下方案之一:
未加密或 ZFS 原生加密:
LUKS:
如果你要创建镜像或 raidz 拓扑,请对所有将参与存储池的磁盘重复上述分区命令。
创建 boot 存储池:
通常无需为 boot 存储池自定义选项。
GRUB 并不支持所有 zpool 特性。参考 spa_feature_names 在 grub-core/fs/zfs/zfs.c。此步骤创建一个独立的 boot 存储池 /boot,其特性限制为 GRUB 支持的功能,从而能让 root 存储池使用任意特性。GRUB 以只读方式打开存储池,因此所有只读兼容特性都被视为“受支持”。
提示:
若创建镜像(mirror)拓扑,使用:
对于 raidz 拓扑,将上面命令中的
mirror替换为raidz、raidz2或raidz3,并列出额外磁盘的分区。boot pool 的名称不再是任意的。必须为
bpool。如果真的想重命名,请在 GRUB 安装后编辑/etc/grub.d/10_linux_zfs并运行update-grub。
功能说明:
allocation_classes功能理论上可安全使用,但除非真的使用(例如specialvdev),否则没有必要启用。对于 boot pool,极少有人会用到此功能。如果想加快 boot pool 速度,将整个 pool 放在更快的磁盘上比作为specialvdev 更合理。project_quota功能已测试,可安全使用。对于 boot pool,几乎不会有影响。resilver_defer功能理论上安全,但由于 boot pool 足够小,通常无需使用。spacemap_v2功能已测试,可安全使用。boot pool 较小,因此实际影响不大。作为只读兼容特性,
userobj_accounting理论上兼容,但在实践中,GRUB 可能报“invalid dnode type”错误。无论如何,这个功能对/boot并不重要。
创建 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 依赖 ACL。禁用 ACL 会破坏 NFSv4 的 umask 处理 相关讨论。normalization=formD消除了 UTF-8 文件名的一些极端情况,并隐含utf8only=on,意味着只允许 UTF-8 文件名。如果希望支持非 UTF-8 文件名,请不要使用该选项。关于 UTF-8 强制使用的问题,参见 The problems with enforced UTF-8 only filenames。relatime=on是atime和atime=off之间的折中方案。Linux 2.6.30 之后,其他文件系统默认即为relatime。RedHat 文档 说明详情。xattr=sa显著提升扩展属性性能。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 节 获取建议。
提示:
如果创建镜像(mirror)拓扑,可使用如下命令创建 root pool:
对于 raidz 拓扑,将上例中的
mirror替换为raidz、raidz2或raidz3,并列出所有参与的磁盘分区。使用 LUKS 时,如果是镜像或 raidz 拓扑,需要使用
/dev/mapper/luks1、/dev/mapper/luks2等路径,这些需先用cryptsetup创建。名称可以自定义存储池名,但一旦修改,新名称必须在整个系统中保持一致。在支持自动安装到 ZFS 的系统中,默认的 root 存储池名称为
rpool。
第 3 步:系统安装
创建作为容器的文件系统数据集:
为 root 和 boot 文件系统创建文件系统数据集:
创建其他数据集:
对于镜像或 raidz 拓扑,为
/boot/grub创建 dataset:在
/run挂载 tmpfs:如果希望
/tmp拥有单独的 dataset:该布局的主要目标是将操作系统与用户数据分离。这样可以在不影响用户数据的情况下回滚 root 文件系统。如果不额外操作,
/tmp将存储在 root 文件系统中。也可创建独立 dataset,这样/tmp数据不会被 root 文件系统的快照包含,并且可以对rpool/tmp设置配额限制最大使用空间。否则,也可以 later 使用 tmpfs(RAM 文件系统)。安装最小系统:
debootstrap会生成一个未配置的系统。另一种方法是将一个已工作的系统整体复制到新的 ZFS root。复制
zpool.cache:
第 4 步:系统配置
配置主机名: 将
主机名替换为所需主机名:添加一行:
或者如果系统在 DNS 中有真实域名:
提示:
如果不熟悉
vi,可以使用nano。配置网络接口: 查找接口名称:
将下面
NAME替换为你的接口名:内容示例:
如果系统不是 DHCP 客户端,请根据实际情况修改此文件。
配置软件源:
添加以下内容:
将 LiveCD 环境的虚拟文件系统绑定到新系统并
chroot:注意: 这里使用
--rbind而不是--bind。配置基本系统环境:
即使偏好非英语系统语言,也确保
en_US.UTF-8可用:安装文本编辑器:
安装完整
vim包可以解决通过 SSH 使用vim-tiny时的终端问题。仅对 LUKS 安装,设置
/etc/crypttab:initramfs是为了解决 cryptsetup 不支持 ZFS 的问题。 提示: 如果是镜像或 raidz 拓扑,请为luks2等磁盘重复添加/etc/crypttab条目。创建 EFI 文件系统(适用于 UEFI 和 legacy BIOS 启动):
对于镜像或 raidz 拓扑,为其他磁盘重复
mkdosfs,但不要重复其它命令。 注意:-s 1仅对 4Kn 盘需要,以满足 FAT32 最小簇大小要求。512B 盘可正常工作。将
/boot/grub放到 EFI 系统分区(仅单盘安装):这样 GRUB 可以写入
/boot/grub,使/boot/grub/grubenv和recordfail功能正常。对于镜像或 raidz 拓扑,/boot/grub保留在 boot pool,以保证镜像/raidz 行为正确,但无法写入grubenv。在 chroot 环境安装 GRUB/Linux/ZFS:
legacy BIOS 启动:
使用空格键选择池中的所有磁盘(非分区)。
UEFI 启动:
注意:
忽略
ERROR: Couldn't resolve device和WARNING: Couldn't determine root device错误。原因同上。忽略
Module zfs not found和couldn't connect to zsys daemon错误。镜像或 raidz 拓扑,此步骤只在第一块磁盘安装 GRUB,其余磁盘稍后处理。
可选:移除 os-prober:
避免
update-grub错误。os-prober 仅在双系统中必要。设置 root 密码:
配置 swap: 选择对应情况执行:
单盘未加密:
镜像/raidz 未加密:
单盘加密(LUKS 或 ZFS 原生加密):
镜像/raidz 加密:
可选(推荐):将
/tmp挂载到 tmpfs 如果之前创建了/tmpdataset,则跳过。否则:配置系统用户组:
修复依赖循环(针对 ZFS 原生加密或 LUKS):
忽略 Hunk #2 的失败(回答
n两次)。来源:Bug #1875577 Encrypted swap won’t load on 20.04 with zfs root。
可选:安装 SSH:
第 5 步:GRUB 安装
验证 ZFS 启动文件系统是否被识别:
刷新 initrd 文件:
注意:
忽略
ERROR: Couldn't resolve device和WARNING: Couldn't determine root device错误。cryptsetup 不支持 ZFS。禁用内存清零:
此操作用于解决 性能回退问题。
可选(强烈推荐):便于调试 GRUB:
系统重启两次确认正常后,可以恢复这些修改。
更新启动配置:
注意: 如出现 os-prober 错误,可忽略。
安装引导加载程序:
legacy BIOS 启动,将 GRUB 安装到 MBR:
注意是安装到整块磁盘,而非分区。若为镜像或 raidz 拓扑,请对池中的每块磁盘重复执行。
UEFI 启动,将 GRUB 安装到 ESP:
禁用 grub-initrd-fallback.service(镜像或 raidz 拓扑):
该服务用于
/boot/grub/grubenv,在镜像或 raidz 中不工作。禁用可防止挂载/boot/grub失败时阻塞。 另一种方法是通过 drop-in 单元设置RequiresMountsFor=/boot/grub,但这里不必额外操作。相关 Bug修复文件系统挂载顺序: 激活
zfs-mount-generator,使 systemd 识别独立挂载点,这对于/var/log和/var/tmp等很重要。rsyslog.service依赖var-log.mount,使用PrivateTmp的服务自动依赖var-tmp.mount。验证缓存已更新:
如果为空,强制更新:
若仍为空,停止 zed(
fg后 Ctrl-C),重新启动 zed 再试。数据写入后,停止
zed:修正路径,去掉
/mnt:退出
chroot回到 LiveCD 环境:在 LiveCD 环境卸载所有文件系统:
重启系统:
等待新系统正常启动并以 root 登录。
第 6 步:首次启动
将 GRUB 安装到附加磁盘:
仅适用于 UEFI 镜像或 raidz 拓扑:
创建用户账户: 将
你的用户名替换为你希望使用的用户名:
第 7 步:完整软件安装
升级最小系统:
安装常规软件集:
选择以下方案之一:
仅安装命令行环境:
安装完整 GUI 环境:
提示:
如果安装完整 GUI 环境,通常希望使用 NetworkManager 管理网络:
可选:禁用日志压缩
由于
/var/log已被 ZFS 压缩,logrotate 的压缩会消耗 CPU 和磁盘 I/O,但收益通常很小。此外,如果你对/var/log做快照,logrotate 的压缩反而浪费空间,因为未压缩的数据仍会存在于快照中。可以手动编辑/etc/logrotate.d下的文件,将compress注释掉,或使用以下循环(强烈建议复制粘贴):重启:
第 8 步:最终清理
等待系统正常启动。使用你创建的账户登录,确保系统(包括网络)运行正常。
可选:禁用 root 密码:
可选(强烈建议):禁用 root SSH 登录
如果之前安装过 SSH,撤销临时更改:
可选:重新启用图形化启动
如果你希望使用图形化启动,现在可以重新启用。使用 LUKS 时,提示界面会更美观。
注意: 如出现
osprober错误,可忽略。可选:仅针对 LUKS 安装,备份 LUKS 头:
将备份存放在安全位置(例如云存储)。它受 LUKS 密码保护,但你也可以使用额外加密。
提示:
如果创建了镜像或 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。
最后更新于