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是有若干好處的:
- 快照是滾動更新的後悔藥
- 大量系統文件並不性能敏感,開啟透明壓縮能節省不少空間
- CoW對SSD硬盤十分友好,而Btrfs的性能劣勢在SSD的補足下並不明顯(相反HDD可能有40%的性能損失)
- 系統分區也沒什麼重要數據,炸了也無所謂
子卷和快照
Btrfs一個非常重要的概念就是子卷。子卷可以理解成為“盤中盤”,可以單獨掛載,行為類似一個文件夾(可以移動,但不能 unlink
)。子卷是可以嵌套的,不過此時子卷相當於同時掛載在一顆文件樹,因此並不會循環嵌套。子卷可以根據其相對於根子卷(Btrfs分區自帶的子卷,路徑是 /
,ID是5)的路徑確定(比如 /subA
),不過因為子卷的路徑可以被移動,因此具體子卷是通過ID定位的。
快照就是基於子卷的,創建快照等同於創建一個和原子卷共享文件的子卷。因此其實快照的原子卷的地位是平等的,創建快照後刪除原子卷也沒有什麼問題。結合根據路徑查找子卷,就可以實現快照某個路徑了:把子卷 /sub
掛載到 /dest
,如果要恢復快照就用某個快照子卷替換掉子卷 /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,[email protected] 0 1 UUID=... /var/log btrfs defaults,noatime,ssd,[email protected] 0 1 UUID=... /var/cache btrfs defaults,noatime,ssd,[email protected] 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 GRUB_TIMEOUT_STYLE=hidden
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
,參考子卷的方式增加一條將子卷 [email protected]
掛載到 /.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
- http://forum.manjaro.org/t/howto-btrfs-and-snapper/25417
- http://wiki.archlinux.org/title/snapper
- http://bbs.archlinux.org/viewtopic.php?id=194491
- http://www.reddit.com/r/openSUSE/comments/l015pb/snapper_rollback_problem/
- http://forum.manjaro.org/t/btrfs-snapper-the-suse-way-with-rollback/52279
- http://wiki.manjaro.org/index.php/Restore_the_GRUB_Bootloader
- http://github.com/teejee2008/timeshift/issues/606
- 論如何又收一個新年解謎紅包 – 2022 篇
- 向着2022年奔去
- Btrfs:認識、從Ext4遷移與快照方案
- 複式記賬指北(三):如何打造不半途而廢的記賬方案
- 複式記賬指北(一):What and Why?
- 使用 Vue Element 開發 Tampermonkey 插件
- 理想影音庫構建之路(二):關於老番管理這件事
- 理想影音庫構建之路(一):使用BGmi自動追番、刮削
- 漫談NAT(一):各種NAT類型
- Shell中錯誤處理的探索
- Hackergame 2020 Writeup
- 關於啟動引導的那些事兒(下) : UEFI與GPT
- 當我們談論Monad的時候(二)
- 關於啟動引導的那些事兒(上) : Legacy Boot