Featured image of post Ubuntu Root on ZFS 引导修复

Ubuntu Root on ZFS 引导修复

Ubuntu Root on ZFS 引导修复

背景

ZFS 是一种先进的文件系统,具有快照、数据完整性保护等功能。Ubuntu 支持将根文件系统安装在 ZFS 上并进行自动化的配置。然而当你误删除某些文件或误操作分区属性后,可能出现无法引导分区的情况。

现象

我遇到的错误是内核文件无法定位的问题,启动时停留在错误提示,错误内容包括以下形式:

  • 错误:文件 “/ROOT/ubuntu_xxx@/6.0.14-33-generic” 未找到。
  • 错误:文件 “/BOOT/ubuntu_xxx@/6.0.14-33-generic” 未找到。
  • 错误:文件 “/ROOT/ubuntu_xxx@/boot/6.0.14-33-generic” 未找到。
  • 错误:您需要先加载内核。

造成的原因可能是引导系统无法正确的解析 EFI 分区下的文件路径映射关系,或者是使用了 GRUB 不支持的 ZFS 特性。

根据 zsh 命令记录回溯,我的错误源头为将 bpool 设置为 zstd 压缩算法,GRUB 不支持该压缩算法,导致无法加载内核文件。

我执行的错误操作为:

1
# zpool set feature@zstd_compress=enabled bpool # ❌ 错误操作,绝对不要模仿

然而一开始没意识到这一点,来回尝试改文件,导致引导彻底崩了,所以只能重建引导,写教程的机会来了。

另外,依据网络其他惨痛案例,GRUB 并不支持使用了 snapshot 功能的 bpool,如果对 bpool 创建了快照也会导致无法启动,这种情况需要进入到 Live USB 环境中删除相关 snapshots。

思路

首先要理解在哪里执行修改,然后理解如何执行修改。我们通过理解 Ubuntu Root on ZFS 的引导流程,来定位需要修改的文件位置,然后通过理解 GRUB 的引导流程,来定位需要执行的命令。

理解 Ubuntu Root on ZFS 引导流程

在较新版本的 Ubuntu(如 22.04 及以后版本)中,ZFS 可在系统安装时被选定用作根文件系统。Ubuntu 使用 GRUB 作为引导加载程序,并通过 EFI 分区引导系统。理解引导流程有助于我们定位问题。

分区结构

标准安装的 Ubuntu Root on ZFS 系统通常包含以下分区:

  • EFI 系统分区(ESP):用于存放引导加载程序 GRUB 与 GRUB 的配置文件,格式为 FAT32
  • Linux swap 分区:用于交换空间
  • ZFS boot 分区:用于存放 ZFS 引导文件(initrd.img,vmlinuz 以及相应配置文件,表现为 Solaris 启动类型)
  • ZFS root 分区:用于存放 ZFS 根文件系统(表现为 Solaris root 类型)

通过 lsblkfdisk -l 可以查看分区信息,例如:

1
2
3
4
5
设备              起点       末尾       扇区  大小 类型
/dev/nvme0n1p1    2048    1050623    1048576  512M EFI 系统
/dev/nvme0n1p2 1050624    5244927    4194304    2G Linux swap
/dev/nvme0n1p3 5244928    9439231    4194304    2G Solaris 启动(bpool)
/dev/nvme0n1p4 9439232 1953525134 1944085903  927G Solaris root(rpool)

目录挂载关系

文件系统通过挂载操作将分区内容映射到系统的目录结构中。与 boot 相关的挂载点结构为:

  • / 挂载 ZFS 根文件系统(rpool/ROOT/ubuntu_xxxxxx)
  • /boot 挂载 ZFS 引导文件系统(bpool/BOOT/ubuntu_xxxxxx)
  • /boot/efi 挂载 EFI 系统分区(/dev/nvme0n1p1)
  • /boot/grub 挂载 EFI 系统分区的 grub 目录(/dev/nvme0n1p1[/grub])(这是兼容性设计)

换句话说上述目录在未挂载前都应当是空的目录,挂载顺序为:

  1. 挂载 ZFS 根文件系统到 / (此时 /boot 和 /boot/efi 应当是空的)
  2. 挂载 ZFS 引导文件系统到 /boot (此时 /boot/efi 应当是空的)
  3. 挂载 EFI 系统分区到 /boot/efi (此时 /boot/grub 应当是空的)
  4. 挂载 EFI 系统分区的 grub 目录到 /boot/grub (此时 /boot/grub 应当包含 grub 文件)

正常状态下,各个分区各司其职,避免将不属于某个分区的文件保存到该分区下。

若在未挂载对应的分区时目录内本来就有文件,则可能造成我们的修改完全没用,所以避免在这些目录下创建或修改文件,除非明确已经是挂载后的状态。

通过 findmnt 命令查看挂载信息,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
➜  ~ findmnt /
TARGET
  SOURCE                   FSTYPE OPTIONS
/ rpool/ROOT/ubuntu_xxxxxx zfs    rw,relatime,xattr,posixacl,casesensitive

➜  ~ findmnt /boot
TARGET
      SOURCE                   FSTYPE OPTIONS
/boot bpool/BOOT/ubuntu_xxxxxx zfs    rw,nodev,relatime,xattr,posixacl,casesensitive

➜  ~ findmnt /boot/efi
TARGET    SOURCE         FSTYPE OPTIONS
/boot/efi /dev/nvme0n1p1 vfat   rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro

➜  ~ findmnt /boot/grub 
TARGET     SOURCE                FSTYPE OPTIONS
/boot/grub /dev/nvme0n1p1[/grub] vfat   rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro

在正常挂载的目录结构下,应当包含以下文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
➜  /boot ls  # 将显示 bpool/BOOT/ubuntu_xxxxxx 下的内容
config-6.14.0-33-generic  efi  grub  initrd.img  initrd.img-6.14.0-33-generic  memtest86+ia32.bin  memtest86+ia32.efi  memtest86+x64.bin  memtest86+x64.efi  System.map-6.14.0-33-generic  vmlinuz  vmlinuz-6.14.0-33-generic

➜  /boot/efi ls * # 将显示 EFI 系统分区下的内容
EFI:
BOOT  ubuntu

grub:
fonts  grub.cfg  grubenv  locale  x86_64-efi

➜  /boot ls grub # 将显示 EFI 系统分区的 grub 目录下的内容,可以看到与上面的 /boot/efi/grub 内容一致(通过 bind mount 挂载)
fonts  grub.cfg  grubenv  locale  x86_64-efi

理解 GRUB 引导流程

GRUB 是一个强大的引导加载程序,支持多种文件系统和操作系统。对于 Ubuntu Root on ZFS 系统,GRUB 需要能够识别 ZFS 存储池并正确加载内核和 initrd 文件。

  1. 开机:UEFI/BIOS 从 ESP 分区加载 GRUB (grubx64.efi)(在挂载的 /boot/efi/EFI/grub 目录下)。
  2. GRUB 启动:GRUB 加载必要的模块(part_gpt, zfs)。
  3. 寻找 bpool:GRUB 扫描所有磁盘,发现两个 ZFS 存储池:bpoolrpool。其中 bpool 的特性集是 GRUB 兼容的,GRUB 成功地导入了 bpool 与其下的数据集。
  4. 读取 grub.cfg
    • 首先从 ESP 分区中的 EFI 文件夹读取默认配置 /boot/efi/EFI//grub.cfg,该文件会指定真正完整的配置文件位置
      1
      2
      3
      4
      
      ➜  / cat /boot/efi/EFI/ubuntu/grub.cfg 
      search.fs_uuid E834-B7A9 root 
      set prefix=($root)'/grub'
      configfile $prefix/grub.cfg
      
      这个文件告诉 GRUB 去寻找 ESP 分区(通过 UUID 定位),然后在该分区的 /grub 目录下寻找 grub.cfg
    • 然后 GRUB 会继续读取 /grub/grub.cfg 文件(在 ESP 分区下,我们在正常启动的系统中看到的路径是 /boot/grub/grub.cfg),该文件包含了实际的引导菜单配置。
  5. 解析 menuentry:GRUB 读取了 grub.cfg,并开始执行 menuentry
  6. 执行 linux 命令:当 GRUB 看到 linux "/BOOT/ubuntu_xxxxxx@/vmlinuz-..." 时:
    • 它开始在其已导入的存储池中,寻找名为 BOOT 的数据集。
    • 它发现 bpool 中有这个数据集,于是就去 bpool/BOOT 下查找文件。路径中无需提及 bpool,因为数据集名 BOOT 在整个系统中(从 GRUB 的视角看)是唯一的。如果两个池都有 BOOT 数据集,GRUB会报错或选择第一个找到的,因此安装器会确保数据集命名的唯一性。
    • 它从 bpool/BOOT/ubuntu_xxxxxx 数据集中读取 vmlinuz 并加载到内存。
  7. 执行 initrd 命令:同理,从 bpool 中读取 initrd.img 加载到内存。
  8. 移交控制权:GRUB 将控制权交给内核,并传递 root=ZFS=rpool/ROOT/ubuntu_xxxxxx 这个参数。
  9. 内核和 initrd 接管:内核知道自己真正的根文件系统是 rpool 上的那个数据集。initrd 中的脚本会导入 rpool(它支持所有高级特性),并将其挂载为最终系统的根 /

修复引导

方法一:修正 bpool 属性(优先)

如果你跟我一样是因为启用了 GRUB 不支持的 ZFS 特性导致引导失败,可以尝试将 bpool 的相关属性恢复为默认值:

在 Ubuntu Live USB 环境下(在“操作说明”小节有详细流程),导入 bpool,修正属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
sudo apt update
sudo apt install zfsutils-linux  # 需联网,也可能Live USB自带ZFS支持

# 将bpool导入到/mnt目录下
sudo zpool import -N -R /mnt bpool
# 如果无法导入,使用以下命令
# sudo zpool import -o feature@zstd_compress=disabled -N -R /mnt bpool

# 修正属性
sudo zpool set feature@zstd_compress=disabled bpool

# 可进一步尝试执行兼容性设置,如果无法设置则在正常启动后执行
#(/usr/share/zfs/compatibility.d/ 目录下有更多选项)
# sudo zpool set compatibility=grub2 bpool

# 检查属性是否正确
sudo zpool get feature@lz4_compress,feature@zstd_compress,compatibility bpool

# 正确情况应当获得以下结果:
# NAME   PROPERTY               VALUE                  SOURCE
# bpool  feature@lz4_compress   active                 local
# bpool  feature@zstd_compress  disabled               local
# bpool  compatibility          grub2                  local

# 检查 GRUB 是否能识别 ZFS 文件系统
sudo grub-probe /mnt # 如果输出 zfs 则表示识别成功,可正常启动

# 如果输出“grub-probe: error: unknown filesystem.”,可尝试增加详细信息查看问题,定位到不支持的属性执行关闭
sudo grub-probe -vvvv /mnt

# 将修改写入磁盘
sudo zpool export bpool

然后直接重启,系统应该可以正常启动。

方法二:重建引导文件

如果还是无法启动,可能是引导文件损坏,可以尝试重建引导文件:

首先在 Ubuntu Live USB 环境下(在“操作说明”小节有详细流程),挂载 ZFS 池并 chroot 进入原系统,然后执行以下命令重建 GRUB 引导文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 注意,下面的路径是基于 chroot 进入原系统后的路径

# 下面这个操作会在 /boot/initrd.img-$(uname -r) 文件中重建 initramfs 文件
# 通过 findmnt 确认 /boot 挂载点确实是在 bpool/BOOT/ubuntu_xxxxxx(bpool 分区)才可有效执行操作
sudo update-initramfs -u -k $(uname -r)  # 可能需要联网

# 下面这个操作会重装 GRUB 到 EFI 分区中的指定目录下
# 通过 findmnt 确认 /boot/efi 挂载点确实是在 /dev/nvme0n1p1(EFI 分区)才可有效执行操作
sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck

# 下面这个操作会在 /boot/grub/grub.cfg 文件中重建引导配置
# 通过 findmnt 确认 /boot/grub 挂载点确实是在 /dev/nvme0n1p1[/grub] (EFI 分区下的 grub 目录) 才可有效执行操作
sudo update-grub

exit  # 退出 chroot 环境

sudo umount -R /mnt  # 卸载所有挂载点
sudo zpool export -a # 卸载所有 ZFS 池
sudo reboot

如果一切顺利,系统应该可以正常启动。如果仍然无法启动,毁灭吧,直接重建引导分区得了(不要怕,bpool 分区里没有用户数据)。

方法三:重建引导分区

在上述操作均不可行的情况或者想要创建干净的引导分区,可以选择重建引导分区。

下面的所有操作均在通过 Ubuntu Live USB 执行 chroot 操作后进入原系统环境进行。

备份已有分区(虽然没太有必要)

1
2
3
4
5
6
7
8
9
# 通过 findmnt 确认 /boot 挂载点 -> bpool/BOOT/ubuntu_xxxxxx(bpool 分区)
findmnt /boot

sudo mkdir -p /mnt/boot_backup
sudo cp -a /boot /mnt/boot_backup  # 备份 bpool 分区内容(到你想要的路径)

# 销毁 bpool 分区
sudo zpool export bpool
sudo zpool destroy bpool

重建 bpool 分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 确认 bpool 分区uuid
DISK=/dev/disk/by-id/nvme-CT1000P5SSD8_21092D567925 # 替换为你的磁盘标识符
PARTITION=${DISK}-part3  # 替换为你的 bpool 分区

# 创建新的兼容性 ZFS 池 bpool
sudo zpool create \
    -o ashift=12 \
    -o autotrim=on \
    -o cachefile=/etc/zfs/zpool.cache \
    -o compatibility=grub2 \
    -o feature@livelist=enabled \
    -o feature@zpool_checkpoint=enabled \
    -O devices=off \
    -O acltype=posixacl -O xattr=sa \
    -O compression=lz4 \
    -O normalization=formD \
    -O relatime=on \
    -O canmount=off -O mountpoint=/boot -R /mnt \
    bpool ${PARTITION}

# 忽略关于“不在指定的‘compatibility’特性集中的特性”的警告。

# 创建 bpool/BOOT/ubuntu_xxxxxx 数据集
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
zfs create -o mountpoint=/boot -o canmount=on bpool/BOOT/ubuntu_xxxxxx  # 替换为你的 ubuntu_xxxxxx 名称,与 rpool/ROOT/ubuntu_xxxxxx 一致

重建引导文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 先检查 /boot 挂载点,应当为 bpool/BOOT/ubuntu_xxxxxx
findmnt /boot

# 重新挂载 /boot/efi 分区
findmnt /boot/efi
mount /dev/nvme0n1p1 /boot/efi

# 重新挂载 /boot/grub 分区
mount --bind /boot/efi/EFI/grub /boot/grub

apt update

# 重新安装当前系统的通用内核包,它会自动带上你出问题前的最新内核
apt install --reinstall linux-image-generic

# 重建 initramfs 文件
update-initramfs -c -k $(uname -r)

# 重装 GRUB 到 EFI 分区中的指定目录下
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck

# 重建引导配置文件
update-grub

# 退出 chroot 环境
exit

# 卸载所有挂载点
sudo umount -R /mnt
sudo zpool export -a # 卸载所有 ZFS 池

sudo reboot

现在系统应该可以正常启动了。启动不了我也没辙了。

另外 GRUB 会有失败的启动后保留引导菜单选项的配置,正常重启两次后就可以直接进入系统不再需要等待选择菜单。

操作说明

Ubuntu Live USB 启动

在 Windows 上使用 Rufus 或其他工具创建 Ubuntu Live USB。确保选择的 ISO 镜像支持 UEFI 启动。 使用与原系统相同的 Ubuntu 版本(如 24.04)以确保兼容性。 插上 USB 启动电脑,进入 BIOS 设置 UEFI 模式并选择从 USB 启动。 重启后选择“Try Ubuntu”进入 Live 环境。

挂载 ZFS 池

在 Live 环境中打开终端,安装 ZFS 工具:

1
2
sudo apt update
sudo apt install zfsutils-linux  # 需联网,在新一点的Live USB中可能自带ZFS支持

挂载 ZFS 池:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sudo zpool export -a
sudo zpool import -N -R /mnt rpool
sudo zpool import -N -R /mnt bpool
sudo zfs load-key -a
# Replace “UUID” as appropriate; use zfs list to find it:
sudo zfs mount rpool/ROOT/ubuntu_UUID  # 会将原系统的根文件系统挂载到 /mnt
sudo zfs mount bpool/BOOT/ubuntu_UUID  # 会将原系统的引导文件系统挂载到 /mnt/boot
sudo zfs mount -a

sudo findmnt # 检查所有挂载点是否正确

Ubuntu Live USB 下通过 chroot 进入原系统

在挂载 ZFS 池后,执行以下命令进入原系统环境:

1
2
3
4
5
6
7
for dir in /dev /proc /sys; do sudo mount --make-private --rbind $dir /mnt$dir; done
sudo mount -t tmpfs tmpfs /mnt/run
sudo mkdir /mnt/run/lock
sudo chroot /mnt /bin/bash --login

# 现在已经进入到原系统的环境,可以执行需要依赖的操作
mount -a

执行操作后,需要退出 chroot 环境并执行与上述操作对应的卸载操作,即:

1
2
3
4
5
6
7
8
# 退出 chroot 环境
exit

# 在 Live USB 环境下卸载所有挂载点
sudo mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | \
    xargs -i{} sudo umount -lf {}
sudo zpool export -a
sudo reboot

GRUB 检查引导文件

在 chroot 进入原系统环境后,可以通过以下命令检查引导文件是否存在:

1
2
3
4
ls /boot/efi/EFI/ubuntu/grub.cfg
ls /boot/grub/grub.cfg
ls /boot/initrd.img-$(uname -r)
ls /boot/vmlinuz-$(uname -r)

GRUB 操作说明

下面对 GRUB 中一些常用的操作的输入、输出、执行的动作进行陈述,以明确这些命令会对什么目标做出何种操作。

GRUB-INSTALL 命令

grub-install 命令用于将 GRUB 引导加载程序安装到指定的设备或分区中。它会将必要的 GRUB 文件复制到 EFI 系统分区,并配置引导项。它的输入是 具体的,命令格式如下:

1
sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck
  • --target=x86_64-efi:指定目标平台为 64 位 UEFI 系统。
  • --efi-directory=/boot/efi:指定 EFI 系统分区的挂载点。
  • --bootloader-id=ubuntu:指定引导加载程序的标识符。
  • --recheck:强制重新检测设备。

这一命令会基于上述输入,在 /boot/efi/EFI/ubuntu/grubx64.efi 创建 GRUB UEFI 引导文件

UPDATE-GRUB 命令

update-grub 命令用于生成或更新 GRUB 的配置文件(grub.cfg)。它会扫描系统中的内核和其他操作系统,并生成相应的引导菜单项。它的输入是当前系统的内核和操作系统信息,命令格式如下:

1
sudo update-grub

意外情况处理

启动中无法导入 ZFS 池

如果 grub 启动中无法导入 ZFS 池,可以按提示强制手动导入

1
zpool import -a -f

参考资料

[OpenZFS 官方文档: Ubuntu 22.04 Root on ZFS]

[OpenZFS 官方文档: Debian Bookworm Root on ZFS]

[Arch Linux 中文 Wiki: 在 ZFS 上安装 Arch Linux]

[Recover ZFS boot disk in Ubuntu 22.04.4]

Licensed under CC BY-NC-SA 4.0
最后更新于 Oct 26, 2025 21:23 CST
使用 Hugo 构建
主题 StackJimmy 设计