构建以 ZFS 为根文件系统的 Ubuntu 22.04
概述
Ubuntu 安装器
Ubuntu 安装器仍然支持 ZFS,但在 22.04 中它被几近删除(它在 22.04 中被几近删除),并且它已经不再安装 zsys(它不再安装 zsys)。目前,这份教程仍然使用 zsys,但在不久的将来很可能会被移除。
树莓派
如果你希望在树莓派上安装,请参阅 Raspberry Pi 的 Ubuntu 22.04 Root on ZFS。
注意事项
本教程使用整个物理磁盘。
不要将这些说明用于双系统。
备份你的数据。一切既有数据都将丢失。
系统需求
Ubuntu 22.04.1(“jammy”)Desktop CD(不是任何 server 镜像)
安装在以 4 KiB 逻辑扇区呈现的驱动器(“4Kn”驱动器)上,仅在 UEFI 启动下可用。这并非 ZFS 特有问题。GRUB 在传统(BIOS)启动下无法也不会支持 4Kn。
在内存少于 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:准备安装环境
启动 Ubuntu Live CD。在 GRUB 启动菜单中,选择 Try or Install Ubuntu。在 Welcome 页面,选择你偏好的语言并选择 Try 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;否则请阅读 troubleshooting 部分。对于镜像或 raidz 拓扑,请使用
DISK1、DISK2等。在选择 boot 池大小时,请考虑你将如何使用这些空间。一个内核和 initrd 可能会占用大约 100M。如果你有多个内核并进行快照,boot 池空间可能会不足,尤其是在需要重新生成 initramfs 镜像时,每个镜像可能约为 85M。请根据你的需求合理规划 boot 池大小。
如果你在重复使用磁盘,请按需清理: 确保 swap 分区未被使用:
如果磁盘之前用于 MD 阵列:
如果磁盘之前用于 zfs:
对于基于闪存的存储,如果磁盘之前被使用过,你可能希望对整块磁盘执行 discard(TRIM/UNMAP),这可以提升性能:
清除分区表:
如果你看到内核仍在使用旧分区表的提示信息,可以通过以下方式请求内核重新加载分区信息:
如果新分区仍未显示,你可以重启并重新开始(但可以跳过此步骤)。
创建 bootloader 分区:
注意:
Ubuntu 安装程序在 legacy(BIOS)启动时使用 MBR 标签,而本教程在 UEFI 和 legacy(BIOS)启动两种情况下都使用 GPT 分区标签。这比提供两种不同方案更简单,同时也提供了前向兼容性(面向未来)。换言之,对于 legacy(BIOS)启动,这将允许你在将来把磁盘移动到新的系统/主板上,而无需重建存储池(也无需从备份中恢复数据)。基于类似原因,两种情况下都会创建 ESP。此外,在单磁盘安装中,ESP 还用于
/boot/grub,如下文所述。
创建 swap 分区
本教程的早期版本曾将 swap 放在 zvol 上。Ubuntu 不推荐这种配置,因为可能导致死锁。 上游也有相关的缺陷报告。 将 swap 放在分区上会放弃 ZFS 校验和带来的好处(针对 swap)。考虑到关于 ZFS 与 swap 发生死锁的报告,这可能是合理的权衡。如果你对此感到疑惑,直接不要用 swap 即可。 如果你希望使用 swap,请选择以下方案之一:
单块磁盘安装:
镜像或 raidz 拓扑:
根据需要调整 swap 大小。如果你希望启用休眠(仅适用于未加密的系统),swap 分区的大小必须至少与系统内存相同。
创建 boot 池分区:
Ubuntu 安装程序使用磁盘空间的 5%,并限制最小为 500 MiB、最大为 2 GiB。将该分区设置得过小(而 500 MiB 可能就太小)可能会导致无法升级内核。
创建根池分区:
请选择以下方案之一:
未加密或 ZFS 原生加密:
LUKS:
如果你要创建镜像或 raidz 拓扑,请对将成为该存储池一部分的所有磁盘重复这些分区命令。
创建 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替换为raidz、raidz2或raidz3,并列出来自其他磁盘的分区。启动池名称不再是任意的。它 必须 是
bpool。如果你确实想重命名它,请在 GRUB 安装完成后编辑/etc/grub.d/10_linux_zfs(并运行update-grub)。
功能说明:
allocation_classes功能应当是安全的。然而,除非实际使用它(即使用specialvdev),否则启用它没有意义。几乎不可能有人会在启动池中使用该功能。如果关心启动池性能,将整个池放在更快的磁盘上,比将其用作specialvdev 更合理。device_rebuild功能应当是安全的(但在 raidz 上不兼容),不过启动池很小,因此在实践中并不重要。log_spacemap和spacemap_v2功能已经过测试并且是安全的。启动池很小,因此在实践中并不重要。project_quota功能已经过测试并且是安全的。该功能几乎不可能对启动池产生影响。resilver_defer应当是安全的,但启动池足够小,因此几乎没有必要。作为只读兼容功能,
userobj_accounting理论上应当兼容,但在实践中,GRUB 可能会因“invalid dnode type”错误而失败。该功能对/boot并不重要。
创建根池: 选择以下方案之一:
未加密:
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=sa是 Linux 特有的。如果你将启用了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。设置
relatime=on是在经典 POSIXatime行为(具有显著的性能影响)与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替换为raidz、raidz2或raidz3,并列出额外磁盘上的分区。在将 LUKS 与 mirror 或 raidz 拓扑一起使用时,请使用
/dev/mapper/luks1、/dev/mapper/luks2等,这些需要你使用cryptsetup创建。池名称是任意的。如果更改,必须一致地使用新名称。在可以自动安装到 ZFS 的系统上,根池默认命名为
rpool。
步骤 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)不受此影响。
在
/run挂载一个 tmpfs:
安装最小系统:
debootstrap 命令会使新系统处于未配置状态。使用 debootstrap 的另一种替代方案是将一个可正常工作的系统完整复制到新的 ZFS 根中。
复制 zpool.cache:
步骤 4:系统配置
配置主机名: 将
主机名替换为所需的主机名:提示:
如果觉得
vi难用,可以使用nano。配置网络接口: 查找接口名称:
将下面的
NAME调整为你的接口名称:如果系统不是 DHCP 客户端,请根据情况自定义此文件。
配置软件源:
将 LiveCD 环境中的虚拟文件系统绑定到新系统并
chroot进入:注意:
这里使用的是
--rbind,而不是--bind。配置基本系统环境:
即使你偏好非英文系统语言,也请确保
en_US.UTF-8可用:安装你喜欢的文本编辑器:
安装完整的
vim软件包可以修复使用vim-tiny(由debootstrap安装)在 SSH 下出现的终端问题。仅针对 LUKS 安装,设置
/etc/crypttab:使用
initramfs是 cryptsetup 不支持 ZFS 的解决方法。提示:
如果你创建的是 mirror 或 raidz 拓扑,请为
luks2等重复/etc/crypttab条目,并针对每个磁盘进行调整。创建 EFI 文件系统:
对于 UEFI 和传统(BIOS)启动均执行以下步骤:
对于 mirror 或 raidz 拓扑,请对额外磁盘重复执行 mkdosfs,但不重复其他命令。
注意:
mkdosfs的-s 1仅在驱动器呈现 4 KiB 逻辑扇区(“4Kn”驱动器)时必需,以满足 FAT32 的最小簇大小(在 512 MiB 分区情况下)。对于呈现 512 B 扇区的驱动器,同样可以正常工作。将
/boot/grub放到 EFI 系统分区:仅适用于单磁盘安装:
这能让 GRUB 写入
/boot/grub(因为它位于 FAT 格式的 ESP 上,而非 ZFS),这意味着/boot/grub/grubenv和recordfail功能可以按预期工作:如果启动失败,下次启动时将显示通常隐藏的 GRUB 菜单。对于 mirror 或 raidz 拓扑,我们不希望 GRUB 写入 EFI 系统分区,因为在安装时我们会复制它,而当 GRUB 配置更改(如内核升级)时,没有机制更新这些副本。因此,对于 mirror 或 raidz 拓扑,我们将/boot/grub保留在启动池中,这样可以保持正确的镜像/raidz 行为,但无法写入/boot/grub/grubenv,因此recordfail行为将失效。在新系统的 chroot 环境中安装 GRUB/Linux/ZFS: 选择以下其中一种:
为传统(BIOS)启动安装 GRUB/Linux/ZFS:
使用空格键选择池中的所有磁盘(而非分区)。
为 UEFI 启动安装 GRUB/Linux/ZFS:
注意:
忽略任何提示
ERROR: Couldn't resolve device和WARNING: Couldn't determine root device的错误信息。cryptsetup 不支持 ZFS。忽略任何提示
Module zfs not found和couldn't connect to zsys daemon的错误信息。第一个错误似乎是由于 Live CD 内核与 chroot 环境版本不匹配造成,但这无关紧要,因为模块已加载。第二个可能由第一个引起,但无论如何也无关紧要,因为zed后续会手动启动。对于 mirror 或 raidz 拓扑,此步骤仅在第一块磁盘上安装 GRUB。其他磁盘稍后处理。出于某种原因,grub-efi-amd64 在此不会提示
install_devices,但重启后会提示。
可选:卸载 os-prober:
这可以避免 update-grub 的错误信息。os-prober 仅在双系统中才需要。
设置 root 密码:
配置 swap: 如果需要 swap,请选择以下选项之一:
对于未加密的单块磁盘安装:
对于未加密的 mirror 或 raidz 拓扑:
对于加密(LUKS 或 ZFS 原生加密)的单块磁盘安装:
对于加密(LUKS 或 ZFS 原生加密)的 mirror 或 raidz 拓扑:
可选(但推荐):将
/tmp挂载为 tmpfs如果你在上文已创建
/tmpdataset,请跳过此步骤,因为两者互斥。否则,可以通过启用tmp.mount单元将/tmp放在 tmpfs(RAM 文件系统)上。
设置系统组:
可选:安装 SSH:
步骤 5:GRUB 安装
验证 ZFS 启动文件系统是否被识别:
刷新 initrd 文件:
注意:
忽略提示
ERROR: Couldn't resolve device和WARNING: Couldn't determine root device的错误信息。cryptsetup 不支持 ZFS。
禁用内存清零:
此操作用于解决 性能回退问题。
可选(但强烈建议):简化 GRUB 调试:
系统重启两次并确认一切正常后,可按需撤销这些更改。
更新启动配置:
注意:
如果出现 osprober 报错,请忽略。
安装启动加载器:
选择以下方案之一:
对于传统(BIOS)启动,将 GRUB 安装到 MBR:
注意
你是在将 GRUB 安装到整个磁盘,而非分区。如果创建 mirror 或 raidz 拓扑,请对池中每块磁盘重复执行
grub-install命令。
对于 UEFI 启动,将 GRUB 安装到 ESP:
禁用 grub-initrd-fallback.service 对于 mirror 或 raidz 拓扑:
这是针对 /boot/grub/grubenv 的服务,在镜像或 raidz 拓扑中无法使用。禁用它可以避免在 /boot/grub 挂载失败时阻塞后续挂载。 另一种选择是通过 drop-in 单元设置 RequiresMountsFor=/boot/grub,但在此没有必要额外操作。希望 此 Bug 上游能修复。
修复文件系统挂载顺序: 需要激活
zfs-mount-generator,让 systemd 识别单独的挂载点,这对于/var/log和/var/tmp等目录非常重要。rsyslog.service会通过local-fs.target依赖var-log.mount,使用 systemdPrivateTmp功能的服务会自动依赖After=var-tmp.mount。
通过确保以下文件不为空来验证 zed 是否更新了缓存:
如果任一为空,强制更新缓存并再次检查:
如果仍为空,停止 zed(如下),重新启动 zed(如上),再尝试一次。
在文件有数据后,停止 zed:
修正路径以去除 /mnt:
从
chroot环境退出,返回 LiveCD 环境:
在 LiveCD 环境中执行以下命令卸载所有文件系统:
如果 export 因繁忙错误失败,尝试终止可能正在使用它的进程:
即使如此池仍然繁忙,启动时挂载会失败,此时需要在 initramfs 提示符下执行
zpool import -f rpool,然后exit。重启:
等待新安装的系统正常启动,使用 root 登录。
步骤 6:首次启动
将 GRUB 安装到其他磁盘: 仅针对 UEFI mirror 或 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 密码保护,但你也可以使用额外加密。 提示: 如果创建了 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。
最后更新于