本文将探讨什么是 Btrfs 快照、它们的工作原理以及如何在日常情况下从快照中获益。这是详细介绍 Btrfs 的系列文章的一部分.
介绍
想象一下,您在很长一段时间内处理一个文件,反复添加更改并撤消更改。然后,在某个时候,您意识到:两个小时前撤消的某些更改现在非常有用。而且,在昨天您还更改了这个特定的部分,然后才删除了那个设计。但是,当然,由于您定期保存文件,因此旧的更改会丢失。很多人可能以前都遇到过这种情况。如果您可以恢复旧文件版本而不必定期手动复制它们,那不是很好吗?
这只是 Btrfs 快照可以帮到您的一种典型情况。如果使用得当,快照还可以为您的 PC 提供出色的备份解决方案。
下面您将看到许多与快照相关的示例。如果您想继续操作,您必须具有 Btrfs 文件系统和 root 访问权限。您可以使用以下命令检查目录的文件系统:
$ findmnt -no FSTYPE /home
btrfs
这里findmnt命令显示了
/home/目录的文件系统类型。如果它显示btrfs
,则一切就绪。让我们创建一个新目录来执行一些实验:
$ mkdir ~/btrfs-snapshot-test
$ cd ~/btrfs-snapshot-test
在下面的文本中,您将在框中找到许多命令响应,如上所示。
让我们先从一个简单的问题开始:什么是 Btrfs 快照?如果你查看文档[1]和 Wiki [2],你不会立即找到这个问题的答案。事实上,在“功能”部分根本找不到答案。如果你搜索一下,你会发现大量提到了快照以及 Btrfs 子卷[3]。那么现在该怎么办呢?
还记得本系列前两篇文章中都提到过快照吗?文章中写道:
CoW 有什么优势?简单来说:可以保留修改和编辑文件的历史记录。Btrfs 会将对旧文件版本 (inode) 的引用保存在可以轻松访问的地方。此引用是
快照:某个时间点的文件系统状态的图像
。
以及:
分离
/
和home
的另一个优点是您可以单独拍摄快照。子卷是快照的边界,快照永远不会包含拍摄快照的子卷下方的其他子卷的内容。
快照似乎与 Btrfs 子卷有关。您可能之前在其他情况下听说过快照,例如 LVM(逻辑卷管理器)。虽然从技术上讲,它们的用途相同,但在实现目标的方式上却有所不同。
每个 Btrfs 快照都是一个子卷
。但是,并非每个子卷都是快照。区别在于子卷包含的内容。快照是添加了内容的子卷:它保存对文件(inode)的当前和/或过去版本的引用
。让我们看看快照来自哪里!
创建 Btrfs 快照
要使用快照,您需要一个 Btrfs 子卷来拍摄快照。让我们在测试文件夹 (~/btrfs-snapshot-test) 中创建一个:
$ cd ~/btrfs-snapshot-test
$ sudo btrfs subvolume create demo
Create subvolume './demo'
$ sudo chown -R $(id -u):$(id -g) demo/
$ cd demo
由于默认情况下 Btrfs 子卷归 root 所有,因此您必须调用chown
来将子卷中的文件修改为普通用户所有。现在在其中添加一些文件:
$ touch foo bar baz
$ echo "Lorem ipsum dolor sit amet, " > foo
你的目录现在看起来像这样:
$ ls -l
total 4
-rw-r--r--. 1 hartan hartan 0 Dec 20 08:11 bar
-rw-r--r--. 1 hartan hartan 0 Dec 20 08:11 baz
-rw-r--r--. 1 hartan hartan 29 Dec 20 08:11 foo
让我们从中创建第一个快照:
$ cd ..
$ sudo btrfs subvolume snapshot demo demo-1
Create a snapshot of 'demo' in './demo-1'
就是这样。让我们看看取得了什么成果:
$ ls -l
total 0
drwxr-xr-x. 1 hartan hartan 18 Dec 20 08:11 demo
drwxr-xr-x. 1 hartan hartan 18 Dec 20 08:11 demo-1
$ tree
.
├── demo
│ ├── bar
│ ├── baz
│ └── foo
└── demo-1
├── bar
├── baz
└── foo
2 directories, 6 files
看来它复制了!为了验证,让我们从快照中读取foo的内容:
$ cat demo/foo
Lorem ipsum dolor sit amet,
$ cat demo-1/foo
Lorem ipsum dolor sit amet,
当我们修改原始文件时,真正的效果就会变得明显:
$ echo "consectetur adipiscing elit, " >> demo/foo
$ cat demo/foo
Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
$ cat demo-1/foo
Lorem ipsum dolor sit amet,
这表明快照仍保留数据的“旧”版本:foo
的内容没有改变。到目前为止,您可以通过简单的文件复制实现完全相同的功能。现在您也可以继续处理旧文件:
$ echo "sed do eiusmod tempor incididunt" >> demo-1/foo
$ cat demo-1/foo
Lorem ipsum dolor sit amet,
sed do eiusmod tempor incididunt
然而,从本质上讲,我们的快照实际上是一个新的 Btrfs 子卷。您可以使用以下命令验证这一点:
$ sudo btrfs subvolume list -o .
ID 259 gen 265 top level 256 path home/hartan/btrfs-snapshot-test/demo
ID 260 gen 264 top level 256 path home/hartan/btrfs-snapshot-test/demo-1
Btrfs 快照与文件副本
那么这一切的意义何在?到目前为止,快照似乎是一种复杂的文件复制方式。事实上,快照比我们看到的要复杂得多。让我们创建一个更大的文件:
$ dd if=/dev/urandom of=demo/bigfile bs=1M count=512
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 1.3454 s, 399 MB/s
现在有一个新的文件demo/bigfile,大小为 512 MiB。让我们再创建一个快照,这样在修改数据时就不会丢失它:
$ sudo btrfs subvolume snapshot demo demo-2
Create a snapshot of 'demo' in './demo-2'
现在让我们通过在文件中添加一小段字符串来模拟一些变化:
$ echo "small changes" >> demo/bigfile
以下是最终的文件结构:
$ tree
.
├── demo
│ ├── bar
│ ├── baz
│ ├── bigfile
│ └── foo
├── demo-1
│ ├── bar
│ ├── baz
│ └── foo
└── demo-2
├── bar
├── baz
├── bigfile
└── foo
3 directories, 11 files
但真正的魔法发生在别处。如果你复制了demo/bigfile,你现在将拥有两个大小约为 512 MiB 且内容基本相同的文件。但是,由于它们是不同的副本,它们总共将占用约 1 GiB 的存储空间。请记住,两个文件之间的差异几乎不超过 10 字节——与原始文件大小相比,这几乎微不足道。
Btrfs 快照的工作方式与文件副本不同:它们保留对当前和过去 inode 的引用。当您将更改附加到文件时,Btrfs 会在后台分配更多空间来存储更改,并将对这些新数据的引用添加到原始 inode。以前的内容保持不变。如果这有助于您的思维模型,您可以将其视为仅“存储”原始文件和修改版本之间的差异。
我们来看看这个的效果:
$ sudo compsize .
Processed 11 files, 5 regular extents (9 refs), 3 inline.
Type Perc Disk Usage Uncompressed Referenced
TOTAL 100% 512M 512M 1.0G
none 100% 512M 512M 1.0G
此处有趣的数字见“TOTAL”行:
“Referenced”是当前目录中所有文件的总大小,总计
“Disk Usage”是指磁盘上分配用于存储文件的存储空间量
虽然您总共有 1 GiB 文件,但仅需 512 MiB 即可存储它们。
Btrfs 快照和备份
到目前为止,在本文中,您已经了解了如何创建 Btrfs 快照以及它们的特殊之处。有人可能会想:如果我在 PC 上本地拍摄一系列 Btrfs 快照,那么我就有了一个可靠的备份策略。事实并非如此。如果 Btrfs 子卷共享的底层数据被意外损坏(由 Btrfs 影响范围之外的某些东西损坏,例如宇宙射线),则指向此数据的所有子卷都会包含相同的错误。
要将快照转换为真正的备份,您应该将它们存储在不同的 Btrfs 文件系统上,例如外部驱动器上。为了本文的目的,让我们创建一个包含在文件内的新 Btrfs 文件系统并将其挂载以模拟外部驱动器。如果您有一个使用 Btrfs 格式化的外部驱动器,请随意替换以下命令中提到的所有路径以进行尝试!让我们创建一个新的 Btrfs 文件系统:
注意:以下命令将在您的文件系统上创建一个 8 GB 大小的新文件。如果您想按照以下步骤操作,请确保您至少有 8 GB 的可用磁盘空间。不要为文件分配少于 8 GB 的空间,否则 Btrfs 在安装过程中可能会遇到问题。
$ truncate -s 8G btrfs_filesystem.img
$ sudo mkfs.btrfs -L "backup-drive" btrfs_filesystem.img
btrfs-progs v5.18
See http://btrfs.wiki.kernel.org for more information.
[ ... ]
Devices:
ID SIZE PATH
1 8.00GiB btrfs_filesystem.img
这些命令创建了一个名为btrfs\filesystem.img的 8 GB 新文件,并在其中格式化了 Btrfs 文件系统。现在您可以将其挂载为外部驱动器:
$ mkdir backup-drive
$ sudo mount btrfs_filesystem.img backup-drive
$ sudo chown -R $(id -u):$(id -g) backup-drive
$ ls -lh
total 4.7M
drwxr-xr-x. 1 hartan hartan 0 Dec 20 08:35 backup-drive
-rw-r--r--. 1 hartan hartan 8.0G Dec 20 08:37 btrfs_filesystem.img
drwxr-xr-x. 1 hartan hartan 32 Dec 20 08:14 demo
drwxr-xr-x. 1 hartan hartan 18 Dec 20 08:11 demo-1
drwxr-xr-x. 1 hartan hartan 32 Dec 20 08:14 demo-2
太好了,现在在备份驱动器下挂载了一个独立的 Btrfs 文件系统!让我们尝试拍摄另一个快照并将其放在那里:
$ sudo btrfs subvolume snapshot demo backup-drive/demo-3
Create a snapshot of 'demo' in 'backup-drive/demo-3'
ERROR: cannot snapshot 'demo': Invalid cross-device link
发生了什么?好吧,您尝试拍摄demo的快照并将其存储在不同的 Btrfs 文件系统中(从 Btrfs 的角度来看是不同的设备)。还记得 Btrfs 子卷仅保存对文件及其内容(inode)的引用吗?这正是问题所在:文件和内容存在于我们的主文件系统中,但不存在于新创建的备份驱动器_中。您必须找到一种方法将子卷连同其内容一起传输到新文件系统。
将快照存储在不同的 Btrfs 文件系统上
Btrfs 实用程序包含两个用于此目的的特殊命令。让我们先看看它们是如何工作的:
$ sudo btrfs send demo | sudo btrfs receive backup-drive/
ERROR: subvolume /home/hartan/btrfs-snapshot-test/demo is not read-only
ERROR: empty stream is not considered valid
又一个错误!这次它告诉你我们尝试传输的子卷不是只读的。这是真的:你可以将新内容写入迄今为止创建的所有快照/子卷。你可以像这样创建只读快照:
$ sudo btrfs subvolume snapshot -r demo demo-3-ro
Create a readonly snapshot of 'demo' in './demo-3-ro'
与之前不同,这里在_快照子_命令中添加了_-r_选项。这将创建一个只读快照,很容易验证:
$ touch demo-3-ro/another-file
touch: cannot touch 'demo-3-ro/another-file': Read-only file system
现在您可以重试传输子卷:
$ sudo btrfs send demo-3-ro | sudo btrfs receive backup-drive/
At subvol demo-3-ro
At subvol demo-3-ro
$ tree
├── backup-drive
│ └── demo-3-ro
│ ├── bar
│ ├── baz
│ ├── bigfile
│ └── foo
├── btrfs_filesystem.img
├── demo
[ ... ]
└── demo-3-ro
├── bar
├── baz
├── bigfile
└── foo
6 directories, 20 files
成功了!您已成功将我们原始子卷演示的只读快照传输到外部 Btrfs 文件系统。
在非 Btrfs 文件系统上存储快照
上面您已经了解了如何将 Btrfs 子卷/快照存储到另一个 Btrfs 文件系统上。但是如果您没有另一个 Btrfs 文件系统并且无法创建一个,您该怎么办?例如因为外部驱动器需要一个与 Windows 或 MacOS 主机兼容的文件系统?在这种情况下,您可以将子卷存储在文件中:
$ sudo btrfs send -f demo-3-ro-subvolume.btrfs demo-3-ro
At subvol demo-3-ro
$ ls -lh demo-3-ro-subvolume.btrfs
-rw-------. 1 root root 513M Dec 21 10:39 demo-3-ro-subvolume.btrfs
文件_demo-3-ro-subvolume.btrfs现在包含在稍后时间重新创建demo-3-ro子卷所需的一切。
增量发送子卷
如果您对不同的子卷重复执行此操作,您会在某个时候注意到不同的子卷不再共享其文件内容。这是因为在发送上述子卷时,重新创建此独立子卷所需的所有数据都会传输到目标。但是,您可以指示 Btrfs 仅将两个子卷之间的差异发送到目标!这种所谓的增量发送将确保共享引用在子卷之间保持共享。为了演示这一点,请对我们的原始子卷添加一些更改:
$ echo "a few more changes" >> demo/bigfile
接下来创建另一个只读快照:
$ sudo btrfs subvolume snapshot -r demo demo-4-ro
Create a readonly snapshot of 'demo' in './demo-4-ro'
现在发送:
$ sudo btrfs send -p demo-3-ro demo-4-ro | sudo btrfs receive backup-drive
At subvol demo-4-ro
At snapshot demo-4-ro
在上面的命令中,-p选项指定一个父子卷,根据该子卷计算差异。请务必记住,源和目标 Btrfs 文件系统都必须包含相同的、未修改的父子卷!确保新子卷确实存在:
$ ls backup-drive/
demo-3-ro demo-4-ro
$ ls -lR backup-drive/demo-4-ro/
backup-drive/demo-4-ro/:
total 524296
-rw-r--r--. 1 hartan hartan 0 Dec 20 08:11 bar
-rw-r--r--. 1 hartan hartan 0 Dec 20 08:11 baz
-rw-r--r--. 1 hartan hartan 536870945 Dec 21 10:49 bigfile
-rw-r--r--. 1 hartan hartan 59 Dec 20 08:13 foo
但是如何知道增量发送是否只传输了两个子卷之间的差异?让我们将数据流传输到文件并查看它有多大:
$ sudo btrfs send -f demo-4-ro-diff.btrfs -p demo-3-ro demo-4-ro
At subvol demo-4-ro
$ ls -l demo-4-ro-diff.btrfs
-rw-------. 1 root root 315 Dec 21 10:55 demo-4-ro-diff.btrfs
根据 ls,该文件只有 315 字节大小!这意味着增量发送仅传输了两个子卷之间的更改,以及其他特定于 Btrfs 的元数据。
从快照恢复子卷
在继续之前,让我们先清理一下您现在不需要的东西:
$ sudo rm -rf demo-4-ro-diff.btrfs demo-3-ro-subvolume.btrfs
$ sudo btrfs subvolume delete demo-1 demo-2 demo-3-ro demo-4-ro
$ ls -l
total 531516
drwxr-xr-x. 1 hartan hartan 36 Dec 21 10:50 backup-drive
-rw-r--r--. 1 hartan hartan 8589934592 Dec 21 10:51 btrfs_filesystem.img
drwxr-xr-x. 1 hartan hartan 32 Dec 20 08:14 demo
到目前为止,您已成功创建 Btrfs 子卷的读/写和只读快照,并将它们发送到外部位置。但是,为了将其转变为备份策略,必须有一种方法将子卷发送回原始文件系统并使其再次可写。为此,让我们将演示子卷移动到其他地方,并尝试从最新的快照重新创建它。首先:重命名“损坏”的子卷。恢复成功后,它将被删除:
$ mv demo demo-broken
第二步:将最新的快照传回此文件系统:
$ sudo btrfs send backup-drive/demo-4-ro | sudo btrfs receive .
At subvol backup-drive/demo-4-ro
At subvol demo-4-ro
[hartan@fedora btrfs-snapshot-test]$ ls
backup-drive btrfs_filesystem.img demo-4-ro demo-broken
第三:从快照创建读写子卷:
$ sudo btrfs subvolume snapshot demo-4-ro demo
Create a snapshot of 'demo-4-ro' in './demo'
$ ls
backup-drive btrfs_filesystem.img demo demo-4-ro demo-broken
最后一步很重要:您不能直接将_demo-4-ro_重命名为_demo_,因为它仍然是只读子卷!最后,您可以检查所需的一切是否都在那里:
$ tree demo
demo
├── bar
├── baz
├── bigfile
└── foo
0 directories, 4 files
$ tail -c -19 demo/bigfile
a few more changes
_上面的最后一个命令告诉您bigfile_中的最后 19 个字符实际上是最后执行的更改。此时,您可能希望将最近的更改从_demo-broken_复制到新的_demo_子卷。由于您没有执行任何其他更改,因此您现在可以删除过时的子卷:
$ sudo btrfs subvolume delete demo-4-ro demo-broken
Delete subvolume (no-commit): '/home/hartan/btrfs-snapshot-test/demo-4-ro'
Delete subvolume (no-commit): '/home/hartan/btrfs-snapshot-test/demo-broken'
就这样!您已成功从之前存储在不同 Btrfs 文件系统(外部媒体)上的快照中恢复_演示子卷。_
子卷作为快照的边界
在本系列的第二篇文章中,我提到子卷是快照的边界,但这到底是什么意思呢?简单来说,子卷的快照将仅包含此特定子卷的内容,而不包含下面嵌套的任何子卷。让我们来看看:
$ sudo btrfs subvolume create demo/nested
Create subvolume 'demo/nested'
$ sudo chown -R $(id -u):$(id -g) demo/nested
$ touch demo/nested/another_file
让我们像以前一样拍摄快照:
$ sudo btrfs subvolume snapshot demo demo-nested
Create a snapshot of 'demo' in './demo-nested'
并检查内容:
$ tree demo-nested
demo-nested
├── bar
├── baz
├── bigfile
├── foo
└── nested
1 directory, 4 files
$ tree demo
demo
├── bar
├── baz
├── bigfile
├── foo
└── nested
└── another_file
1 directory, 5 files
请注意,即使存在_嵌套_文件夹,也缺少_another_file 。发生这种情况的原因是__嵌套文件夹_是子卷:_demo_的快照包含嵌套子卷的文件夹(挂载点),但其内容不存在。目前无法以递归方式执行快照以包含嵌套子卷。但是,我们可以利用这一点从快照中排除文件夹!这通常适用于您可以轻松重现或很少更改的数据。示例包括虚拟机或容器映像、电影、游戏文件等。
在结束本文之前,让我们删除测试时创建的所有内容:
$ sudo btrfs subvolume delete demo/nested demo demo-nested
Delete subvolume (no-commit): '/home/hartan/btrfs-snapshot-test/demo/nested'
Delete subvolume (no-commit): '/home/hartan/btrfs-snapshot-test/demo'
Delete subvolume (no-commit): '/home/hartan/btrfs-snapshot-test/demo-nested'
$ sudo umount backup-drive
$ cd ..
$ rm -rf btrfs-snapshot-test/
对基于 Btrfs 的备份的最终想法
如果您决定使用 Btrfs 定期备份数据,您可能需要使用可以自动执行此任务的工具。Btrfs wiki 上有一个专门针对 Btrfs 的备份工具列表[4]。在此页面上,您还可以找到另一个手动执行 Btrfs 备份的步骤摘要。就我个人而言,我对_btrbk_ [5]有很多很好的体验,我正在用它来执行自己的备份。除了备份之外,_btrbk_还可以在您的 PC 本地保留 Btrfs 快照列表。我用它来防止意外删除数据。
如果您想了解有关使用 Btrfs 执行备份的更多信息,请在下面发表评论,我会考虑撰写一篇专门讨论该主题的后续文章。
结论
本文研究了 Btrfs 快照,即 Btrfs 底层的子卷。您了解了如何创建读/写和只读快照,以及此机制如何帮助防止数据丢失。
本系列的下一篇文章将讨论:
- 压缩——透明地节省存储空间
- Qgroups – 限制文件系统大小
- RAID – 替换 mdadm 配置
如果您想了解更多与 Btrfs 相关的其他主题,请查看 Btrfs Wiki [2]和文档[1]。如果您还没有看过本系列的前两篇文章,请不要忘记查看!如果您觉得本系列文章中缺少某些内容,请在下面的评论中告诉我。我们下一篇文章再见!