Btrfs是一个相对较新的文件系统,它提供了许多先进的功能,如CoW(写时文件复制)、快照、压缩、校验和等。然而,它在Ubuntu中的支持并不完善,因此在安装Ubuntu到Btrfs中时,需要注意一些细节。

为什么选择Btrfs而不是ZFS

Btrfs与ZFS的详细对比可以参考这篇文章

ZFS是另一个先进的文件系统,它提供了与Btrfs类似的功能,并且在某些场景下有更高的灵活性和性能。但是使用ZFS作为系统盘目前有以下问题:

  • ZFS并不在Linux内核中,如果将系统安装至ZFS,必须手动通过模块的方式加载ZFS模块。若系统出现异常,如何在救援模式中访问zfs pool也是一个问题。而Btrfs是Linux内核的一部分,目前绝大多数的linux发行版都提供了开箱即用的Btrfs支持。
  • ZFS的内存需求较高,可能会占用更多的系统资源。
  • ZFS目前仅在Ubuntu Desktop 24.04安装中提供极为有限的实验性支持(必须清除整块硬盘进行安装), 在Ubuntu Server乃至其它发行版中很少提供官方支持。在安装Arch Linux时也必须对照专门的文档添加zfs特定的模块才可使用。而Btrfs已在openSUSE中作为默认文件系统多年,并且目前已被大多数Linux发行版支持。

因此我认为至少在现阶段,使用Btrfs作为系统盘是一个更好的选择。

安装Ubuntu到Btrfs中

这个过程与安装到其它文件系统并没有太大的区别,无论你在安装的是Ubuntu Desktop还是Ubuntu Server, 都可以按照正常的安装流程进行。唯一需要注意的是在安装过程中选择Btrfs作为文件系统。

将Btrfs下的Ubuntu系统转换为子卷格式

Ubuntu 24.04不再使用子卷格式来安装Ubuntu,而是将所有文件安装在根子卷中。这种安装方法将失去对系统不同文件分卷管理的很多优势,如无法单独对用户数据进行快照、无法单独对系统文件进行快照等。

你可以通过以下命令来确认当前btrfs卷中的子卷情况:

1
sudo btrfs subvolume list /

如果你的系统中只有一个根子卷,建议通过下面的方式将系统转换为子卷格式,以便于管理和备份。如果你的系统已经使用了子卷格式,可以跳过这一步。

在下面的步骤中,我们会创建以下子卷并将其挂载到对应目录:

子卷名称挂载目录用途
@/系统根目录
@home/home用户数据
@cache/var/cacheapt等缓存
@log/var/log日志
@swap/swapfileswap文件
@snapshots/.snapshots根目录的快照

对于/tmp目录,我们将直接使用tmpfs进行挂载,根据此文章,使用tmpfs可以提高性能并减少对磁盘的写入。

以下操作会对系统所在的Btrfs卷进行修改,请确认你输入了正确的命令并谨慎操作。操作失误可能导致系统故障甚至数据丢失。

如果系统分区中有重要的业务数据,建议提前进行备份。可以使用umount取消挂载无关的卷,以减少操作风险。

建议准备任意发行版的LiveCD,以便在系统无法启动时进行救援。

以下操作需要重新安装grub(grub-install)并更新grub配置(update-grub),请确保你清楚grub所在的位置(Legacy中的引导设备或UEFI中的EFI分区)。

以下操作要求您具有一定的Linux与Btrfs基础知识,如编辑/etc/fstab、挂载或取消挂载卷、进行grub-install等操作。建议在开始前先完整阅读以下步骤,确保您理解每一步的含义。

以下方式参考了此文章

  1. 如果你的btrfs卷中存放了swap文件,需要先关闭swap并删除swap文件以便于对根目录进行快照:

    使用swapon --show查看当前的swap文件,然后使用swapoff -a关闭swap文件。

    删除swap文件,我们会在后续的步骤中重新创建swap文件(如果需要)。

    1
    2
    sudo swapoff -a
    sudo rm /swap.img # Change the path to your swap file

    编辑/etc/fstab文件,删除或注释swap文件的挂载项。

    1
    sudo vi /etc/fstab
  2. 对根目录进行快照:

    1
    sudo btrfs subvolume snapshot / /@

    我们接下来的所有操作都会在这个快照上进行,以保证不会干扰到目前正在运行的系统。但是这也意味着从执行这个命令后根子卷的所有文件变更都不会保留到重启后的系统中。

  3. 创建所需的其它子卷,并将文件移动到对应的子卷中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    sudo btrfs subvolume create /@home
    sudo mv /@/home/* /@home

    sudo btrfs subvolume create /@cache
    sudo mv /@/var/cache/* /@cache

    sudo btrfs subvolume create /@log
    sudo mv /@/var/log/* /@log

    sudo btrfs subvolume create /@swap
    sudo mkdir /@/swapfile # Create a directory for the mount point

    sudo btrfs subvolume create /@snapshots
    sudo mkdir /@/.snapshots # Create a directory for the mount point

    这里可以顺便清空tmp目录,我们稍后将使用tmpfs来挂载:

    1
    sudo rm -rf /@/tmp/*
  4. 编辑/etc/fstab,设置各个子卷的挂载点信息。

    1
    sudo vi /@/etc/fstab

    找到类似如下的根目录的挂载项(其中UUID为btrfs卷所在分区标识符):

    1
    /dev/disk/by-uuid/<UUID> / btrfs defaults 0 1

    将其更改为:

    1
    /dev/disk/by-uuid/<UUID> / btrfs subvol=@,defaults 0 1

    复制该行,并依次添加以下子卷的挂载项:

    1
    2
    3
    4
    5
    /dev/disk/by-uuid/<UUID> /.snapshots btrfs subvol=@snapshots,defaults 0 1
    /dev/disk/by-uuid/<UUID> /home btrfs subvol=@home,defaults 0 1
    /dev/disk/by-uuid/<UUID> /swapfile btrfs subvol=@swap,defaults 0 1
    /dev/disk/by-uuid/<UUID> /var/cache btrfs subvol=@cache,defaults 0 1
    /dev/disk/by-uuid/<UUID> /var/log btrfs subvol=@log,defaults 0 1

    添加/tmp路径的挂载项:

    1
    tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0

    最后fstab文件应该类似如下(UUID为你的Btrfs分区对应UUID):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /dev/disk/by-uuid/26d85121-ee34-4266-8dcf-dc73292f7d2b / btrfs subvol=@,defaults 0 1
    /dev/disk/by-uuid/26d85121-ee34-4266-8dcf-dc73292f7d2b /.snapshots btrfs subvol=@snapshots,defaults 0 1
    /dev/disk/by-uuid/26d85121-ee34-4266-8dcf-dc73292f7d2b /home btrfs subvol=@home,defaults 0 1
    /dev/disk/by-uuid/26d85121-ee34-4266-8dcf-dc73292f7d2b /swapfile btrfs subvol=@swap,defaults 0 1
    /dev/disk/by-uuid/26d85121-ee34-4266-8dcf-dc73292f7d2b /var/cache btrfs subvol=@cache,defaults 0 1
    /dev/disk/by-uuid/26d85121-ee34-4266-8dcf-dc73292f7d2b /var/log btrfs subvol=@log,defaults 0 1

    tmpfs /tmp tmpfs rw,nosuid,nodev,mode=1777 0 0 # Use tmpfs for tmp directory

    # /boot/efi was on /dev/sdj1 during curtin installation
    /dev/disk/by-uuid/29F8-F222 /boot/efi vfat defaults 0 1
  5. 编辑grub菜单超时配置,以便于在启动时编辑grub菜单:

    1
    sudo vi /etc/default/grub

    找到并修改下面选项:

    1
    2
    GRUB_TIMEOUT=10
    GRUB_TIMEOUT_STYLE=menu

    更新grub配置:

    1
    sudo update-grub
  6. 重启系统。

    1
    sudo reboot
  7. 当grub菜单出现时,按下e键编辑grub启动项。

    找到类似如下的启动项:

    找到linux开头的行,为其路径添加/@/前缀:

    1
    linux /@/boot/...

    然后在后面为其添加rootflags=subvol=@参数,类似如下:

    1
    ro rootflags=subvol=@ quiet splash

    修改完成后的行应类似如下:

    1
    2
    linux   /@/boot/vmlinuz-6.8.0-40-generic root=UUID=<UUID> ro rootflags=subvol=@ quiet splash   # Ubuntu Desktop
    linux /@/boot/vmlinuz-6.8.0-40-generic root=UUID=<UUID> ro rootflags=subvol=@ # Ubuntu Server

    接着修改initrd行,同样为其路径添加/@/前缀:

    1
    initrd /@/boot/...

    完成后按下F10开始引导。

  8. 进入系统后,确认目前的根目录由@子卷挂载:

    1
    mount | grep ' / '

    输出应类似如下:

    1
    /dev/nvme0n1p1 on / type btrfs (...subvol=/@...)

    如果输出中没有subvol=@,则说明根目录没有正确挂载,需要检查之前的步骤是否有错误。可以再次重启系统,按照步骤7中的方式编辑grub启动项。

    你可以使用sudo btrfs subvolume list /来查看当前的子卷情况,结果应类似如下:

    1
    2
    3
    4
    5
    6
    ID 258 gen 4629 top level 5 path @
    ID 259 gen 4616 top level 5 path @home
    ID 261 gen 4475 top level 5 path @swap
    ID 354 gen 4519 top level 5 path @cache
    ID 355 gen 4546 top level 5 path @snapshots
    ID 430 gen 4629 top level 5 path @log
  9. 重新安装grub,来永久应用新的挂载方式:

    对于UEFI引导方式:

    1
    2
    3
    4
    sudo grub-install --efi-directory=/boot/efi
    sudo update-grub

    reboot

    对于Legacy引导方式:

    1
    2
    sudo grub-install /dev/sdX # Change the device to your boot device
    sudo update-grub
  10. 清除迁移前的系统文件:

    首先需要挂载btrfs根卷:

    1
    2
    3
    4
    5
    sudo mkdir -p /mnt/sysdrv
    sudo mount /dev/nvme0n1p1 /mnt/sysdrv # Change the device to your root device

    cd /mnt/sysdrv
    ls -la

    你应该可以在ls的结果中看到我们刚刚创建的子卷,同时也包含了迁移前的系统文件:

    1
    2
    3
    4
    5
    6
    7
    8
    '@'/
    '@cache/'
    '@home/'
    ...
    bin/
    boot/
    usr/
    ...

    我们需要删除迁移前的系统目录,但是保留刚刚创建的所有子卷,在bash中运行以下命令:

    1
    2
    3
    4
    5
    6
    7
    shopt -s extglob # Enable extended globbing

    sudo echo !(@*) # Make sure you are deleting the correct files

    sudo rm -rf !(@*) # Perform the deletion

    shopt -u extglob # Disable extended globbing

    请确保你已经正确挂载了根子卷,并且确认你要删除的文件是正确的。在echo的结果中确保没有删除不应删除的文件。

    到这里为止,你的系统已经成功迁移到子卷格式下。

  11. 如有必要,重新创建swap文件:

    1
    2
    3
    4
    5
    6
    7
    8
    cd /swapfile
    sudo touch swap.img
    sudo chattr +C swap.img # Disable CoW on the swap file
    sudo fallocate -l 8G swap.img # Change the size to your needs
    sudo chmod 600 swap.img
    sudo mkswap swap.img

    sudo vi /etc/fstab

    编辑/etc/fstab文件,添加swap文件的挂载项:

    1
    /swapfile/swap.img     none    swap    sw      0       0

    使用swapon -a命令启用swap文件:

    1
    2
    sudo swapon -a
    sudo swapon --show
  12. 安装Snapper用于管理快照:

    1
    sudo apt install snapper

    使用下面命令创建当前根目录的快照配置文件:

    1
    sudo snapper -c root create-config /

    Snapper会自动在运行apt命令等操作时创建快照,你可以使用sudo snapper list来查看当前的快照列表。 也可以使用sudo snapper create手动创建快照。

    关于Snapper的更多用法,建议参考Arch Wiki