在树莓派上构建以 ZFS 为根文件系统的 Ubuntu 22.04
概览
注意
这些是 beta 说明。作者仍然需要对其进行测试。此外,现在可能可以使用 U-Boot,这将减少其中的一些需要自定义的内容。
注意
本教程使用整块物理磁盘。
请备份数据,现有数据将会丢失。
系统要求
树莓派 4B。(如果希望在普通 PC 上安装,请参见 Ubuntu 22.04 Root on ZFS)
存储卡或 USB 磁盘。存储卡推荐请参考 Jeff Geerling 的 性能比较。当使用 USB 外置硬盘时,确保其支持 UASP。
一台 Ubuntu 系统(可写入存储卡或 USB 磁盘)——目标树莓派外的设备。
建议配备 4 GiB 内存。请勿使用去重功能,因为它需要 大量内存。启用去重是永久性更改,无法轻易撤销。
树莓派 3 B/B+ 可能也可运行(树莓派 3 为 64 位,但内存容量不大),但尚未经过测试。请使用下方问题链接报告你的结果(无论好坏)。
加密
警告:
尚未在树莓派上测试过加密功能。
本指南支持三种不同的加密方案:未加密、ZFS 原生加密和 LUKS。无论选择哪种方式,所有 ZFS 功能均可完全使用。
未加密当然不会对任何数据进行加密。在没有加密的情况下,该选项自然具有最佳性能。
ZFS 原生加密会加密根池中的数据和大部分元数据,但不会加密数据集或快照的名称或属性。启动池完全不加密,但它只包含引导加载程序、内核和 initrd。(除非你在 /etc/fstab 中设置了密码,否则 initrd 不太可能包含敏感数据。)如果未在控制台输入口令,系统无法启动。性能良好。由于加密在 ZFS 内进行,即使使用多个磁盘(镜像或 raidz 拓扑),数据也只需加密一次。
LUKS 会几乎加密所有内容。唯一未加密的数据是引导加载程序、内核和 initrd。如果未在控制台输入口令,系统无法启动。性能良好,但 LUKS 位于 ZFS 之下,因此如果使用多个磁盘(镜像或 raidz 拓扑),数据需要对每个磁盘分别加密一次。
USB 磁盘
树莓派 4 使用 USB 固态硬盘(SSD)比使用存储卡运行速度快得多。这些指令也可用于在 USB 连接的 SSD 或其他 USB 磁盘上安装 Ubuntu。对 USB 磁盘有三个要求,对存储卡没有这些限制:
1.树莓派的 Bootloader EEPROM 必须为 2020-09-03 或更高版本。 要检查 bootloader 版本,请在未插入存储卡或未连接 USB 启动设备的情况下启动树莓派;日期会显示在 bootloader 行上。(如果看不到 bootloader 行,说明 bootloader 太旧。)或者,在现有树莓派系统上运行 sudo rpi-eeprom-update(在 Ubuntu 上需要 apt install rpi-eeprom)。 如有需要,可在现有树莓派系统上使用 rpi-eeprom-update -a 更新 bootloader 并重启。其他选项请参见 更新 Bootloader。 2.树莓派必须配置为 USB 启动。Bootloader 会显示 boot 行;如果 order 中包含 4,说明 USB 启动已启用。 如果尚未启用,可在现有树莓派系统上使用 rpi-eeprom-config -e 启用:设置 BOOT_ORDER=0xf41 并重启以应用更改。之后的启动将启用 USB 启动。 如果没有现有系统,也可按以下步骤启用:
下载 树莓派启动盘制作工具。
将
USB Boot镜像写入存储卡。USB Boot镜像位于Misc utility images文件夹下的Bootloader中。使用存储卡启动树莓派。USB 启动应会自动启用。
Ubuntu 20.04 上的 U-Boot 似乎不支持树莓派 USB。Ubuntu 20.10 或许可行。作为变通方法,树莓派 bootloader 配置为直接启动 Linux。为此,Linux 内核必须未压缩。本指南会解压内核并在
/etc/kernel/postinst.d添加脚本以处理内核升级。
第 1 步:格式化磁盘
本步骤的命令在目标树莓派以外的系统上运行。
本指南会让你做一些额外操作,以便删除原始 ext4 分区。
下载解压官方镜像:
导出镜像的分区表:
输出如下:
关键数字是 524288 和 6285628。将其存入变量:
创建分区脚本:
这会为 bootloader 准备第 1 个分区,为初始 ZFS 池准备第 2 个分区(大小与原始镜像相同),并为原始镜像本身准备第 3 个分区。磁盘的其余部分暂时保持未分区状态。
下面,我们将上面下载的镜像内容写入第 1 个和第 3 个分区,在第 2 个分区上创建 ZFS 池,并将第 3 个分区中的文件传输到该池中。由于 ZFS 分区的大小与原始镜像相同,所有内容都应能够容纳;尽管文件系统开销可能有所不同,但原始镜像并未完全占满,而且 ZFS 已启用压缩。
最后,我们移除第 3 个分区,并扩展第 2 个分区以及 ZFS 池,使其占用磁盘的其余空间。
连接磁盘: 将磁盘连接到目标树莓派以外的机器。如果有文件系统被自动挂载(例如 GNOME),请先卸载。确定设备名称。对于存储卡,设备名称几乎肯定是
/dev/mmcblk0;对于 USB SSD,设备名称是/dev/sdX,其中X是小写字母。lsblk可帮助确定设备名称。将DISK环境变量设置为设备名称:由于
/dev/mmcblk0和/dev/sdX的分区命名不同,设置第二个变量以便操作分区:
提示:
通过 USB 读卡器连接的存储卡也会显示为
/dev/sdX。
警告:
以下步骤会清除磁盘上现有数据。在继续之前,请确保
DISK和DISKP设置正确。
确保交换分区未被使用:
清除旧的 ZFS 标签:
如果之前系统或尝试中存在 ZFS 标签,扩展池会导致系统无法启动。
提示:
如果尚未安装 ZFS 工具,可以使用命令
sudo apt install zfsutils-linux安装。或者,也可以使用sudo dd if=/dev/zero of=${DISK} bs=1M status=progress清零整个磁盘。
删除现有分区:
确保没有任何分区,仅剩下磁盘本身的设备文件。此步骤不是必需的,仅用于检查问题。
创建分区:
挂载镜像为 loop 设备:
复制引导加载器数据:
清除分区 2 上的旧标签:
如果 Ubuntu 镜像分区 2 上仍存在 writable 标签的文件系统,系统最初将无法启动。
复制根文件系统数据:
卸载镜像:
如果使用 USB 磁盘: 解压内核:
修改启动配置:
创建内核升级后自动解压内核的脚本:
清理:
启动树莓派。 将存储卡或 USB 磁盘移至树莓派,启动并登录(例如通过 SSH),用户名和密码均为
ubuntu。如果使用 SSH,请注意首次启动时 cloud-init 需要一点时间来启用密码登录。首次登录后按提示设置新密码,并使用新密码再次登录。如果你的本地 SSH 配置使用了ControlPersist,可能需要先终止已有的 SSH 进程,才能进行第二次登录。
第 2 步:设置 ZFS
切换到 root 用户:
再次设置
DISK和DISKP变量:
警告:设备名称在将设备移动到不同计算机或将存储卡从 USB 读卡器切换到内置插槽时可能会变化。继续之前务必仔细检查设备名称。
安装 ZFS:
注意:
由于这是首次启动,可能会出现
Waiting for cache lock,这是因为unattended-upgrades正在后台运行。请耐心等待其完成。
创建根池: 选择以下一种方式:
未加密:
警告:
在树莓派上尚未测试过加密。
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 处理。设置
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 文档。设置
xattr=sa大幅提高扩展属性性能。在 ZFS 中,扩展属性用于实现 POSIX ACL,也可被用户空间应用使用。部分桌面 GUI 应用使用它。Samba 可用它存储 Windows ACL 和 DOS 属性,并且 Samba AD 域控制器必需该功能。注意,xattr=sa仅适用于 Linux。如果将包含xattr=sa的池移动到其他 OpenZFS 实现(非 ZFS-on-Linux),扩展属性将无法读取(数据本身仍可读取)。如果扩展属性可移植性重要,可省略-O xattr=sa。即使不希望对整个池使用xattr=sa,通常在/var/log上使用也没问题。确保包含驱动器路径中的
-part4。如果忘记,ZFS 将识别整个磁盘并重新分区,从而丢失启动加载器分区。ZFS 原生加密现在默认使用
aes-256-gcm。对于 LUKS,选择的密钥长度为 512 位。但 XTS 模式需要两个密钥,因此 LUKS 密钥会对半拆分。故
-s 512实际表示 AES-256。密码短语可能是最薄弱环节,请谨慎选择。参见 cryptsetup FAQ 第 5 节 获取指导。
第 3 步:安装系统
创建作为容器的文件系统数据集:
为根文件系统创建数据集:
对于 ZFS,通常不需要使用 mount 命令(无论是
mount还是zfs mount)。这里使用canmount=noauto是一个例外情况。创建其他数据集:
下面的数据集是可选的,取决于你的偏好及软件选择。
如果你希望将它们分离以将其排除在快照之外:
如果需要(Ubuntu 安装程序会创建这些):
如果你在此系统上使用 /srv:
如果你在此系统上使用 /usr/local:
如果此系统将安装游戏:
如果此系统将使用 GUI:
如果此系统将使用 Docker(其自行管理数据集和快照):
如果此系统将在 /var/mail 中存储本地电子邮件:
如果此系统将使用 Snap 包:
如果你在此系统上使用 /var/www:
对于 mirror 或 raidz 拓扑,为 /boot/grub 创建一个数据集:
稍后建议使用 tmpfs,但如果你想为 /tmp 使用单独的数据集:
这种数据集布局的主要目的是将操作系统与用户数据分离,使得回滚根文件系统不会影响用户数据。若不做额外操作,/tmp 将作为根文件系统的一部分存储。通过创建单独的数据集 /tmp,可以将 /tmp 数据排除在根文件系统的快照之外,也可以为 rpool/tmp 设置配额以限制最大空间。否则,可以使用 tmpfs(内存文件系统)。
注意:
如果你将启动所必需的目录(例如
/etc)拆分为独立的 dataset,必须将其添加到/etc/default/zfs中的ZFS_INITRD_ADDITIONAL_DATASETS。具有canmount=off的 dataset(例如上面的rpool/usr)不受此影响。
可选:忽略同步请求 存储卡速度相对较慢。如果希望提升性能(尤其是在安装软件包时),可以禁用同步请求(如
fsync()或O_[D]SYNC)的刷新操作,代价是安全性会下降。可选择以下方案:仅针对根文件系统,不影响用户数据:
针对所有数据:
ZFS 是事务型的,仍能保证崩溃一致性。但若系统需要保证持久性(如数据库或 NFS 服务器),应保持
sync为默认的standard。将系统内容复制到 ZFS 文件系统:
第 4 步:系统配置
配置主机名: 将
主机名替换为你想要的主机名:在
/mnt/etc/hosts中添加一行:如果系统在 DNS 中有完整域名(FQDN):
提示:
如果不熟悉
vi,可以使用nano。停止
zed服务:将当前环境的虚拟文件系统绑定到新的 ZFS 环境,并
chroot进入:配置基础系统环境:
即使偏好非英语系统语言,也请确保
en_US.UTF-8可用:仅针对 LUKS 安装,设置
/etc/crypttab:使用
initramfs是为了解决 cryptsetup 不支持 ZFS 的问题。可选:将
/tmp挂载为 tmpfs如果上一步已为
/tmp创建了数据集,则跳过此步骤。否则可启用tmp.mount单元,将/tmp作为内存文件系统:设置系统组:
修复文件系统挂载顺序: 需要激活
zfs-mount-generator,使 systemd 识别各挂载点,这对/var/log、/var/tmp等非常重要。系统服务如rsyslog.service会通过local-fs.target依赖var-log.mount,使用 systemdPrivateTmp的服务会自动依赖After=var-tmp.mount。强制更新缓存:
确认
zed已更新缓存(等待几秒,确保文件非空):停止
zed:修正路径以去掉
/mnt:从
/etc/fstab移除旧文件系统:可选(强烈推荐):便于调试启动:
重启:
等待新系统正常启动,以
ubuntu用户登录。
第 5 步:首次启动
切换到 root 用户:
再次设置 DISK 变量:
删除 ext4 分区并扩展 ZFS 分区:
注意:这不会自动扩展 ZFS pool,扩展将在重启后发生。
创建用户账户:
将
你的用户名替换为你希望的用户名:重启:
等待系统正常启动,并使用新创建的账户登录。
切换到 root 用户:
扩展 ZFS 存储池: 验证存储池是否已扩展:
如果未自动扩展,可以手动扩展:
删除默认的
ubuntu用户:
第 6 步:完整软件安装
可选:移除 cloud-init:
可选:移除其他存储相关软件包:
更新最小系统:
可选:安装完整 GUI 环境:
提示:
如果安装完整 GUI 环境,通常需要如上移除 cloud-init,但改用 NetworkManager 管理网络:
可选(推荐):禁用日志压缩:
由于
/var/log已由 ZFS 压缩,logrotate 的压缩会消耗 CPU 和磁盘 I/O,而收益有限。此外,如果对/var/log做快照,logrotate 压缩反而会浪费空间,因为未压缩的数据仍会保存在快照中。你可以手动编辑/etc/logrotate.d下的文件,将compress注释掉,或者使用如下循环(强烈建议复制粘贴):重启:
第 7 步:最终清理
等待系统正常启动。使用你创建的账户登录,确保系统(包括网络)正常运行。
可选:仅针对 LUKS 安装,备份 LUKS 头信息:
将备份保存到安全位置(例如云存储)。虽然它受 LUKS 密码保护,但你可能仍希望使用额外加密。
提示:
如果你创建了镜像或 raidz 拓扑,请为每个 LUKS 卷(如
luks2等)重复此操作。
最后更新于