文章目录[隐藏]
对于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
评论