Btrfs:认识、从Ext4迁移与快照方案

对于Arch系等依赖滚动更新的发行版,Btrfs的快照功能真的是太具有吸引力了。纵使我已经很久没有遇到“滚炸”、纵使就算“滚炸”去Manjaro论坛看一眼一般都能解决,但是这些都不如一个“后悔药”来得实在——遇到问题,重启、选择老快照、恢复,一切都是那么美好。因此,前阵子(指12月中旬)我就把系统分区迁移到Btrfs上了。这篇博客就主要记录了迁移与快照的各种实现方案。

Btrfs:现代Linux文件系统

概述

本着Arch精神,我还是想先简单介绍一下Btrfs。Btrfs(我一般念B tree FS)是最早由Oracle贡献的Linux文件系统,如今已经进入Linux内核许久,是最有希望(我认为)成为未来Linux主流文件系统的候选者。相较之下,类似的ZFS有License问题;潜力十足的ReiserFS因为作者谋杀入狱后开发进度就不太乐观,且v4也没被合入内核;XFS走的是类似Ext4的稳定路线,对新功能的支持较为保守(但是足够稳定,可以用于数据盘)。这个开源软件的命运啊,还真是不可预料。

Btrfs功能繁多,可以称得上是新文件系统功能的试验田。到目前为止,Btrfs已经有了包括但不限于如下的功能:

  • 写时复制(CoW)、事务
  • 在线卷管理:碎片整理、大小调整、跨设备卷中物理设备的增删
  • 子卷:可以单独挂载的文件树,类似LVM的逻辑卷
  • 软件RAID
  • 透明压缩:写磁盘时自动压缩
  • 快照
  • 支持数据校验和

组合这些功能,可以实现很多非常不错的功能。比如开启透明压缩、CoW后跨网络同步磁盘,可以最大限度的减少需要传输的数据量。此外,Btrfs也提供了一个非常不错的CLI接口来进行管理。不过Btrfs的缺点也很明显:数据恢复难度显著大于Ext4等传统FS、稳定性一般、读写速度也相对较差(主要是HDD,SSD基本没有问题)。这些问题就算到了Btrfs上述功能已经完成了将近15年的今天,也仍旧需要改善。好在目前Facebook、群晖等企业都在使用Btrfs,未来还是明朗的。

不过,这些小问题并不能阻挡我作死尝鲜的步伐。因为把系统分区迁移到Btrfs是有若干好处的:

  1. 快照是滚动更新的后悔药
  2. 大量系统文件并不性能敏感,开启透明压缩能节省不少空间
  3. CoW对SSD硬盘十分友好,而Btrfs的性能劣势在SSD的补足下并不明显(相反HDD可能有40%的性能损失)
  4. 系统分区也没什么重要数据,炸了也无所谓

子卷和快照

Btrfs一个非常重要的概念就是子卷。子卷可以理解成为“盘中盘”,可以单独挂载,行为类似一个文件夹(可以移动,但不能unlink)。子卷是可以嵌套的,不过此时子卷相当于同时挂载在一颗文件树,因此并不会循环嵌套。子卷可以根据其相对于根子卷(Btrfs分区自带的子卷,路径是/,ID是5)的路径确定(比如/subA),不过因为子卷的路径可以被移动,因此具体子卷是通过ID定位的。

快照就是基于子卷的,创建快照等同于创建一个和原子卷共享文件的子卷。因此其实快照和原子卷的地位是平等的,创建快照后删除原子卷也没有什么问题。将快照功能与根据路径查找子卷结合,就可以实现对某个路径的快照了:把子卷/sub挂载到/dest,快照时就创建一个快照子卷/sub_202201,如果要恢复快照就用某个快照子卷替换掉子卷/sub

从Ext4迁移

子卷规划

了解了Btrfs之后,就可以考虑如何规划子卷了。区分不同子卷的主要目的是为Btrfs提供的大量功能划定作用的粒度。比如对于系统文件(比如配置等等),我们可能不太在意它的存取效率,因此可以划分一个单独的子卷并在挂载的时候开启压缩;对于日志之类的文件,则没有快照的意义,因此另立子卷不进行快照。

参考Ubuntu风格的命名方式,我推荐如下的子卷格式:

  • @:根子卷,对应/。存放需要快照的系统文件
  • @home:家子卷,对应/home
  • @cache:缓存子卷,对应/var/cache。包含Pacman包缓存等没必要快照的文件
  • @log:日志子卷,对应/var/log。不快照日志,方便查错

这个规划方式主要是针对Manjaro等Arch系发行版。如果有其他需要,也可以添加如@opt@srv等子卷,反正不要钱也不麻烦(openSUSE默认有9个子卷)。

迁移

规划完子卷后,迁移时按图索骥即可。不过由于我只是想用到Btrfs的快照功能,加之比较担心自己的数据,因此并没有迁移家目录。如果有此需求,请自行扩充迁移过程。此外,我也不建议通过迁移工具来直接把ext4分区转为btrfs分区,因为至少在本文写作时还有见到迁移后使用不稳定的报告。

所以,我最终采用的是Timeshift的迁移方式,也可以使用等价的rsync指令。首先备份好根分区。之后,建立btrfs分区并建立根子卷@(假设目标分区是/dev/nvme0n1p1)。

sudo mkfs.btrfs /dev/nvme0n1p1  # 这一步也可以在分区工具里完成
sudo mount /dev/nvme0n1p1 /mnt/btrfs
sudo btrfs subvolume create /mnt/btrfs/@

然后就可以进入Live CD,把原系统分区扬掉了。之后通过Timeshift或rsync把备份好的根分区恢复到目录/mnt/btrfs/@,这样就完成了文件的迁移。接下来,逐一的建立子卷。比如要建立/var/cache对应的@cache子卷:

sudo btrfs subvolume create /mnt/btrfs/@cache
mv -v /mnt/btrfs/@/var/cache/* /mnt/btrfs/@cache

完成子卷建立后,还需要修改fstab以正确挂载(这里编辑的是/mnt/btrfs/@/etc/fstab)。注意每个子卷都需要增加对应的记录,但是对应的磁盘分区UUID都是同一个,指向Btrfs分区。分区UUID可以通过blkid指令查询。此外,非ssd磁盘需要去掉ssd选项。

UUID=...	/	btrfs	defaults,noatime,ssd,subvol=@	0	1
UUID=...	/var/log	btrfs	defaults,noatime,ssd,subvol=@log	0	1
UUID=...	/var/cache	btrfs	defaults,noatime,ssd,subvol=@cache	0	1

最后更新一下引导就可以了。

manjaro-chroot /mnt/btrfs/@
grub-install --target=x86_64-efi --efi-directory=EFI文件夹 --bootloader-id=manjaro --recheck

此时重启系统就应该在根子卷了。

系统快照方案

需求

简单梳理了下需求,并按照重要程度排序:

  • 方便的实时创建快照、实时回滚
  • 可以定时备份、清理
  • 可以启动进快照,以备滚炸之需
  • 在滚动更新前自动创建快照

各种方案们

经过了解与分析,主要的方案有如下几种。

  • Ubuntu风格:使用Timeshift,只能识别@@home子卷
  • openSUSE风格:使用Snapper,可以作用于任何子卷,快照存放在默认的@/.snapshot
  • Arch Linux风格:使用Snapper,可以作用于任何子卷,但创建独立的@snapshot子卷

权衡利弊

至于为什么会有那么多方案?没错,因为每一种都多少有点缺陷,所以就有一帮人出来造轮子……

首先是Timeshift的解决方案,这个方案就非常有Ubuntu的稳定气质。它强制了子卷风格必须是@@home,也没有提供快照的复杂功能,但是在用户体验上做的很好(尤其是GUI)。在启动进快照时会自动弹出窗口提示回滚,在Live CD里也可以进行恢复操作。因此,对于安稳的使用来说,我最推荐的也就是Timeshift方案了(这也是Manjaro默认的方案)。

Snapper和Timeshift就很不同了,比起Timeshift做的类似TimeMachine的系统恢复工作,Snapper更像是一个快照管理工具。它提供了丰富的快照管理方式:可以设定快照类型,并按类型定制自动清理规则;支持pre-post的方式管理快照,即在进行某操作的前后进行快照;甚至可以分析两个快照之间文件系统发生的变化,可以说是非常的Geek。但更Geek的是,它的GUI甚至不包含快照恢复功能。对于openSUSE,快照恢复可以通过snapper rollback解决;而对于Arch Linux风格,你甚至需要手动输入一堆指令回滚。至于支持Live CD回滚?想都别想,老老实实敲指令!

究其因,Snapper是openSUSE的人做的,但是它们的办法却并不是很Arch。这里的分歧在于快照子卷。Arch Linux社区认为,恢复快照的方式应该是这样:首先删除老的子卷(@),然后“复制”某个快照为新的子卷(实际操作也是一次快照)。但是openSUSE社区的思路完全是逆转过来的,它们认为为啥要把系统放在固定的@子卷呢?不如直接把系统放在一个可写入的快照里面(比如@/.snapshot/0)!每次回滚,比如说回滚到快照6,就当场给快照6创建一个可写快照8,然后我的系统就直接变成了快照8(比如@/.snapshot/8)。相当于所有快照构成一系列世界线,然后真的系统待在其中一个上。但问题是,这样需要修改grub的源码,这就很不Arch了。所以Arch Linux Wiki里给出的Snapper用法就是用Snapper管理快照,但是独立快照子卷且手动恢复快照。由于这是违背Snapper设计目的的用法,因此部分功能就是有问题的(rollback命令不可用、无法识别当前所在的快照)。不过我自己设计了一个Workaround,因此也能在Manjaro下实现openSUSE的方法,各位可以看完具体方案再做决定。

Ubuntu风格:Timeshift方案(推荐)

配置流程

非常滴简单,你只需要:

sudo pacman -S timeshift timeshift-autosnap-manjaro grub-btrfs
sudo systemctl enable --now grub-btrfs.path

然后打开Timeshift,按照配置向导的Btrfs设置即可。此时使用pacman安装包也会创建快照,如果不需要创建快照,就在指令之前加上SKIP_AUTOSNAP=。还可以编辑/etc/timeshift-autosnap.conf文件来控制快照行为:

  • 设置updateGrubfalse,因为不更新也会自动触发
  • maxSnapshots可以设置大一点,免得安装时覆盖了滚系统前的快照。我设置了5

如果你使用的不是Manjaro,那安装timeshift-autosnap之后需要使用sudo systemctl edit grub-btrfs.path并修改为:

[Unit]
Description=Monitors for new snapshots of timeshift
DefaultDependencies=no
Requires=run-timeshift-backup.mount
After=run-timeshift-backup.mount
BindsTo=run-timeshift-backup.mount

[Path]
PathModified=/run/timeshift/backup/timeshift-btrfs/snapshots

[Install]
WantedBy=run-timeshift-backup.mount

Troubleshooting

如果创建快照时卡顿

建议在设定里关闭启用配额组功能,然后运行sudo btrfs quota disable /

如果KDE下无法浏览快照文件

新版本KDE应该不会出现这个问题。如果打不开文件夹,可以安装一个其他文件浏览器,比如Nemo。

rEFInd支持快照启动

虽然有轮子,但是说实话效果一般且难看。所以我建议rEFInd引导Grub,然后Grub负责快照。如果实在看不惯两个界面,建议编辑/etc/default/grub

  • GRUB_TIMEOUT=2,就留2秒时间
  • GRUB_TIMEOUT_STYLE=hidden,黑屏显示,2秒内按Shift可选择快照

Arch Linux/openSUSE风格:Snapper方案

配置

两种方案的麻烦程度大差不差。硬要说的话,我个人更喜欢openSUSE的方式,因为它能运用到Snapper本身的全部功能。虽然看完过程很可能被劝退,但是其实我真的挺喜欢Snapper强大的功能,非常值得一试。

1. 安装Snapper

sudo pacman -S snapper snapper-gui
sudo snapper -c root create-config /

2. Arch Linux风格特有的配置

虽然看着很麻烦,但其实基本照做即可(假设目标分区是/dev/nvme0n1p1)。大致的操作就是把Snapper创建的@/.snapshots删掉,替换成独立的子卷@snapshots

sudo umount /.snapshots
sudo rm -r /.snapshots
sudo btrfs subvolume delete /.snapshots
sudo mkdir /.snapshots
sudo mount -o subvol=/ /dev/nvme0n1p1 /mnt
sudo btrfs subvolume create /mnt/@snapshots

然后修改/etc/fstab,参考子卷的方式增加一条将子卷subvol=@snapshots挂载到/.snapshots的即可。运行sudo mount -a生效。

3. Snapper配置

首先是配置定时快照与定时清理。编辑/etc/snapper/configs/root(也可以通过GUI配置:sudo snapper-gui),可以参考这个配置:

TIMELINE_MIN_AGE="1800"
TIMELINE_LIMIT_HOURLY="0"
TIMELINE_LIMIT_DAILY="2"
TIMELINE_LIMIT_WEEKLY="4"
TIMELINE_LIMIT_MONTHLY="4"
TIMELINE_LIMIT_YEARLY="1"

然后启用定时任务:

sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer

然后需要配置允许非root用户创建、管理快照。也是先编辑/etc/snapper/configs/root,在ALLOW_USERS里增加自己的用户(whoami)。然后修改快照目录的权限:

sudo chmod 0750 /.snapshots/
sudo chown :用户 /.snapshots/


重启生效。

4.openSUSE风格特有配置

主要的操作是把系统转移进某一个可写的快照。

  1. 为了使用manjaro-chroot,安装包manjaro-tools-base(运行sudo pacman -S manjaro-tools-base)。其实不装也行,把指令换成chroot也一样。
  2. 编辑/etc/fstab,把挂载根目录一行的subvol=/去掉;参考子卷的方式增加一条将子卷subvol=/@/.snapshots挂载到/.snapshots的配置。
  3. 创建一个快照作为当前的系统快照:snapper create --read-write --type single -d "Init snapshot"。在指令输出中应该可以看到创建快照的快照号,把它设置为当前快照:sudo btrfs subvolume set-default /.snapshots/快照号/snapshot
  4. 更新一下Grub,让系统启动到这个快照:manjaro-chroot /.snapshots/快照号/snapshot bash -c "sudo grub-install && sudo update-grub"
  5. 重启。然后看看findmnt /里面显示的子卷是不是设置的那个快照号。
  6. 如果是,那就来一个刺激的狠活。先挂载它sudo mount -o subvol=/ /dev/nvme0n1p1 /mnt,然后进行一个巨大删除sudo rm -rf /mnt/@/*(也可以手动删除,不过千万别把.snapshot删了)。

5.配置Grub启动到快照的菜单

安装grub-btrfs包(sudo pacman -S grub-btrfs),运行sudo systemctl edit grub-btrfs.path并修改为:

[Unit]
Description=Monitors for new snapshots of snapper
DefaultDependencies=no
Requires=
After=
BindsTo=

[Path]
PathModified=/.snapshots

[Install]
WantedBy=multi-user.target

然后运行即可:sudo systemctl enable --now grub-btrfs.path

快照回滚

之前提到过,Snapper的快照回滚并不是一桩易事。不过这些问题都可以通过写脚本来解决。

Arch Linux风格

先来看看手动的方式吧(假设分区是/dev/nvme0n1p1):

sudo mount /dev/nvme0n1p1 /mnt
sudo btrfs subvolume snapshot /mnt/@ /mnt/@bad
sudo btrfs subvolume delete /mnt/@
sudo btrfs subvolume snapshot /mnt/@snapshots/要恢复的快照号/snapshot /mnt/@

要恢复的快照号需要手工检查,如果在快照中可以通过snapper ls查看。如果在Live CD中,可以通过精准的猜测和一点运气和奇迹逐个查看快照文件夹内的xml得到。

由于过程确实很繁琐,于是我就写了一个脚本,建议保存在/usr/local/bin/rollback,这样就可以rollback 快照号了。脚本会创建一个快照,然后进行恢复操作。如果当前不在快照,还会创建@old子卷保存当前系统,需要重启后手动删除。当然,这个脚本可以在Live CD里使用。

#!/bin/sh
set -e
if [[ x"$1" == x ]]; then
  echo "No snapshot number given." 1>&2
  echo "Usage: rollback [snapshot to rollback]" 1>&2
  exit 1
fi
root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'`
root_subvol=`findmnt -n -o SOURCE / | sed 's/.*\[\(.*\)\].*/\1/'`
echo ">= Rollback to #$1 on device $root_dev"
# create snapshot before
sudo snapper create --read-only --type single -d "Before rollback to #$1" --userdata important=yes
sudo mount -o subvol=/ $root_dev /mnt
# check enviornment
if [[ x"$root_subvol" == x/@ ]]; then
  echo "Warning: Not run in a snapshot, a subvolume @old will be created. You should consider remove it after reboot." 1>&2
  if [[ -d /mnt/@old ]]; then
    echo "Found last @old, remove it."
    sudo btrfs subvolume delete /mnt/@old >/dev/null
  fi
  sudo mv /mnt/@ /mnt/@old
else
  sudo btrfs subvolume delete /mnt/@ >/dev/null
fi
sudo btrfs subvolume snapshot /mnt/@snapshots/$1/snapshot /mnt/@ >/dev/null
sudo umount /mnt

openSUSE风格

如果你在用openSUSE,那启动进一个快照后运行snapper rollback就完事了,但可惜我们并不是openSUSE。因为正常的Grub并不能在不更新的情况下启动到默认子卷,因此只能手动更新它了。同样,用一个脚本来完成这些操作,同样建议保存在/usr/local/bin/rollback。不过就不需要手动指定快照号了,直接rollback即可。

#!/bin/sh
set -e
root_dev=`findmnt -n -o SOURCE / | sed 's/\[.*\]//g'`
sudo snapper rollback $1
echo ">= Update GRUB on $root_dev"
sudo mount $root_dev /mnt
if [[ -d /boot/efi ]]; then
  manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=manjaro --recheck
else
  read -p "Enter MBR device (e.g. /dev/sda): " install_dev
  manjaro-chroot /mnt grub-install --force --target=i386-pc --recheck --boot-directory=/boot $install_dev
fi
manjaro-chroot /mnt update-grub
sudo umount /mnt

同样,脚本能在Live CD里用。此外,虽然如今PC分区一般都不会是MBR格式,但如果真是则还需要手动输入引导设备。

openSUSE风格在Live CD中的快照回滚

还是基于之前的原因,本来很简单的操作因为Grub的缘故还要增加一步更新操作。此外,Live CD中就需要指定快照号了,确定方法和Arch的一样。不过还好,一般的小场面也用不到Live CD。

sudo mount /dev/nvme0n1p1 /mnt
sudo btrfs subvolume set-default /mnt/@snapshots/快照号/snapshot
manjaro-chroot /mnt grub-install --target=x86_64-efi --efi-directory=EFI文件夹 --bootloader-id=manjaro --recheck

成功进去系统后建议运行一次rollback

Troubleshooting

如果创建快照、列出快照时卡顿

真的巨卡无比,建议运行sudo btrfs quota disable /就没事了。

rEFInd支持快照启动

类似Timeshift,理由、解决方案也一样。

Reference

  1. https://forum.manjaro.org/t/howto-btrfs-and-snapper/25417
  2. https://wiki.archlinux.org/title/snapper
  3. https://bbs.archlinux.org/viewtopic.php?id=194491
  4. https://www.reddit.com/r/openSUSE/comments/l015pb/snapper_rollback_problem/
  5. https://forum.manjaro.org/t/btrfs-snapper-the-suse-way-with-rollback/52279
  6. https://wiki.manjaro.org/index.php/Restore_the_GRUB_Bootloader
  7. https://github.com/teejee2008/timeshift/issues/606
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

  1. 没有图片
  2. 没有图片

评论

还没有评论。

在此评论中不能使用 HTML 标签。