github编辑

在树莓派上构建以 ZFS 为根文件系统的 Ubuntu 20.04

概览

有更新版本教程可用

注意

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

  • 请备份数据,现有数据将会丢失。

系统要求

建议配备 4 GiB 内存。请勿使用去重功能,因为它需要 大量内存arrow-up-right。启用去重是永久性更改,无法轻易撤销。

树莓派 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 并重启。其他选项请参见 更新 Bootloaderarrow-up-right。 2.树莓派必须配置为 USB 启动。Bootloader 会显示 boot 行;如果 order 中包含 4,说明 USB 启动已启用。 如果尚未启用,可在现有树莓派系统上使用 rpi-eeprom-config -e 启用:设置 BOOT_ORDER=0xf41 并重启以应用更改。之后的启动将启用 USB 启动。 如果没有现有系统,也可按以下步骤启用:

  • USB Boot 镜像写入存储卡。USB Boot 镜像位于 Misc utility images 文件夹下的 Bootloader 中。

  • 使用存储卡启动树莓派。USB 启动应会自动启用。

  1. Ubuntu 20.04 上的 U-Boot 似乎不支持树莓派 USB。Ubuntu 20.10 可能可行arrow-up-right。作为变通方法,树莓派 bootloader 配置为直接启动 Linux。为此,Linux 内核必须未压缩。本指南会解压内核并在 /etc/kernel/postinst.d 添加脚本以处理内核升级。

第 1 步:格式化磁盘

本步骤的命令在目标树莓派以外的系统上运行。

本指南会让你做一些额外操作,以便删除原始 ext4 分区。

  1. 下载解压官方镜像:

  2. 导出镜像的分区表:

    输出如下:

    关键数字是 524288 和 6285628。将其存入变量:

  3. 创建分区脚本:

  4. 连接磁盘: 将磁盘连接到目标树莓派以外的机器。如果有文件系统被自动挂载(例如 GNOME),请先卸载。确定设备名称。对于存储卡,设备名称几乎肯定是 /dev/mmcblk0;对于 USB SSD,设备名称是 /dev/sdX,其中 X 是小写字母。lsblk 可帮助确定设备名称。将 DISK 环境变量设置为设备名称:

    由于 /dev/mmcblk0/dev/sdX 的分区命名不同,设置第二个变量以便操作分区:

提示

通过 USB 读卡器连接的存储卡也会显示为 /dev/sdX

警告

以下步骤会清除磁盘上现有数据。在继续之前,请确保 DISKDISKP 设置正确。

  1. 确保交换分区未被使用:

  1. 清除旧的 ZFS 标签:

如果之前系统或尝试中存在 ZFS 标签,扩展池会导致系统无法启动。

提示

如果尚未安装 ZFS 工具,可以使用命令 sudo apt install zfsutils-linux 安装。或者,也可以使用 sudo dd if=/dev/zero of=${DISK} bs=1M status=progress 清零整个磁盘。

  1. 删除现有分区:

确保没有任何分区,仅剩下磁盘本身的设备文件。此步骤不是必需的,仅用于检查问题。

  1. 创建分区:

  1. 挂载镜像为 loop 设备:

  1. 复制引导加载器数据:

  1. 清除分区 2 上的旧标签:

如果 Ubuntu 镜像分区 2 上仍存在 writable 标签的文件系统,系统最初将无法启动。

  1. 复制根文件系统数据:

  1. 卸载镜像:

  1. 如果使用 USB 磁盘: 解压内核:

修改启动配置:

创建内核升级后自动解压内核的脚本:

清理:

  1. 启动树莓派。 将存储卡或 USB 磁盘移至树莓派,启动并登录(例如通过 SSH),用户名和密码均为 ubuntu。如果使用 SSH,请注意首次启动时 cloud-init 需要一点时间来启用密码登录。首次登录后按提示设置新密码,并使用新密码再次登录。如果你的本地 SSH 配置使用了 ControlPersist,可能需要先终止已有的 SSH 进程,才能进行第二次登录。

第 2 步:设置 ZFS

  1. 切换到 root 用户:

  1. 再次设置 DISKDISKP 变量:

警告:设备名称在将设备移动到不同计算机或将存储卡从 USB 读卡器切换到内置插槽时可能会变化。继续之前务必仔细检查设备名称。

  1. 安装 ZFS:

注意

由于这是首次启动,可能会出现 Waiting for cache lock,这是因为 unattended-upgrades 正在后台运行。请耐心等待其完成。

  1. 创建根池: 选择以下一种方式:

  • 未加密:

警告:在树莓派上尚未测试加密。

  • ZFS 原生加密:

  • LUKS 加密:

注意事项:

  • 建议使用 ashift=12,因为如今许多硬盘物理扇区为 4 KiB(或更大),即使其逻辑扇区仍为 512 B。此外,将来替换的硬盘可能采用 4 KiB 物理扇区(此时 ashift=12 可取)或 4 KiB 逻辑扇区(此时 ashift=12 必须)。

  • 设置 -O acltype=posixacl 可全局启用 POSIX ACL。如果不希望全局启用,可移除该选项,但在 /var/logzfs create 命令中仍需加 -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),可参考 这些文章arrow-up-right博客arrow-up-right

  • 设置 relatime=on 是传统 POSIX atime(性能影响大)和 atime=off(完全禁用 atime 更新以获得最佳性能)之间的折中。自 Linux 2.6.30 起,其他文件系统的默认值为 relatime。详见 RedHat 文档arrow-up-right

  • 设置 xattr=sa 大幅提高扩展属性性能arrow-up-right。在 ZFS 中,扩展属性用于实现 POSIX ACL,也可被用户空间应用使用。部分桌面 GUI 应用使用它arrow-up-right。Samba 可用它存储 Windows ACL 和 DOS 属性,并且 Samba AD 域控制器必需该功能。注意,xattr=sa 仅适用于 Linuxarrow-up-right。如果将包含 xattr=sa 的池移动到其他 OpenZFS 实现(非 ZFS-on-Linux),扩展属性将无法读取(数据本身仍可读取)。如果扩展属性可移植性重要,可省略 -O xattr=sa。即使不希望对整个池使用 xattr=sa,通常在 /var/log 上使用也没问题。

  • 确保包含驱动器路径中的 -part4。如果忘记,ZFS 将识别整个磁盘并重新分区,从而丢失启动加载器分区。

  • ZFS 原生加密默认使用 aes-256-ccm,但 上游已改为arrow-up-right aes-256-gcmAES-GCM 通常优于 AES-CCMarrow-up-right,现在更快,未来性能还将提升。

  • 对于 LUKS,选择的密钥长度为 512 位。但 XTS 模式需要两个密钥,因此 LUKS 密钥会对半拆分。故 -s 512 实际表示 AES-256。

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

第 3 步:系统安装

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

  2. 为根文件系统创建数据集:

    对于 ZFS,通常不需要使用 mount 命令(无论是 mount 还是 zfs mount)。这里使用 canmount=noauto 是一个例外情况。

  3. 创建其他数据集:

    如果希望为 /tmp 创建单独数据集:

    这种数据集布局的主要目的是将操作系统与用户数据分离,使得回滚根文件系统不会影响用户数据。若不做额外操作,/tmp 将作为根文件系统的一部分存储。通过创建单独的数据集 /tmp,可以将 /tmp 数据排除在根文件系统的快照之外,也可以为 rpool/tmp 设置配额以限制最大空间。否则,可以使用 tmpfs(内存文件系统)。

  4. 可选:忽略同步请求 存储卡速度相对较慢。如果希望提升性能(尤其是在安装软件包时),可以禁用同步请求(如 fsync()O_[D]SYNC)的刷新操作,代价是安全性会下降。可选择以下方案:

    • 仅针对根文件系统,不影响用户数据:

    • 针对所有数据:

    ZFS 是事务型的,仍能保证崩溃一致性。但若系统需要保证持久性(如数据库或 NFS 服务器),应保持 sync 为默认的 standard

  5. 将系统内容复制到 ZFS 文件系统:

第 4 步:系统配置

  1. 配置主机名: 将 主机名 替换为你想要的主机名:

    /mnt/etc/hosts 中添加一行:

    如果系统在 DNS 中有完整域名(FQDN):

    提示

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

  2. 停止 zed 服务:

  3. 将当前环境的虚拟文件系统绑定到新的 ZFS 环境,并 chroot 进入:

  4. 配置基础系统环境:

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

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

    使用 initramfs 是为了解决 cryptsetup 不支持 ZFSarrow-up-right 的问题。

  6. 可选:将 /tmp 挂载为 tmpfs

    如果上一步已为 /tmp 创建了数据集,则跳过此步骤。否则可启用 tmp.mount 单元,将 /tmp 作为内存文件系统:

  7. 设置系统组:

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

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

    此补丁来源于 Bug #1875577 Encrypted swap won’t load on 20.04 with zfs rootarrow-up-right

  9. 修复文件系统挂载顺序: 需要激活 zfs-mount-generator,使 systemd 识别各挂载点,这对 /var/log/var/tmp 等非常重要。系统服务如 rsyslog.service 会通过 local-fs.target 依赖 var-log.mount,使用 systemd PrivateTmp 的服务会自动依赖 After=var-tmp.mount

强制更新缓存:

确认 zed 已更新缓存(等待几秒,确保文件非空):

停止 zed

修正路径以去掉 /mnt

  1. /etc/fstab 移除旧文件系统:

  2. 配置内核启动参数:

    fixrtc 脚本与 ZFS 不兼容,会导致启动挂起 180 秒。

    init_on_alloc=0 用于解决 性能回退问题arrow-up-right

  3. 可选(强烈建议):便于调试启动:

  4. 重启:

    等待新系统正常启动,以 ubuntu 用户登录。

第 5 步:首次启动

  1. 切换到 root 用户:

  2. 再次设置 DISK 变量:

  3. 删除 ext4 分区并扩展 ZFS 分区:

    注意:这不会自动扩展 ZFS pool,扩展将在重启后发生。

  4. 创建用户账户:

    你的用户名 替换为你希望的用户名:

  5. 重启:

    等待系统正常启动,并使用新创建的账户登录。

  6. 切换到 root 用户:

  7. 扩展 ZFS 存储池: 验证存储池是否已扩展:

    如果未自动扩展,可以手动扩展:

  8. 删除默认的 ubuntu 用户:

第 6 步:完整软件安装

  1. 可选:移除 cloud-init:

  2. 可选:移除其他存储相关软件包:

  3. 更新最小系统:

  4. 可选:安装完整 GUI 环境:

    提示

    如果安装完整 GUI 环境,通常需要如上移除 cloud-init,但改用 NetworkManager 管理网络:

  5. 可选(推荐):禁用日志压缩:

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

  6. 重启:

第 7 步:最终清理

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

  2. 可选:仅针对 LUKS 安装,备份 LUKS 头信息:

    将备份保存到安全位置(例如云存储)。虽然它受 LUKS 密码保护,但你可能仍希望使用额外加密。

    提示

    如果你创建了镜像或 raidz 拓扑,请为每个 LUKS 卷(如 luks2 等)重复此操作。

最后更新于