文章目录 [隐藏]
对于 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 是有若干好处的:
- 快照是滚动更新的后悔药
- 大量系统文件并不性能敏感,开启透明压缩能节省不少空间
- CoW 对 SSD 硬盘十分友好,而 Btrfs 的性能劣势在 SSD 的补足下并不明显(相反 HDD 可能有 40% 的性能损失)
- 系统分区也没什么重要数据,炸了也无所谓
子卷和快照
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
文件来控制快照行为:
- 设置
updateGrub
为false
,因为不更新也会自动触发 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 风格特有配置
主要的操作是把系统转移进某一个可写的快照。
- 为了使用
manjaro-chroot
,安装包manjaro-tools-base
(运行sudo pacman -S manjaro-tools-base
)。其实不装也行,把指令换成chroot
也一样。 - 编辑
/etc/fstab
,把挂载根目录一行的subvol=/
去掉;参考子卷的方式增加一条将子卷subvol=/@/.snapshots
挂载到/.snapshots
的配置。 - 创建一个快照作为当前的系统快照:
snapper create --read-write --type single -d "Init snapshot"
。在指令输出中应该可以看到创建快照的快照号,把它设置为当前快照:sudo btrfs subvolume set-default /.snapshots/快照号/snapshot
。 - 更新一下 Grub,让系统启动到这个快照:
manjaro-chroot /.snapshots/快照号/snapshot bash -c "sudo grub-install && sudo update-grub"
。 - 重启。然后看看
findmnt /
里面显示的子卷是不是设置的那个快照号。 - 如果是,那就来一个刺激的狠活。先挂载它
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
- https://forum.manjaro.org/t/howto-btrfs-and-snapper/25417
- https://wiki.archlinux.org/title/snapper
- https://bbs.archlinux.org/viewtopic.php?id=194491
- https://www.reddit.com/r/openSUSE/comments/l015pb/snapper_rollback_problem/
- https://forum.manjaro.org/t/btrfs-snapper-the-suse-way-with-rollback/52279
- https://wiki.manjaro.org/index.php/Restore_the_GRUB_Bootloader
- https://github.com/teejee2008/timeshift/issues/606
评论