在树莓派上构建以 ZFS 为根文件系统的 Ubuntu 20.04
概览
有更新版本教程可用
请参见 Ubuntu 22.04 Root on ZFS for Raspberry Pi 以进行新安装。本指南不再接收大部分更新,仅保留供已按此教程安装的用户参考。
注意
本教程使用整块物理磁盘。
请备份数据,现有数据将会丢失。
系统要求
树莓派 4B。(如果希望在普通 PC 上安装,请参见 Ubuntu 20.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。将其存入变量:
创建分区脚本:
连接磁盘: 将磁盘连接到目标树莓派以外的机器。如果有文件系统被自动挂载(例如 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-ccm,但 上游已改为aes-256-gcm。AES-GCM 通常优于 AES-CCM,现在更快,未来性能还将提升。对于 LUKS,选择的密钥长度为 512 位。但 XTS 模式需要两个密钥,因此 LUKS 密钥会对半拆分。故
-s 512实际表示 AES-256。密码短语可能是最薄弱环节,请谨慎选择。参见 cryptsetup FAQ 第 5 节 获取指导。
第 3 步:系统安装
创建一个作为容器的文件系统数据集:
为根文件系统创建数据集:
对于 ZFS,通常不需要使用 mount 命令(无论是
mount还是zfs mount)。这里使用canmount=noauto是一个例外情况。创建其他数据集:
如果希望为
/tmp创建单独数据集:这种数据集布局的主要目的是将操作系统与用户数据分离,使得回滚根文件系统不会影响用户数据。若不做额外操作,
/tmp将作为根文件系统的一部分存储。通过创建单独的数据集/tmp,可以将/tmp数据排除在根文件系统的快照之外,也可以为rpool/tmp设置配额以限制最大空间。否则,可以使用 tmpfs(内存文件系统)。可选:忽略同步请求 存储卡速度相对较慢。如果希望提升性能(尤其是在安装软件包时),可以禁用同步请求(如
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 原生加密或 LUKS:
忽略 Hunk #2 的失败(连续回答
n两次)。此补丁来源于 Bug #1875577 Encrypted swap won’t load on 20.04 with zfs root。
修复文件系统挂载顺序: 需要激活
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等)重复此操作。
最后更新于