- 操作系统
【免费下载链接】zfs
OpenZFS on Linux and FreeBSD
导读
本文以 OpenZFS 仓库中的 contrib/initramfs/README.md 为骨架,系统讲解 Debian 系发行版(Ubuntu 及其衍生版)如何通过initramfs-tools在启动早期导入 ZFS 池、挂载根文件系统、从快照引导,以及解锁加密根文件系统。读完本文,你将掌握:内核命令行上所有受支持的根设备指定语法(rpool、bootfs、root=ZFS=等)及其优先级、/etc/default/zfs中与 initramfs 相关的全部配置项、从快照克隆/回滚启动的完整流程,以及通过 dropbear-initramfs 或自定义 key-loading hook 在 initramfs 阶段解锁加密根的两种方案,并了解对应脚本在仓库中的实现位置。
一、背景:initramfs-tools 与 OpenZFS 启动脚本
initramfs-tools是 Debian GNU/Linux 及其衍生发行版(Ubuntu、Linux Mint 等)用来生成与运行 initramfs(初始 RAM 文件系统)的框架,与 Red Hat 系发行版使用的dracut属于同类产品。OpenZFS 在 contrib/initramfs 目录下提供了一整套与该框架对接的脚本,使系统能够在根文件系统本身位于 ZFS 池上时正常引导。
这些脚本与 SysV init 脚本共享一部分公共功能,核心是 etc/zfs/zfs-functions.in(安装后位于/etc/zfs/zfs-functions)。initramfs 中的 ZFS 启动脚本会在mountroot阶段 source 该公共函数库,复用其中的日志、挂载、加载密钥等基础设施(见 contrib/initramfs/scripts/zfs 第 12 行的. /etc/zfs/zfs-functions)。
仓库中 initramfs 相关文件按 initramfs-tools 的标准目录结构组织,安装目标在 contrib/initramfs/Makefile.am 中定义:
| 仓库路径 | 安装到 initramfs 中的位置 | 作用 |
|---|---|---|
| hooks/zfs.in | /usr/share/initramfs-tools/hooks/zfs | 构建 initramfs 时把zpool、zfs、mount.zfs、vdev_id、zvol_id等二进制及配置拷入镜像 |
| hooks/zfsunlock.in | /usr/share/initramfs-tools/hooks/zfsunlock | 把zfsunlock脚本及 motd 拷入镜像 |
| scripts/zfs | /usr/share/initramfs-tools/scripts/zfs | 启动时被/initsource 的引导桩,包含mountroot主流程 |
| scripts/local-top/zfs | /usr/share/initramfs-tools/scripts/local-top/zfs | 在导入池之前等待 udev 就绪并激活 LVM 卷组(prereqs 依赖mdadm mdrun multipath) |
| conf.d/zfs | /usr/share/initramfs-tools/conf.d/zfs | 当内核命令行出现root=ZFS=*或root=zfs:*时设置BOOT=zfs |
| conf-hooks.d/zfs | /usr/share/initramfs-tools/conf-hooks.d/zfs | 强制 initramfs 包含 Busybox、键盘映射与 framebuffer(保证输入解锁密码时有可用的终端/plymouth 界面) |
| zfsunlock | /usr/share/initramfs-tools/zfsunlock | SSH 解锁加密根的辅助脚本,见下文“通过 SSH 解锁”一节 |
二、配置根池与根文件系统:内核命令行语法
不同发行版对“如何在内核命令行指定从 ZFS 文件系统启动”有各自的习惯。本脚本按**以下顺序(第一个匹配生效,first match wins)**支持所有常见写法:
rpool=<pool>—— 只指定池,根文件系统自动探测(依靠池的bootfs属性);bootfs=<pool>/<dataset>—— 直接指定池与根数据集;rpool=<pool> bootfs=<pool>/<dataset>—— 同时指定;-B zfs-bootfs=<pool>/<fs>—— GRUB 等引导器注入的zfs-bootfs内核参数(-B表示向内核追加参数);root=<pool>/<dataset>—— 经典root=语法直接给 ZFS 数据集;root=ZFS=<pool>/<dataset>;root=zfs:AUTO—— 自动探测模式:同时自动发现池与根文件系统;root=zfs:<pool>/<dataset>;rpool=rpool—— 兜底默认值,即命名池rpool并自动探测根数据集。
在源码层面,这些参数的实际解析逻辑位于 contrib/initramfs/scripts/zfs 的mountroot()函数中。其中值得注意的实现细节:
- 大小写兼容:脚本先处理
rpool、bootfs内核变量,再把ROOT(Debian 系)/root(RedHat 系)/REAL_ROOT(Gentoo)统一归一化到ROOT,因此三种发行版体系的写法都能识别。 zfs-bootfs的特殊处理:由于 POSIX shell 变量名不能含连字符,脚本无法直接引用$zfs-bootfs,而是通过eval ZFS_RPOOL="$(set | sed -n -e 's,^zfs-bootfs=,,p')"从环境变量转储中提取该值(见 scripts/zfs 第 864–879 行)。- AUTO 模式的实现:当既没有指定池也没有指定数据集时,脚本会“作弊”地把
ROOT设为zfs:AUTO,走统一的自动探测分支——列出所有可导入的池、逐个导入并检查其bootfs属性(find_rootfs()),直到找到挂载点为/的数据集为止。 - 用 GUID 指定池:
<pool>位置也可以写池的 GUID,导入成功后脚本会把 GUID 解析回池名,并把ZFS_BOOTFS中的 GUID 部分一并替换为池名(scripts/zfs 第 963–972 行)。
自动探测规则与排除列表
如果指定了池,则只使用该池;否则在 AUTO 模式下会搜索所有可见的池。不需要搜索的池可以列入/etc/default/zfs中的ZFS_POOL_EXCEPTIONS,脚本的get_pools()会按分号分隔的列表过滤掉这些池(scripts/zfs 第 167–195 行)。
池的导入顺序
脚本按以下优先级尝试导入池(见 contrib/initramfs/scripts/zfs 的import_pool()与get_pools()):
- 若
/dev/disk/by-vdev存在则优先使用(vdev 路径由 etc/zfs/vdev_id.conf 定义); - 依次尝试
/dev/disk/by-id及其他所有/dev/disk/by-*目录; - 尝试
/dev; - 以上全部失败时使用 cache 文件(默认
/etc/zfs/zpool.cache)。
这一顺序可通过在/etc/default/zfs中设置ZPOOL_IMPORT_PATH覆盖,例如:
ZPOOL_IMPORT_PATH="/dev/disk/by-vdev:/dev/disk/by-id"关于该变量的更多说明可参考 man/man8/zpool-import.8 与 man/man8/zpool.8。从源码看,若检测到仅通过/dev/disk/by-id才能发现额外池,脚本会自动把USE_DISK_BY_ID置为yes并据此构造完整的ZPOOL_IMPORT_PATH(by-vdev在前、/dev殿后),确保后续能成功导入这些池。
根文件系统自动发现与子数据集挂载
如果内核命令行指定了数据集,则直接以它为根文件系统;否则脚本会尝试自动发现:读取池的bootfs属性(通过zpool list -H -obootfs),要求该值非-且其mountpoint属性为/,否则导出该池继续尝试下一个(find_rootfs(),scripts/zfs 第 81–114 行)。
一旦确定根文件系统,根数据集之下所有嵌套的子数据集都会被自动挂载,无需额外配置。例如根文件系统为rpool/ROOT/rootfs时,rpool/root/rootfs/var、rpool/root/rootfs/usr等(如果存在)也会被依次挂载。实现上,mountroot()用zfs list -oname -tfilesystem -H -r "${ZFS_BOOTFS}"递归枚举所有子数据集并逐一调用mount_fs()(scripts/zfs 第 1025–1032 行)。
此外,还可以通过/etc/default/zfs中的ZFS_INITRD_ADDITIONAL_DATASETS挂载“位于根数据集之外、但属于操作系统组成部分”的额外数据集(例如/home不属于 OS,会自动在后续启动阶段挂载,不应列入):
ZFS_INITRD_ADDITIONAL_DATASETS="rpool/usr rpool/var rpool/var/spool"对应配置项在 etc/default/zfs.in 第 65 行有完整注释与示例。
挂载细节
mount_fs()会跳过canmount=off的数据集(根数据集例外,向后兼容);对mountpoint=legacy或none的数据集(尤其是快照克隆产物),会回退读取org.zol:mountpoint属性来获得真实挂载点,必要时以mount.zfs直接挂载。根文件系统自身由 initramfs 脚本手动挂载到${rootmnt}(Debian 系)或${NEWROOT}(RedHat 系)/${NEW_ROOT}(Gentoo 系)。源码注释中还特别解释了为何不能简单地用zpool import -R "${rootmnt}" -N+zfs mount -o mountpoint=/的组合:MOUNTPOINT前缀会残留在后续文件系统上,导致 pivoting 之后zfs mount -a与/proc/self/mounts刷新出现问题(scripts/zfs 第 1004–1012 行)。
三、从快照引导
<dataset>位置也可以是快照(即含@的数据集名)。此时脚本会把快照克隆出来,用克隆体作为根文件系统。相关逻辑位于 contrib/initramfs/scripts/zfs 的setup_snapshot_booting()与clone_snap()。需要注意以下几点:
- 快照不存在时:自动回退,把
@之前的基础数据集作为引导文件系统使用(若快照名无法解析,会提示用户从快照列表中选择,见ask_user_snap())。 - 克隆目标已存在时:先递归销毁它(
destroy_fs()),再重新克隆,保证每次引导都从快照的干净状态出发。 - 克隆属性:克隆以
mountpoint=none、canmount=noauto创建,由 initramfs 脚本手动挂载根文件系统;同时用org.zol:mountpoint属性记录原挂载点,供mount_fs()恢复(scripts/zfs 第 545–587 行)。这是为了避免zfs mount -a在 init/systemd 阶段把克隆体再次自动挂载。 - 递归克隆:除了根快照,根数据集下所有子文件系统对应的同名快照(如
rpool/ROOT/debian/usr@snap2)也会被克隆到对应的嵌套路径(如rpool/ROOT/debian_snap2/usr),确保/usr、/var等子数据集同样来自快照(scripts/zfs 第 701–744 行给出了完整的命名示例)。 - 未指定快照但有
@:若内核命令行root=只给了数据集前缀(不含具体快照名),脚本会交互式询问用户选择要引导的快照。
典型的快照引导命令行示例:
root=zfs:rpool/ROOT/debian@snap2脚本会把rpool/ROOT/debian@snap2转换为克隆数据集rpool/ROOT/debian_snap2作为ZFS_BOOTFS使用。
四、附加内核命令行选项
除根设备指定外,脚本还支持以下附加参数(取值on、yes或1均视为启用,由get_cmdline()在内核命令行中匹配,见 scripts/zfs 第 28–34 行):
| 参数 | 作用 |
|---|---|
zfsdebug=(on,yes,1) | 输出额外调试信息:设置set -x,在挂载完成后列出已导入池与已挂载 ZFS 文件系统,并等待用户按键(c进入 shell、r重启、回车继续) |
zfsforce=(on,yes,1) | 强制导入池,等价于给zpool import追加-f(源码中同时接受zfs_force、zfs.force等变体写法) |
rollback=(on,yes,1) | 对快照执行回滚(zfs rollback -Rf)而非克隆,引导用回滚后的原数据集 |
调试模式的完整行为可参考mountroot()末尾的 DEBUG 分支(scripts/zfs 第 1053–1073 行)。
五、解锁加密的 ZFS 根文件系统
ZFS 原生加密根文件系统在 initramfs 阶段需要加载密钥才能挂载。脚本在挂载每个文件系统前会调用decrypt_fs()检查所在池的feature@encryption是否 active,进而读取encryptionroot、keylocation与keystatus属性决定如何加载密钥(scripts/zfs 第 432–509 行)。密钥提示顺序为:先尝试自定义 hook(见下文),keylocation != prompt时非交互加载,否则依次尝试 plymouth 密码框(3 次机会)、systemd-ask-password、以及普通 tty 提示(临时把printk提到 7 以突破quiet参数隐藏输出)。
5.1 通过 SSH 解锁
在无显示器或需要远程解锁的服务器上,可以使用 dropbear SSH 服务器在 initramfs 阶段提供远程登录,步骤如下:
- 安装
dropbear-initramfs软件包;若同时存在cryptsetup-initramfs,可以卸载它以避免警告; - 把 SSH 公钥加入
/etc/dropbear-initramfs/authorized_keys。注意:Dropbear 在 2020.79 版本之前不支持 ed25519 密钥,这种情况下请改用 RSA(2048 位及以上)密钥; - 重新生成 initramfs 以打包密钥:
update-initramfs -u; - 系统启动进入 initramfs 阶段后,通过 SSH 登录并运行
zfsunlock。
zfsunlock脚本(contrib/initramfs/zfsunlock)会等待根池导入(轮询/run/zfs_fs_name),然后循环提示输入密码并执行/sbin/zfs load-key,密码正确后返回“Password accepted”并继续等待下一个需要解锁的文件系统;全部解锁完成后写ok到/run/zfs_unlock_complete_notify通知启动流程恢复。其配套 hook(hooks/zfsunlock.in)负责把zfsunlock与 motd 拷入 initramfs,并在 prereqs 阶段声明依赖dropbear。同时,decrypt_fs()也会把当前待解锁数据集名写入/run/zfs_fs_name、把所用的提示方式写入/run/zfs_console_askpwd_cmd,供zfsunlock与之协同。
5.2 通过其他方式加载密钥(自定义 hook)
如果不想(或不能)交互式输入密码,可以在生成 initramfs 时提供一段 shell 程序:位于/etc/zfs/initramfs-tools-load-key的单个脚本,或/etc/zfs/initramfs-tools-load-key.d/*目录下任意匹配的文件。它们会在生成 initramfs 时被拷入镜像(见 hooks/zfs.in 第 44–46 行),并在需要加载密钥时被 source 执行。
执行环境中预置了以下变量:
$ENCRYPTIONROOT:需要加载密钥的数据集;$KEYLOCATION:密钥位置(即该数据集的keylocation属性值);- initramfs-tools 的全部函数均可用;
- 请使用不加引号的
$ZPOOL和$ZFS来调用zpool与zfs(这两个变量由 etc/zfs/zfs-functions.in 导出,不加引号是为了支持附加参数)。
语义约定:
- hook成功返回且密钥确实加载成功,则停止继续搜索,直接结束解锁流程;
- hook失败返回不致命,脚本会继续尝试下一个 hook;若所有 hook 均未成功,则回落到正常的密钥加载流程(即非交互
load-key或交互式密码提示)。
下面是一个极简示例:把keylocation指向文件的BLAKE2 校验和作为密钥,通过-L prompt方式喂给zfs load-key:
key="$(b2sum "${KEYLOCATION#file://}")" || return printf '%s\n' "${key%% *}" | $ZFS load-key -L prompt "$ENCRYPTIONROOT"这里KEYLOCATION#file://去除file://前缀得到文件路径,b2sum计算其 BLAKE2 摘要,${key%% *}提取哈希值本身,随后经管道交给zfs load-key作为提示输入(-L prompt)。该文件在 initramfs 生成期间会被拷贝(copy_file config),因此在 initramfs 环境中可读。
六、常见问题排查
- 池无法导入:脚本会打印执行的命令、错误输出与错误码,并落下交互 shell(优先使用 Debian 的
panic(),允许通过内核参数panic=0/panic=15禁止交互),提示手动执行zpool import -N <pool>后退出。 - 根文件系统未知:既没有
bootfs池属性也未在内核命令行指定时,脚本会给出提示并建议手动挂载,例如mount -o zfsutil -t zfs rpool/ROOT/system ${rootmnt}。 - 需要强制导入:多数情况下不应使用,但若确实需要,在内核命令行加
zfsforce=1即可按单次需求追加-f(/etc/default/zfs中的ZPOOL_IMPORT_OPTS则适用于“总是”追加选项的场景)。 - ZFS 模块加载时序:
load_module_initrd()会等待设备节点出现(wait_for_udev/wait_for_dev),并支持ROOTDELAY环境变量(对应ZFS_INITRD_PRE_MOUNTROOT_SLEEP)与ZFS_INITRD_POST_MODPROBE_SLEEP延迟,帮助在设备枚举较慢的硬件上稳定引导(scripts/zfs 第 285–345 行)。
七、小结
OpenZFS 的 initramfs 集成脚本让 Debian 系发行版能够可靠地从 ZFS 池启动:内核命令行九种根设备写法按优先级解析、池导入路径可配置、子数据集自动挂载、快照克隆/回滚引导、加密根可交互或自动解锁。以上能力均可在仓库中溯源:核心引导逻辑见 contrib/initramfs/scripts/zfs,镜像生成 hook 见 contrib/initramfs/hooks/zfs.in 与 contrib/initramfs/hooks/zfsunlock.in,用户侧默认配置与完整注释见 etc/default/zfs.in,公共函数库见 etc/zfs/zfs-functions.in。按本文步骤配置后,即可在 Debian/Ubuntu 类系统上实现一套完整、可回滚、可远程解锁的 ZFS 根文件系统启动环境。
- 操作系统
【免费下载链接】zfs
OpenZFS on Linux and FreeBSD
相关推荐
Linux 内核启动揭秘:深入理解 initrd 与 initramfs 的临时根文件系统
Linux 内核启动揭秘:深入理解 initrd 与 initramfs 的临时根文件系统 在 Linux 系统启动的早期阶段,内核需要一个临时的根文件系统来加
文档教程操作系统OpenZFS 从根文件系统引导(ZFS-on-root)实战指南:zfs-dracut 模块与内核命令行参数全解析
OpenZFS 从根文件系统引导(ZFS on root)实战指南:zfs dracut 模块与内核命令行参数全解析 导读 :本文基于 OpenZFS 仓库中的
操作系统Crazyflie-Firmware多机通信开发:P2P协议与分布式控制实现
Crazyflie Firmware多机通信开发:P2P协议与分布式控制实现 Crazyflie Firmware是适用于Crazyflie Nano四轴飞行器
嵌入式无人机机器人物联网RTOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考