标签 Btrfs 下的文章

这篇文章将探索 Btrfs 中的透明文件系统压缩,以及它如何帮助节省存储空间。这篇文章是《Btrfs 详解》系列文章中的一篇。从 Fedora Linux 33 开始,Btrfs 就是 Fedora Workstation 和 Fedora Silverblue 的默认文件系统。

如果你错过了,这里是本系列的上一篇文章:Btrfs 详解:快照

简介

很多人都经历过存储空间用完的情况。也许你想从互联网下载一个大文件,或者你需要快速从你的手机中复制些照片,然后操作突然失败。虽然存储空间成本正在稳步降低,但越来越多的设备要么制造时就是固定数量的存储容量,要么最终用户难以扩展其存储容量。

但当你的存储空间不足时你可以做什么呢?也许你会求助于云存储,或者你可以随身携带一些外部存储设备。

在这篇文章里我会研究该问题的另一种解决方案:透明的文件系统压缩,这是 Btrfs 的一个特性。理想情况下,这将解决你的存储问题,同时几乎不需要对你的系统进行修改!让我们来看看是如何做到的。

透明压缩的解释

首先,让我们来探寻 透明 压缩是什么意思。你可以通过像 gzip、xz 或者 bzip2 这些压缩算法去压缩文件。这通常是显式操作:你利用一个压缩工具并且让它操作你的文件。虽然根据文件的内容,节约了空间,这有一个主要的缺点:当你想读取文件或者修改的时候,你得先解压缩。

这不仅是一个乏味的过程,而且也暂时打破了你之前节省的空间。再者,你最终解压了你不想访问的那部分文件内容。明显有比这更好的方法!

相反,透明压缩发生在文件系统级别。在这里,压缩的文件对用户看起来像常规的未压缩文件一样。但是,它们是被压缩后存储在硬盘上的。这之所以可行,是因为操作系统仅仅选择性地访问那部分文件,并且确保在向磁盘写入更新时再次压缩它们。

这里的压缩是透明的在于它不被用户感知,除了在文件访问时可能的 CPU 负载小量增加。因此,你可以应用在已有的系统而不是进行硬件修改或者求助于云存储。

压缩算法对比

Btrfs 提供了多个压缩算法的选择。出于技术原因它不能选用任意的压缩算法。它现在支持:

  • zstd
  • lzo
  • zlib

好消息是,由于透明压缩的工作原理,你不需要安装这些程序供 Btrfs 使用。在下面的文章里,你会看到如何去运行一个简单的性能测试来对比压缩算法。但是,为了运行性能测试,你必须安装必要的可执行文件。事后不需要留着它们,所以你将使用 Podman 容器来确保不会在系统中留下任何痕迹。

注意 :因为 Btrfs 使用的压缩依赖于内核对这些压缩算法的(重新)实现,用户空间版本的算法得出的结果应该认为是粗略估计。

因为一次次敲重复的命令是枯燥的工作,我已经在 Gitlab 上准备了一个可以运行的 Bash 脚本 (https://gitlab.com/hartang/btrfs-compression-test)。这会用上面提到的每个算法在不同的压缩级别运行一次简单的压缩和解压缩。

首先,下载脚本:

$ curl -LO https://gitlab.com/hartang/btrfs-compression-test/-/raw/main/btrfs_compression_test.sh

下一步,启动一个 Fedora Linux 容器去挂载你当前的工作目录,以便你可以和主机交换文件同时在那里运行脚本:

$ podman run --rm -it --security-opt label=disable -v "$PWD:$PWD" \
    -w "$PWD" registry.fedoraproject.org/fedora:37

最后运行脚本:

$ chmod +x ./btrfs_compression_test.sh
$ ./btrfs_compression_test.sh

在我机器上的输出是这样:

[INFO] Using file 'glibc-2.36.tar' as compression target
[INFO] Target file 'glibc-2.36.tar' not found, downloading now...
################################################################### 100.0%
[ OK ] Download successful!
[INFO] Copying 'glibc-2.36.tar' to '/tmp/tmp.vNBWYg1Vol/' for benchmark...
[INFO] Installing required utilities
[INFO] Testing compression for 'zlib'

    Level | Time (compress) | Compression Ratio | Time (decompress)
-------+-----------------+-------------------+-------------------
        1 |         0.322 s |          18.324 % |           0.659 s
        2 |         0.342 s |          17.738 % |           0.635 s
        3 |         0.473 s |          17.181 % |           0.647 s
        4 |         0.505 s |          16.101 % |           0.607 s
        5 |         0.640 s |          15.270 % |           0.590 s
        6 |         0.958 s |          14.858 % |           0.577 s
        7 |         1.198 s |          14.716 % |           0.561 s
        8 |         2.577 s |          14.619 % |           0.571 s
        9 |         3.114 s |          14.605 % |           0.570 s

[INFO] Testing compression for 'zstd'

    Level | Time (compress) | Compression Ratio | Time (decompress)
-------+-----------------+-------------------+-------------------
        1 |         0.492 s |          14.831 % |           0.313 s
        2 |         0.607 s |          14.008 % |           0.341 s
        3 |         0.709 s |          13.195 % |           0.318 s
        4 |         0.683 s |          13.108 % |           0.306 s
        5 |         1.300 s |          11.825 % |           0.292 s
        6 |         1.824 s |          11.298 % |           0.286 s
        7 |         2.215 s |          11.052 % |           0.284 s
        8 |         2.834 s |          10.619 % |           0.294 s
        9 |         3.079 s |          10.408 % |           0.272 s
       10 |         4.355 s |          10.254 % |           0.282 s
       11 |         6.161 s |          10.167 % |           0.283 s
       12 |         6.670 s |          10.165 % |           0.304 s
       13 |        12.471 s |          10.183 % |           0.279 s
       14 |        15.619 s |          10.075 % |           0.267 s
       15 |        21.387 s |           9.989 % |           0.270 s

[INFO] Testing compression for 'lzo'

    Level | Time (compress) | Compression Ratio | Time (decompress)
-------+-----------------+-------------------+-------------------
        1 |         0.447 s |          25.677 % |           0.438 s
        2 |         0.448 s |          25.582 % |           0.438 s
        3 |         0.444 s |          25.582 % |           0.441 s
        4 |         0.444 s |          25.582 % |           0.444 s
        5 |         0.445 s |          25.582 % |           0.453 s
        6 |         0.438 s |          25.582 % |           0.444 s
        7 |         8.990 s |          18.666 % |           0.410 s
        8 |        34.233 s |          18.463 % |           0.405 s
        9 |        41.328 s |          18.450 % |           0.426 s

[INFO] Cleaning up...
[ OK ] Benchmark complete!

重要的是在根据脚本得出的数据做决定之前注意这些事情:

  • 不是所有的文件压缩效果都一样好。像图片或电影这种已经压缩过的现代多媒体格式不会压缩得更小。
  • 脚本中压缩和解压缩各进行一次。重复运行会产生稍微不同的输出。因此,时间应该被理解为是估计,而不是准确的测量。

鉴于输出的数据,我决定在我的系统上使用压缩级别 3 的 zstd 压缩算法。依据你的需求,你可能想使用更高的压缩级别(比如,如果你存储设备相当的慢)。要估算可达到的读/写速度,可以将源存档大小(约 260MB)除以(解)压缩时间。

压缩测试默认是对 GNU libc 2.36 源码进行的。如果你想看看对指定文件的效果,你可以通过第一个参数传递文件路径给脚本。记住文件一定要可以在容器内访问才行。

如果你想要测试其他东西或者执行更加详细的测试,可以阅读脚本的源码,根据需要修改它。

配置 Btrfs 压缩

Btrfs 里的透明文件系统压缩可以通过几种方式配置:

  • 作为挂载文件系统的挂载选项(可用于相同 Btrfs 文件系统的所有子卷)
  • 通过 Btrfs 文件属性
  • btrfs filesystem defrag 时(不是永久的,不在这里介绍)
  • 通过 chattr 文件属性接口(不在这里介绍)

我只会介绍其中前两个。

在挂载时开启压缩

有一个 Btrfs 挂载选项可以开启文件压缩:

$ sudo mount -o compress=<ALGORITHM>:<LEVEL> ...

例如,去挂载一个文件系统,并使用等级 3 的 ztsd 算法去压缩,你可以写成:

$ sudo mount -o compress=zstd:3 ...

设置压缩等级是可选的。重要的是注意到 compress 挂载选项应用到整个 Btrfs 文件系统和它所有的子卷。此外,这是目前唯一支持的指定压缩等级的方式。

为了对文件系统的根应用压缩,必须在 /etc/fstab 上指定。例如,Fedora Linux 安装器,默认启用级别 1 的 zstd 压缩,在 /etc/fstab 里是这样:

$ cat /etc/fstab
[ ... ]
UUID=47b03671-39f1-43a7-b0a7-db733bfb47ff  /  btrfs   subvol=root,compress=zstd:1,[ ... ] 0 0

启用单个文件压缩

另外一种方式指定压缩的方法是通过 Btrfs 文件系统属性。使用下面的命令去查看文件、目录或子卷的压缩设置:

$ btrfs property get <PATH> compression

类似的,你可以像这样配置压缩:

$ sudo btrfs property set <PATH> compression <VALUE>

例如,对在 /etc 下所有文件启用 zlib 压缩:

$ sudo btrfs property set /etc compression zlib

你可以通过 man btrfs-property 得到支持值的列表。记住这个接口不允许指定压缩级别。除此之外,如果设置了一个压缩属性,它会覆盖挂载时的其他压缩配置。

压缩已有文件

在这时,如果你对现有文件系统采用压缩,然后通过 df 或类似命令检查空间利用率,你会发现什么都没变。这是因为 Btrfs 自身不会 “重新压缩” 所有已有的文件。压缩只会发生在往磁盘写新数据的时候。有一些方式去执行显式的重压缩:

  1. 等待,什么都不做:只要文件被修改并被写回磁盘,Btrfs 根据配置压缩新写入的文件内容。如果我们等待足够长,越来越多的文件被重写,在某个时间点就会被压缩。
  2. 移动文件到另一个文件系统然后移动回来:取决于你想压缩哪些文件,这可能是相当乏味的选项。
  3. 执行一次 Btrfs 碎片整理。

最后一个选项可能是最方便的,但是它会对已经包含快照的 Btrfs 文件系统提出警告:它会破坏快照间的共享范围。换句话来说,两个快照间所有的共享内容,或者一个快照和它的父子卷,在碎片整理操作后将保存多份。

因此,如果你在你的文件系统里已经有很多快照,你不应该对整个文件系统运行碎片整理。这也没有必要,因为如果你想的话,Btrfs 可以对特定的目录或者单个文件进行碎片整理。

你可以使用以下命令去执行一次碎片整理:

$ sudo btrfs filesystem defragment -r /path/to/defragment

例如,你想像这样去整理你主目录的碎片:

$ sudo btrfs filesystem defragment -r "$HOME"

如果有疑问,最好从碎片整理单个大文件开始,并在监视文件系统上的可用空间的同时继续处理越来越大的目录。

测量文件系统压缩

有时,你可能会想,文件系统压缩为你节省了多少空间。但如何判断呢?首先,要知道一个 Btrfs 文件系统是否在挂载时启用了压缩,你可以使用以下命令:

$ findmnt -vno OPTIONS /path/to/mountpoint | grep compress

如果你得到了结果,那么给定挂载点的文件系统就使用了压缩!下一步,compsize 命令会告诉你你的文件需要多少空间:

$ sudo compsize -x /path/to/examine

在我的主目录,结果是这样:

$ sudo compsize -x "$HOME"
Processed 942853 files, 550658 regular extents (799985 refs), 462779 inline.
Type       Perc     Disk Usage   Uncompressed Referenced
TOTAL       81%       74G          91G         111G
none       100%       67G          67G          77G
zstd        28%      6.6G          23G          33G

每一行告诉你应用到文件的压缩 “类型” 。* TOTAL 是下面所有行的总计。

另一方面,这些列告诉你我们的文件需要多少空间:

  • Disk Usage 是实际分配在硬盘上的空间,
  • Uncompressed 是如果没有压缩,文件所需要的空间,
  • Referenced 是所有未压缩文件加起来的总大小。

Referenced 可以与数据 Uncompressed 不同,比如一个文件之前被重复了,或者有快照共享内容。在上面的例子,你可以看到在我的硬盘上总计 91 GB 的未压缩文件仅占据了 74 GB 的存储。取决于在目录里存储的文件类型和应用的压缩等级,这些数字可以有很大差异。

文件压缩的其它注意事项

Btrfs 使用启发式算法去探测压缩文件。这是因为压缩文件通常效果不好,所以没有必要浪费 CPU 周期去尝试进一步的压缩。为了这个目的,Btrfs 在写入压缩数据到磁盘之前测量压缩率。如果文件的第一部分压缩效果不好,文件被标记为不可压缩并且不会有后续的压缩。

如果出于某些原因,你想 Btrfs 压缩所有写入的数据,你可以通过 compress-force 选项挂载一个 Btrfs 文件系统,像这样:

$ sudo mount -o compress-force=zstd:3 ...

当像这样配置,Btrfs 会用等级 3 的 zstd 算法压缩所有写入磁盘的数据。

一个重要的注意事项是挂载一个有很多数据并开启压缩的 Btrfs 文件系统会比没开启压缩耗时更长。这是有技术上的原因的,而且这是一个不会影响文件系统操作的正常行为。

总结

本文详细介绍了 Btrfs 中的透明文件系统压缩。这是一种内置的、相对廉价的方法,可以在不需要修改的情况下从现有硬件中获得一些额外的存储空间。

本系列文章的下一篇将讨论:

  • Qgroups - 限制文件系统大小
  • RAID - 替换 mdadm 配置

(LCTT 译注:后继文章尚未发布,一旦发布我们会尽快翻译。)

如果你想了解与 Btrfs 相关的其他主题,请查看 Btrfs 维基 [1] 和文档 [2] 。如果你还没有阅读本系列的前三篇文章,请不要忘记去看看!如果你觉得本文缺少某些内容,请在下面的评论中让我知道。我们下篇文章见!

参考资料

  1. https://btrfs.wiki.kernel.org/index.php/Main_Page ↩︎
  2. https://btrfs.readthedocs.io/en/latest/Introduction.html ↩︎

(题图:MJ/1a45064c-8da5-4b60-87f2-9886d6a3299e)


via: https://fedoramagazine.org/working-with-btrfs-compression/

作者:Andreas Hartmann 选题:lujun9972 译者:A2ureStone 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章会探讨什么是 Btrfs 快照,它们如何工作的,你在日常生活中进行快照的好处。这篇文章是《Btrfs 详解》系列文章中的一篇。从 Fedora Linux 33 开始,Btrfs 就是 Fedora Workstation 和 Fedora Silverblue 的默认文件系统。

如果你错过了,这里是本系列的上一篇文章:Btrfs 详解:子卷

简介

想象一下,你长时间处理一个文件,反复添加和撤销修改。然后,在某个时刻你意识到:两小时前你撤销的部分修改,现在会非常有用。而昨天在你销毁那个设计之前,你也已经修改了这个特殊的部分。当然,由于你会定期保存文件,所以旧的改动会丢失。很多人可能都遇到过这样的情况。如果能恢复旧版本的文件,而无需定期手动复制,岂不美哉?

这是一个 Btrfs 快照可以帮助你的特别场景。当你使用正确的话,快照同时也为你的电脑提供了很好的备份方案。

下面你会找到一些关于快照的例子。如果你想跟着操作,你必须拥有访问某些 Btrfs 文件系统的权限和 root 权限。你可以通过下面命令来验证一个目录的文件系统。

$ findmnt -no FSTYPE /home
btrfs

这个命令会输出你 /home/ 目录的文件系统名称。如果它是 btrfs,那就可以了。让我们创建一个新的目录去做实验:

$ mkdir ~/btrfs-snapshot-test
$ cd ~/btrfs-snapshot-test

在下面的文本中,你会看到很多像上面显示的那样的命令输出框。请在阅读/比较命令输出时请记住,框中的内容在行末会被换行。这使得识别跨多行的长行变得困难,降低了可读性。如果有疑问,试着调整浏览器窗口的大小,看看文本的变化!

Btrfs 快照

让我们从一个基本的问题开始:什么是 Btrfs 快照?如果你在文档 [1] 和维基 [2] 中查找,你不会立刻找到这个问题的答案。事实上,从“功能”一节里是找不到的。如果你搜索一下,你会发现快照和 Btrfs 子卷一起被大量地提及 [3] 。所以现在做什么呢?

还记得快照在系列前面的文章里两次被提到吗?是这样说的:

CoW 的优势在哪里?简单的说:文件被修改和编辑的历史被保存了下来。Btrfs 保存文件旧版本的引用(inode)可以轻易地被访问。这个引用就是快照:文件系统在某个时间点的状态镜像。这将是这系列文章里的单独的一篇,所以暂时留到后面介绍。

—— Btrfs 详解:基础概念

以及:

另外一个分离 //home 的优势是我们可以分别进行 快照 。子卷是快照的边界,对一个子卷的快照永远不会包含该子卷下面的其他子卷的内容。快照的更多细节会在后续的文章中介绍。

—— Btrfs 详解:子卷

看起来快照是和 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 子卷 vs. 文件复制

这一切有什么意义呢?到目前为止快照看起来是一个更加复杂的复制文件的方式。事实上,快照不仅仅是表面上看起来那么简单。让我们来创建一个更大的文件:

$ 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

现在有一个512 MB 大小的新文件 demo/bigfile 。让我们创建另一个快照,这样在你修改数据的时候就不会丢失:

$ 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 文件系统:

注意:下面的命令会在你的文件系统上创建一个 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

妙,现在挂载在 backup-drive 下面有一个独立的 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)?这正是问题所在:文件和内容存在于我们的 home 文件系统,但不在新创建的 backup-drive 。你得找到一种方式去传输子卷和其内容到新的文件系统里。

在不同的 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 选项被加到了 snapshot 子命令里。这创建一个只读的快照,这很容易去验证:

$ 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

成功了!你成功传输原来子卷 demo 的一个只读快照到一个外部的 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 文件系统和目标 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 子卷快照,并把它们发送到外部。但是,为了把这作为备份策略,还要有一种方式去发送子卷回原来的文件系统和让它们再次变为可写。出于这个目的,我们移动 demo 子卷到其他地方并且尝试从最近的快照中重建它。第一步:重命名为 broken 子卷。一旦恢复成功它会被删除:

$ 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-rodemo ,因为这仍然是一个只读子卷!最后你可以检查你所有你想要的东西是不是在那里:

$ 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 文件系统(外部介质)的快照中恢复 demo 子卷。

子卷作为快照的边界

在本系列的第二篇文章中我提到子卷作为快照的边界,但这到底是什么意思呢?简单来说,子卷的一份快照仅包含这个子卷的内容,而不是下面嵌套的子卷。让我们来看看这个:

$ 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 不见了,仅仅是目录 nested 还在。这是因为 nested 是一个子卷: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 维基有一个专门针对 Btrfs [4] 的备份工具列表。在那里,你还将看到另一个手动执行 Btrfs 备份步骤的摘要。就我个人而言,我对 btrbk [5] 有很多很好的体验,我正在使用它来执行我自己的备份。除了备份之外,btrbk 还可以在你的 PC 本地保存 Btrfs 快照列表。我使用它来防止意外的数据删除。

如果你想要了解更多有关使用 Btrfs 进行备份的内容,欢迎在下面评论,我会考虑写一篇专门讨论这个话题的后续文章。

总结

本文研究了 Btrfs 快照,它们本质上是 Btrfs 子卷。你了解了如何创建可读写和只读快照,以及这种机制如何有助于防止数据丢失。

本系列的后续文章将讨论:

  • 压缩 - 透明地节省存储空间
  • 配额组 - 限制文件系统大小
  • RAID - 替代 mdadm 配置

如果你还想了解与 Btrfs 相关的其他主题,请查看 Btrfs 维基 [2:1] 和文档 [1:1] 。不要忘记查看本系列的前两篇文章!如果你认为本文缺少了一些内容,请在下面的评论中告诉我们。再会!

参考资料

  1. https://btrfs.readthedocs.io/en/latest/Introduction.html ↩︎ ↩︎
  2. https://btrfs.wiki.kernel.org/index.php/Main_Page ↩︎ ↩︎
  3. https://btrfs.readthedocs.io/en/latest/Subvolumes.html ↩︎
  4. https://btrfs.wiki.kernel.org/index.php/Incremental_Backup#Available_Backup_Tools ↩︎
  5. https://github.com/digint/btrbk ↩︎

(题图:MJ/ad8a61ed-ce6f-409e-b503-69104dd71149)


via: https://fedoramagazine.org/working-with-btrfs-snapshots/

作者:Andreas Hartmann 选题:lujun9972 译者:A2ureStone 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在 2018 年的一篇 早前的文章 中,我们介绍了在升级 Fedora Linux 前如何利用 LVM 复制根文件系统,以便在可能出现错误的情况下能有一个回退机制。然而如今,Fedora 工作站的默认安装已经是 Btrfs 文件系统了。现在,你可以利用 Btrfs 快照来更简便地创建一个可引导的回退系统。注意,本文不涉及会如何从其它文件系统转换或迁移到 Btrfs 上。

确认根文件系统是否为 Btrfs

本示例采用的是 Pinebook aarch64 笔记本电脑。在开始前,务必确认你的根文件系统是否采用 Btrfs。要明确,不是所有的定制版或者镜像文件默认都使用 Btrfs。

$ df -T
Filesystem     Type     1K-blocks     Used Available Use% Mounted on
devtmpfs       devtmpfs      4096        0      4096   0% /dev
tmpfs          tmpfs       998992        0    998992   0% /dev/shm
tmpfs          tmpfs       399600     6360    393240   2% /run
/dev/mmcblk2p3 btrfs     56929280 39796116  15058348  73% /
tmpfs          tmpfs       998996       24    998972   1% /tmp
tmpfs          tmpfs      5242880        0   5242880   0% /var/lib/mock
/dev/mmcblk2p3 btrfs     56929280 39796116  15058348  73% /f34
/dev/mmcblk2p3 btrfs     56929280 39796116  15058348  73% /home
/dev/mmcblk2p2 ext4        996780   551888    376080  60% /boot
/dev/mmcblk2p1 vfat        194348    31648    162700  17% /boot/efi
tmpfs          tmpfs       199796      100    199696   1% /run/user/1000
tmpfs          tmpfs       199796       84    199712   1% /run/user/0

列出当前的 Btrfs 子卷

以上的示例输出显示挂载在 “根”(/)的文件系统类型是 Btrfs。你会注意到,有三个挂载点显示了相同的备份设备以及 已用可用 的块数。这是因为它们是从同一 Btrfs 文件系统挂载的不同部分(子卷)。比如,/f34 子卷是我去年创建的那个可引导快照。

默认的 Fedora Btrfs 安装会创建一个 Btrfs 文件系统,并在其上分别挂载两个子卷,roothome ,挂载路径分别为 //home。让我们一起来看看我还添加了哪些其它的子卷:

$ sudo btrfs subvol list /
ID 272 gen 110428 top level 5 path root
ID 273 gen 110426 top level 5 path home
ID 300 gen 109923 top level 5 path f34
ID 301 gen 95852 top level 5 path home.22Jul26
ID 302 gen 95854 top level 5 path f36.22Jul26

在这里,我们有一个来自最近一次系统升级的 f34 子卷,以及两个只读快照 homef36。要添加和删除这些快照,最简单的方法就是挂载 Btrfs 的根目录。我会更新系统并创建当前 f36 root 子卷的新快照。如果你已经重命名了你的 root 子卷,我相信你知道该如何调整以下的示例以适应你的系统。

创建 Btrfs 的回退快照

$ sudo dnf update --refresh
...更新了很多部分(如果升级了内核还需要重启)
$ sudo mkdir -p /mnt/root
$ sudo mount /dev/mmcblk2p3 /mnt/root
$ cd /mnt/root
$ ls
f34  f36.22Jul26  home  home.22Jul26  root
$ sudo btrfs subvol snapshot root f36
Create a snapshot of 'root' in './f36'

因为 Btrfs 快照是以文件系统为基础的,所以并不需要在创建快照之前进行 “同步”,正如我在 LVM 中建议的。要从新的子卷引导为回退,你需要使用你喜欢的编辑器编辑 /mnt/root/f36/etc/fstab。如果你是刚入门的话,nano 就是一款十分简单的文本编辑器,功能基本够用。以下是我 fstab 文件中的一些行:

LABEL=PINE        /    btrfs   subvol=root,compress=zstd:1        1 1
UUID=e31667fb-5b6f-48d9-aa90-f2fd6aa5f005 /boot ext4    defaults        1 2
UUID=75DB-5832          /boot/efi               vfat    umask=0077,shortname=winnt 0 2
LABEL=PINE        /home    btrfs   subvol=home,compress=zstd:1    1 1
LABEL=SWAP swap            swap    discard=once    0 0

subvol=root 更改为 subvol=f36。这个改动是作用在快照中的文件,而并非你实际运行中的 fstab 文件。你可以通过 diff /etc/fstab /mnt/root/f36/etc/fstab 对比它们的区别。在我的情况下,我还使用了 sudo btrfs subvol delete f34 来删除我去年的 f34 快照。

测试 Btrfs 的回退快照

你现在可以进行回退的测试了。你可以使用 grubby 或在 /boot/loader/entries 中编辑一项来将 subvol=root 替换为 subvol=f36。然而,为了初学者的安全起见,我们更建议你在启动时编辑 GRUB 进行操作。你可以参考 关于 GRUB 的这篇文章 了解如何进入 GRUB 菜单。在你到达那里后,按下 e 键编辑默认的内核启动项。放心 —— 你所做的更改都只存在于内存中,如果你弄错了,重启电脑即可重新开始。就像编辑 fstab 文件一样,找到 subvol=root 并将其更改为 subvol=f36。然后,按 F10Ctrl + X 来引导你修改过的项目。通过这些更改,你的系统应该能够引导进入你的新快照。你可以查看 /etc/fstab 确保你正在引导至正确的子卷,或键入 mount | grep subvol 查看此时在 / 上挂载的子卷。

进行 Fedora Linux 的系统升级

如果你的回退功能没问题,重启并返回你的正常根文件系统(并像上面所述,进行确认)。然后,按照 维基页面 上的指南进行标准的系统升级。提示:在运行 dnf system-upgrade reboot 之前,给 root 子卷创建另一个快照,你可以给它命名为 root.dl。这样,如果你发现硬盘空间不够,你不必再下载五个 GB 的文件包。因为除了下载的包以外,所有的内容都将与 rootf36 共享,因此它不会占用任何额外的空间。然后说到磁盘块的共享问题……

因为在 root 子卷中的 f36 文件和 f36 子卷中的相同文件都使用的是相同的磁盘位置,所以在处理 Btrfs 报告的可用空间时,dnf system-upgrade 会感到困惑。因此在升级过程中从 root 子卷中删除它们并不会真正释放任何空间。如果你耗尽了所有空间,并且决定重启电脑,那么图形用户界面(GUI)将无法启动。此时,你需要使用 Ctrl + Alt + F2 进入文本控制台并登录,这将是你磨练命令行技能的好机会。如何清空空间或扩展根文件系统在这里先不做讨论(我的文件系统经常在一个 LVM 的卷上,可以被扩展)。通常,为升级保留超过 50% 的空闲空间是比较保险的。

恢复环节

如果不幸出现问题,你可以重启电脑,并编辑 GRUB 条目以引导启动回退快照。如果你是新手,一旦需要在磁盘上修改 GRUB 条目(这样你就不需要每次启动时都进行编辑),可能需要一些指导。删除或重命名损坏的 root 子卷是非常直接的。你可以为 f36 子卷(或 root.dl 快照)创建一个快照,然后尝试再一次进行系统升级。以下是在子卷 f36 上启动到回退系统后重新开始的示例:

$ mount | grep subvol
$ sudo mount /dev/mmcblk2p3 /mnt/root
$ cd /mnt/root
$ sudo mv root root.failed
$ sudo btrfs subvol snapshot f36 root
Create a snapshot of 'f36' in './root'

Don't forget to edit /mnt/root/root/etc/fstab to change the subvol mounted on "/" to "root".

事实证明,新的 f38 版本的 kernel-6.2.11 在我的 Pinebook 上进行系统升级后并未成功启动!(不过请不要担心,ARM 只是 Fedora Linux 的可选 CPU 架构——在主流设备上,你很少会遭遇此类问题。)的确,我成功地按照前文所述,在启动时编辑了 GRUB 条目并恢复到了 f36 的 kernel-6.2.10。现在,我继续使用着 f38,但同时搭载了 f36 的 kernel-6.2.10。

更新:kernel-6.2.12 已经发布,且我已确认它在 Pinebook 上运行正常。

过期问题

随着你不断更新 f38 系统,它最终可能会希望删除 /boot 下的最后一个 f36 内核。通常来说,这并不是问题,因为到那个时候,你已经完全熟悉 f38,而 f36 快照只是一个存档。然而,如果你希望你的复刻版本(即 f36 的快照)能无限期地启动,那么你应当在 /boot 下保存一个能正常工作的 f36 内核。最简单的方法是在 /etc/dnf/dnf.conf 文件中设置 installonly_limit=0 并手动移除旧的内核,这种方法既简单又安全(尽管可能稍显繁琐)。

这里还有一个更复杂的解决方案(不适合新手):运行 find /boot -name "*fc36*" 指令,列举出所有在 f36 子卷快照中的内核及 GRUB 文件(这些文件并未包含在快照之内)。将这些文件备份至安全位置(例如我会挂载 f36 子卷,并将文件备份至其下的一个目录)。当 f38 系统启动后,对于每一个 f36 内核版本,都需使用 dnf 删除特定版本的内核(例如,使用 dnf remove kernel-core-5.19.11-200.fc36)。千万不要删除 f38 的内核!然后将你先前备份的 f36 内核恢复至 /boot 下。此时,f38 系统已经无法再识别 f36 的内核,因此也不会将其从 /boot 中删除。

然而,使用此方法有一个缺点,那就是你可能会不小心删除正在运行的 f38 内核。如果大家有更佳的解决办法,欢迎在评论区分享。

展望未来

对于习惯于修改 GRUB 条目的用户,他们可能会考虑创建一个命名为 f38 的快照子卷。将当前的 GRUB 条目进行修改,以便启动到这个子卷,重启后,在这个子卷中进行系统升级。此后,总是根据它所包含的 Fedora Linux 版本来命名根文件系统的子卷。但在本文中,我没有采用这种作法,原因有两点:

  1. 将当前活动的子卷命名为 root,这是遵循 Fedora Linux 的默认设置。
  2. 坚持使用 root 作为当前子卷,这样在进行正常的系统升级操作之外,并不需要任何永久性的更改。

正如本文所展示的,针对重大系统更改(如系统版本升级)可能引发问题时,只读快照作为本地恢复点,非常有用。这些快照还可以通过 Btrfs 的 send 子命令,发送至远程备份。(如果远程备份设备已经包含了先前的备份数据,Btrfs 可以进行增量发送,仅传输发生变更的文件,这样就能节省时间和空间。)如果你打算长期存档这些快照,保持清晰不混淆且能明确恢复顺序的关键,就是要使用一套一致的命名规则。关于如何使用 Btrfs 的 send 命令来创建备份的更多信息,你可以参考关于 Btrfs 快照备份 的文章。

(题图:MJ/6aed1d19-f1dd-4935-ad6f-61f6a868f150)


via: https://fedoramagazine.org/make-use-of-btrfs-snapshots-to-upgrade-fedora-linux-with-easy-fallback/

作者:Stuart D Gathman 选题:lujun9972 译者:ChatGPT 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章是《Btrfs 详解》系列文章中的一篇。从 Fedora Linux 33 开始,Btrfs 就是 Fedora Workstation 和 Fedora Silverblue 的默认文件系统。

以防你忘记,这是系列文章中的前一篇:Btrfs 详解:基础概念

简介

子卷 Subvolume 允许将一个 Btrfs 文件系统划分成多个独立的子文件系统。这意味着你可以从 Btrfs 文件系统挂载子卷,就好像它们是独立的文件系统。除此之外,例如,你还可以通过 限额组 qgroup (我们将在本系列的另一篇文章里介绍)定义子卷能够占据的最大空间,或者用子卷去包含或排除快照中的文件(我们会后面的文章中会讲到)。自 Fedora Linux 33 后每个 Fedora Workstation 和 Fedora Silverblue 默认安装过程中会利用子卷。在这篇文章中我们会介绍它是如何工作的。

下面你会找到很多关于子卷的例子。如果你想跟着操作,你必须拥有访问某些 Btrfs 文件系统的权限和 root 权限。你可以通过下面命令来验证你的 /home/ 目录是否是 Btrfs 。

$ findmnt -no FSTYPE /home
btrfs

这个命令会输出你 /home/ 目录的文件系统名。如果它是 btrfs,那就可以了。让我们创建一个新的目录去做实验:

$ mkdir ~/btrfs-subvolume-test
$ cd ~/btrfs-subvolume-test

在下面的文本中,你会看到很多像上面显示的那样的命令输出框。请在阅读/比较命令输出时请记住,框中的内容在行末会被换行。这使得识别跨多行的长行变得困难,降低了可读性。如果有疑问,试着调整浏览器窗口的大小,看看文本的变化!

创建和使用子卷

我们可以通过以下命令创建一个 Btrfs 子卷:

$ sudo btrfs subvolume create first
Create subvolume './first'

当我们检查当前目录,我们可以看到现在有一个名为 first 的新目录。注意到下面输出的第一个字符 d

$ ls -l
total 0
drwxr-xr-x. 1 root root 0 Oct 15 18:09 first

我们可以像常规目录一样操作它:我们可以重命名它,移动它,在里面创建新文件和目录,等等。注意到目录属于 root,所以我们必须以 root 身份去做这些事情。

如果它表现和看起来就像个目录,那我们如何知道这是不是一个 Btrfs 子卷呢?我们可以使用 btrfs 工具去列出所有子卷:

$ sudo btrfs subvolume list .
ID 256 gen 30 top level 5 path home
ID 257 gen 30 top level 5 path root
ID 258 gen 25 top level 257 path root/var/lib/machines
ID 259 gen 29 top level 256 path hartan/btrfs-subvolume-test/first

如果你安装的是最新的 Fedora Linux,且未修改过,你很可能会看到和上面一样的输出。我们会在之后检查 homeroot ,还有全部数字的含义。现在,我们看到在我们指定的路径下有一个子卷。我们可以将输出限制在我们当前位置下面的子卷:

$ sudo btrfs subvolume list -o .
ID 259 gen 29 top level 256 path home/hartan/btrfs-subvolume-test/first

让我们重命名子卷:

$ sudo mv first second
$ sudo btrfs subvolume list -o .
ID 259 gen 29 top level 256 path home/hartan/btrfs-subvolume-test/second

我们还可以嵌套子卷:

$ sudo btrfs subvolume create second/third
Create subvolume 'second/third'
$ sudo btrfs subvolume list .
ID 256 gen 34 top level 5 path home
ID 257 gen 37 top level 5 path root
ID 258 gen 25 top level 257 path root/var/lib/machines
ID 259 gen 37 top level 256 path hartan/btrfs-subvolume-test/second
ID 260 gen 37 top level 259 path hartan/btrfs-subvolume-test/second/third

我们也可以移除子卷,就像移除目录一样:

$ sudo rm -r second/third

或者通过特殊的 Btrfs 命令:

$ sudo btrfs subvolume delete second
Delete subvolume (no-commit): '/home/hartan/btrfs-subvolume-test/second'

像单独的文件系统一样操作子卷

前面的简介里说 Btrfs 子卷就好像单独的文件系统。这意味着我们可以挂载子卷并且传递一些挂载选项给它。我们先创建一个小的目录结构去更好的理解发生了什么:

$ mkdir -p a a/1 a/1/b
$ sudo btrfs subvolume create a/2
Create subvolume 'a/2'
$ sudo touch a/1/c a/1/b/d a/2/e

这就是目录结构的样子:

$ tree
.
└── a
    ├── 1
    │   ├── b
    │   │   └── d
    │   └── c
    └── 2
        └── e

4 directories, 3 files

验证现在这里有一个新的 Btrfs 子卷:

$ sudo btrfs subvolume list -o .
ID 261 gen 41 top level 256 path home/hartan/btrfs-subvolume-test/a/2

为了挂载子卷,我们必须知道 Btrfs 子卷所在的块设备路径。下面的命令会告诉我们:

$ findmnt -vno SOURCE /home/
/dev/vda3

现在我们挂载子卷。确保你将参数替换成你 PC 上的:

$ sudo mount -o subvol=home/hartan/btrfs-subvolume-test/a/2 /dev/vda3 a/1/b

观察到我们使用 -o 参数去提供额外的选项去挂载程序。在这里我们告诉它挂载在设备 /dev/vda3 上 btrfs 文件系统里名为 home/hartan/btrfs-subvolume-test/a/2 的子卷。这是 Btrfs 特有的选项,在其他文件系统里没有的。

我们可以看到目录结构变化了:

$ tree
.
└── a
    ├── 1
    │   ├── b
    │   │   └── e
    │   └── c
    └── 2
        └── e

4 directories, 3 files

现在文件 e 出现了两次, d 不见了。我们现在可以用两个不同的路径访问相同的 Btrfs 子卷。在一个路径的所有变化会被立刻反应在其他的位置:

$ sudo touch a/1/b/x
$ ls -lA a/2
total 0
-rw-r--r--. 1 root root 0 Oct 15 18:14 e
-rw-r--r--. 1 root root 0 Oct 15 18:16 x

让我们尝试更多的挂载选项。例如我们可以像这样以只读方式挂载子卷到 a/1/b(插入你 PC 的参数):

$ sudo umount a/1/b
$ sudo mount -o subvol=home/hartan/btrfs-subvolume-test/a/2,ro /dev/vda3 a/1/b

我们和上面使用相同的命令,除了我们加上了 ro 在末尾。现在我们不能在这个挂载点上创建文件:

$ sudo touch a/1/b/y
touch: cannot touch 'a/1/b/y': Read-only file system

但直接访问子卷仍然像之前一样:

$ sudo touch a/2/y
$ tree
.
└── a
    ├── 1
    │   ├── b
    │   │   ├── e
    │   │   ├── x
    │   │   └── y
    │   └── c
    └── 2
        ├── e
        ├── x
        └── y

4 directories, 7 files

在下一步之前不要忘记进行清理:

$ sudo rm -rf a
rm: cannot remove 'a/1/b/e': Read-only file system
rm: cannot remove 'a/1/b/x': Read-only file system
rm: cannot remove 'a/1/b/y': Read-only file system

天啊,发生了什么?噢,因为我们在上面挂载只读子卷,所以不能删除它。从文件系统的角度来看,删除是一种写入操作:为了删除 a/2/b/e,我们从父目录 a/1/b 的内容中删除目录项 e。换句话来说,我们必须 写入 a/1/b 去表明 e 不复存在。所以我们先卸载子卷,然后移除目录:

$ sudo umount a/1/b
$ sudo rm -rf a
$ tree
.

0 directories, 0 files

子卷 ID

还记得 btrfs subvolume list 命令的第一次输出吗?那包含了很多数字,让我们看看这些究竟什么。我在这里复制了输出,以便再次查看:

ID 256 gen 30 top level 5 path home
ID 257 gen 30 top level 5 path root
ID 258 gen 25 top level 257 path root/var/lib/machines
ID 259 gen 29 top level 256 path hartan/btrfs-subvolume-test/first

我们看到有三列数字,每个前面有一些字母来描述它们的作用。第一列是子卷 ID 。子卷 ID 在 Btrfs 文件系统是唯一的,而且唯一地标识子卷。这意味着名为 home 的子卷也可以用它的 ID 256 来引用。之前的挂载命令是这样写的:

$ sudo mount -o subvol=hartan/...

另外一个完全合法的选择是使用子卷 ID :

$ sudo mount -o subvolid=...

子卷 ID 从 256 开始,每创建一个子卷依次递增 1 。但是在这里有一个例外:文件系统的根的子卷名称总是为 /,并且子卷 ID 是 5 。没错,即使文件系统的根技术上也是一个子卷。这是不言而喻的,因此不会出现在 btrfs subvolume 的输出列表里。如果你没有用 subvolsubvolid 参数去挂载一个 Btrfs 文件系统,subvolid=5 的顶级子卷就是默认的挂载对象。下面我们会看到一个想要显式挂载文件系统根的例子。

第二列的数字是生成号,并且在每次 Btrfs 事务中递增。这几乎是一个内部的计数器,我们不会在这里讨论。

最后,第三列数字是 子卷的子卷 ID。在上面的输出我们可以看到子卷 homeroot 的父子卷 ID 都是 5。记住 ID 5 的特殊含义:这是文件系统的根。所以我们知道 homeroot 都是顶级子卷的子卷。另一方面 hartan/btrfs-subvolume-test.first 是子卷 ID 256(也就是 home)的子卷。

在下一节我们会看看子卷 roothome 是怎么来的。

检查 Fedora Linux 的默认子卷

当你从头创建一个新的 Btrfs 文件系统,里面是没有子卷的(当然,除了顶级子卷)。所以 Fedora Linux 里的 homeroot 子卷是哪里来的?

它们是安装程序在安装时创建的。传统的安装经常会为 //home 目录包含单独的文件系统分区。在启动时,它们通过恰当的挂载组成一个完整的文件系统。但这个方法有一个问题:除非你使用像 lvm 这样的技术,想在将来改变分区的大小是非常难的。因而你可能出现 //home 用完空间的情况,然而还有很多其他没被使用的分区和空间剩余。

因为 Btrfs 子卷全都是相同文件系统的一部分,它们共享底层文件系统提供的空间。还记得我们在上面创建的子卷吗?我们从未告诉 Btrfs 它们多大:一个子卷可以占据文件系统拥有的全部空间,默认是不会阻止这种行为的。但是,我们 可以 通过 Btrfs 的 限额组 qgroup 动态地约束其大小,同时也可以在运行时修改(我们将在后续的文章中了解如何做的)。

另外一个分离 //home 的优势是我们可以分别进行 快照 。子卷是快照的边界,对一个子卷的快照永远不会包含该子卷下面的其他子卷的内容。快照的更多细节会在后续的文章中介绍。

理论已经足够了!我们来看看这是怎么回事。首先确保你的根文件系统类型是 Btrfs :

$ findmnt -no FSTYPE /
btrfs

然后我们获取它所在的分区:

$ findmnt -vno SOURCE /
/dev/vda3

记住我们可以通过特殊的子卷 ID 5 挂载文件系统的根(适应文件系统分区!):

$ mkdir fedora-rootsubvol
$ sudo mount -o subvolid=5 /dev/vda3 ./fedora-rootsubvol
$ ls fedora-rootsubvol/
home  root

而且还有 Fedora Linux 安装的子卷!但 Fedora Linux 是如何知道子卷 root 属于 / ,而 home 属于 /home 的呢?

文件 /etc/fstab 包含了所谓的文件系统的静态信息。简而言之,在你系统启动的时候会一行一行地读取这个文件,然后挂载那里列出的所有文件系统。在我的系统上,这个文件长这样:

$ cat /etc/fstab
# [ ... ]
# /etc/fstab
# Created by anaconda on Sat Oct 15 12:01:57 2022
# [ ... ]
#
UUID=5e4e42bb-4f2f-4f0e-895f-d1a46ea47807 /                       btrfs   subvol=root,compress=zstd:1 0 0
UUID=e3a798a8-b8f2-40ca-9da7-5e292a6412aa /boot                   ext4    defaults        1 2
UUID=5e4e42bb-4f2f-4f0e-895f-d1a46ea47807 /home                   btrfs   subvol=home,compress=zstd:1 0 0

(注意上面的 “UUID” 开头行的内容被换行成两行)

每行开头的 UUID 用于标识你系统上的硬盘和文件系统分区(大概相当于我在上面使用的 /dev/vda3 )。第二列是文件系统应该挂载在文件系统树上的路径。第三列是文件系统类型。我们可以看到 //home 都是 btrfs 类型,正如我们期望的那样!最后,第四列是:这些是挂载选项,这里说通过 subvol=root 选项去挂载 / 。这正是我们一直在 btrfs subvolume list / 里看到的输出!

有了这些信息,我们可以重新构建创建这个文件系统项的 mount 命令

$ sudo mount -o subvol=root,compress=zstd:1 UUID=5e4e42bb-4f2f-4f0e-895f-d1a46ea47807 /

(再次,上面的 “UUID” 开头行的内容被换行成两行)

这就是 Fedora Linux 如何使用 Btrfs 子卷!如果你对好奇 Fedora Linux 为什么选择 Btrfs 作为默认的文件系统,请参阅下面链接的更改提议 [1]

Btrfs 子卷的更多内容

Btrfs 维基提供了关于子卷的更多信息,其中最重要的是可应用于 Btrfs 子卷的挂载选项。有些选项,比如 compress 只能应用到文件系统的层面,因而会影响一个 Btrfs 文件系统的所有子卷。你可以通过下面的链接找到entry [2]

如果你对哪些目录是普通目录和哪些是子卷有困惑,你可以对你的子卷采用特殊的命名约定。例如,你可以给子卷名加上 @ 前缀去方便区分。

现在你知道子卷表现得就像文件系统,有人可能会问如何才能最好地将子卷放置在特定位置。比如你想要一个 Btrfs 子卷在 ~/games 下面,然而你的主目录(~)本身就是一个子卷,你该如何实现呢?鉴于上面的例子,你可以使用像 sudo btrfs subvolume create ~/games 的命令。这样,你创建了所谓的 嵌套 子卷:在你的子卷 ~ 里,有一个子卷 games 。这正是一种达成目的的方法。

其他有效的方法就是如同 Fedora 默认行为那样:在根子卷下创建所有子卷(也就是它们的父子卷 ID 是 5 ),然后挂载它们到特定的位置。Btrfs 维基有这些方法的概述和对于各自文件系统管理影响的简短讨论 [3]

总结

在本文中,我们探索了 Btrfs 子卷,它们像是 Btrfs 文件系统内部的独立的 Btrfs 文件系统。我们学习了如何创建、挂载和删除子卷。最后,我们探讨了 Fedora Linux 如何在我们完全没有注意到的情况下使用子卷。

本系列的下一篇文章将讨论:

  • 快照 - 回到过去
  • 压缩 - 透明地节省存储空间
  • 配额组 - 限制文件系统大小
  • RAID - 替代 mdadm 配置

如果你还想了解与 Btrfs 相关的其他主题,请查看 Btrfs 维基 [4] 和文档 [5] 。不要忘记查看本系列的第一篇文章(如果你还没有看过的话)!如果你认为本系列文章缺少了一些内容,请在下面的评论中告诉我们。再会!

参考资料

  1. https://fedoraproject.org/wiki/Changes/BtrfsByDefault#Benefit_to_Fedora ↩︎
  2. https://btrfs.readthedocs.io/en/latest/Subvolumes.html ↩︎
  3. https://btrfs.wiki.kernel.org/index.php/SysadminGuide#Layout ↩︎
  4. https://btrfs.wiki.kernel.org/index.php/Main_Page ↩︎
  5. https://btrfs.readthedocs.io/en/latest/Introduction.html ↩︎

(题图:MJ/f047ea87-2490-40e5-9f91-d48d236675e5)


via: https://fedoramagazine.org/working-with-btrfs-subvolumes/

作者:Andreas Hartmann 选题:lujun9972 译者:A2ureStone 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章是《Btrfs 详解》系列文章中的一篇。从 Fedora Linux 33 开始,Btrfs 就是 Fedora Workstation 和 Fedora Silverblue 的默认文件系统。

介绍

文件系统是现代计算机的基础之一。它是任何操作系统必不可少的一部分,且通常不为人注意。但是,像 Btrfs 这样的现代文件系统提供了许多很棒的特性,使计算机的使用更加方便。例如,它可以无感地为你压缩文件,或者为增量备份建立可靠的基础。

这篇文章将带你高屋建瓴地了解 Btrfs 文件系统是如何工作的,有什么特性。本文既不会过多涉及技术细节,也不会研究其底层实现,系列后续的文章会详细介绍一些重要特性。

什么是文件系统

如果你基本了解过文件系统是如何工作的,那么下面的内容对你应该是不陌生的,你可以直接跳到下一节。否则,请先阅读下面对文件系统的简短介绍。

简单来说,文件系统允许你的 PC 去寻找存储在磁盘上的数据。这听起来像是微不足道的工作,但实际上时至今日各种类型的非易失性存储设备(比如机械硬盘、固态硬盘、SD 卡等等)仍然与 1970 年代 PC 被发明时基本相同:一个(巨大的)存储块集合。

Block ” 是最小的可寻址存储单元。PC 上的每个文件内容被存储在多个块中。一个块通常是 4096 字节的大小。这取决于你的硬件和在这之上的软件(即文件系统)。

文件系统允许我们从海量的存储块中查找文件的内容,这是通过所谓的 inode 去实现的。一个 inode 在特殊格式的存储块里记录了文件的信息。这包含文件的大小,哪里去寻找组成文件内容的存储块,访问规则(即谁可读,可写,可执行)等等。

下面是 inode 的示意图:

A text file “myfile.txt” and a hypothetical example of its representation on disk. All the squares are individual storage blocks.

inode 的结构对文件系统的功能有巨大的影响,因此它是各种文件系统诸多的重要数据结构之一。出于这个原因,每个文件系统有各自的 inode 结构。如果你想了解更多信息,看看下面 链接 关于 Btrfs 文件系统 inode 结构的内容。如需更详细地了解各个字段的含义,你可以 参考 ext4 文件系统的 inode 结构。

写时复制(CoW)文件系统

相比 ext4,Btrfs 拥有的杰出特性之一是,它是一个 写时复制 Copy-on-Write (CoW)文件系统。当一个文件被改变和回写磁盘,它不会故意写回它原来的位置,而是被复制和存储在磁盘上的新位置。从这个意义上,可以简单地认为 Cow 是一种 “重定向”,因为文件写入被重定向到不同的存储块上。

这听起来很浪费,但实际上并不是。这是因为被修改的数据无论如何一定会被写到磁盘上,不管文件系统是如何工作的。Btrfs 仅仅是确保了数据被写入在之前没被占据的块上,所以旧数据保持完整。唯一真正的缺点就是这种行为会导致文件碎片化比其他文件系统要快。在日常的电脑使用中,你不太可能会注意到这点差异。

CoW 的优势在哪里?简单的说:文件被修改和编辑的历史被保存了下来。Btrfs 保存文件旧版本的引用(inode)可以轻易地被访问。这个引用就是快照:文件系统在某个时间点的状态镜像。这将是这系列文章里的单独的一篇,所以暂时留到后面介绍。

除了保存文件历史,CoW 文件系统永远处于一致的状态,即使之前的文件系统事务(比如写入一个文件)由于断电等原因没有完成。这是因为文件系统的元数据更新也是写时复制的:文件系统本身永远不会被覆写,所以中断不会使其处于部分写入的状态。

对文件的写时复制

你可以将文件名视为对 inode 的指针。在写入文件的时候,Btrfs 创建一个被修改文件内容(数据)的拷贝,和一个新的 inode(元数据),然后让文件名指向新的 inode,旧的 inode 保持不变。下面是一个假设示例来阐述这点:

Continuation of the example above: 3 more bytes of data were added

这里 myfile.txt 增加了三个字节。传统的文件系统会更新中间的 Data 块去包含新的内容。CoW 文件系统不会改变旧的数据块(图中灰色),写入(复制)更改的数据和元数据在新的地方。值得注意的是,只有被改变的数据块被复制,而不是全部文件。

如果没有空闲的块去写入新内容,Btrfs 将从被旧文件版本占据的数据块中回收空间(除非它们是快照的一部分,本系列后续文章会看到)。

对目录的写时复制

从文件系统的角度看,目录只是特殊类型的文件。与常规文件不同,文件系统直接解释数据块的内容。一个目录有自身的元数据(inode,就像上面说的文件一样)去记录访问权限或修改时间。最简单的形式,存在目录里的数据(被叫作目录项)是一个 inode 引用的列表,每个 inode 又是另外的文件或目录。但是,现代文件系统在目录项中至少会存储一个文件名和对应的 inode 引用。

之前已经指出,写入一个文件会创建之前 inode 的副本,并相应修改其内容。从根本上,这产生了一个和之前无关的新的 inode 。为了让被修改的文件对文件系统可见,所有包含这个文件引用的目录项都会被更新。

这是一个递归的过程!因为一个目录本身是一个带有 inode 的文件。修改目录里的任何一项都会为这个目录文件创建新的 inode 。这会沿着文件系统树递归直到文件系统的根。

所以,只要保留对任何旧目录的引用,并且这些目录没有被删除和覆写,就可以遍历之前旧状态的文件系统树。这就是快照的功能。

后续文章可以期待的内容

Btrfs 不只是一个 Cow 文件系统。它目标是实现高级特性的同时关注容错、修复和易于管理(参见 文档)。本系列未来的文章将会专门介绍这些特性。

  • 子卷 – 文件系统中的文件系统
  • 快照 – 回到过去
  • 压缩 – 透明节省存储空间
  • 配额组 – 限制文件系统大小
  • RAID – 替代 mdadm 配置

这远非 Btrfs 特性的详尽列表。如果你想全面地了解可用特性,查看 维基文档

总结

我希望我已能激起你进一步了解计算机文件系统的兴趣。如果目前你有任何疑问,请在评论区留言讨论以便在日后文章中探讨,同时,你也可以自行学习文中提供的相关资源。如果你发现 Btrfs 中某项特别有趣的功能,也欢迎在评论区提出。如果某个主题收到足够的关注,我可能会在系列文章中新增相关内容。下一篇文章再见!

(题图:MJ/35fa1970-1806-4026-8d58-095a56206ec9)


via: https://fedoramagazine.org/working-with-btrfs-general-concepts/

作者:Andreas Hartmann 选题:lkxed 译者:A2ureStone 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

引言

这篇概述文章将告诉你为何以及如何迁移你的当前分区到 Btrfs 文件系统。如果你对此感兴趣,请阅读这篇分步指南来完成。

从 Fedora 33 开始,新安装的 Fedora 操作系统默认文件系统为 Btrfs。我确信大部分用户现在已经听说了它的优势:写时复制、内置校验、灵活的压缩方式、简易的快照和回滚方式。它确实是一个现代化的文件系统,为桌面存储带来新的功能。

在升级到 Fedora 33 后,我想利用 Btrfs 的优势,但对我个人来说,我不想因为“只是为了改变文件系统”而去重装整个系统。我发现(只有)寥寥无几的具体如何做转换的教程,所以我决定在这里分享我的详细经验。

小心!

这样做你是在玩火。希望你阅读以下内容时不要感到惊讶:

在编辑分区和转换文件系统时,你的数据可能会被破坏和丢失。最终,你可能会得到一个不能启动的操作系统,并面临数据恢复的风险。你可能会无意删除你的分区,或者以其它方式破坏了你的操作系统。

这些转换过程即使对于生产系统来说也是安全的 —— 前提是你提前做好了计划,对关键数据做好了备份和回滚计划。作为一个 可以执行超级权限的系统管理员,你可以在没有限制、没有任何常规安全防护措施的情况下,做任何事情。

安全的方式:重装 Fedora

重装操作系统是转换文件系统到 Btrfs 的 “官方” 方式,推荐给大多数用户使用。因此,如果在这个教程中有那么一点不确定,就选择这种方式。步骤大致如下:

  1. 备份你的主文件夹和你系统中可能会用到的任何数据,比如 /etc。(编者按:虚拟机也是这样)
  2. 将已安装的安装包以列表形式保存到到文件中。
  3. 重新安装 Fedora,删除你当前的分区,并选择新的 Btrfs 默认分区方案。
  4. 恢复主文件夹的内容,并使用软件包列表文件重装软件包。

对于详细的步骤和命令,请看一位社区用户在 ask.fedoraproject.org 站点的评论。如果正确完成,你将得到一个和之前一样的操作系统,使丢失数据的风险最小化。

转换的利弊

让我们快速澄清一下:这种文件系统转换有什么优势和劣势?

优势:

  • 当然,不需要重新安装!你的系统里的所有文件和之前一模一样。
  • 技术上来说,没有备份的情况下,就地进行是可能的。
  • 你会学到许多关于 Btrfs 的知识!
  • 如果所有都按计划进行,会是相当快的一个过程。

劣势:

  • 你必须熟悉终端环境和 shell 命令。
  • 你可能会丢失数据,参见上文。
  • 如果出了什么问题,你得自己解决。

特别之处:

  • 你需要大约 20% 的可用磁盘空间才能成功转换。但对于完整的备份和重装方式,你可能需要的空间更多。
  • 你可以在转换过程中自定义你分区的所有参数,但如果选择重装,你也可以从 Anaconda 自定义。

LVM 怎么办?

在近期几次 Fedora 安装中,LVM 布局一直是默认的。如果你有一个带有多个分区(例如 //home)的 LVM 分区布局,你得以某种方式合并它们,来获得 Btrfs 所有性能。

如果选择这样做,你可以单独转换分区到 Btrfs 文件系统,同时保留卷组。然而,迁移到 Btrfs 文件系统的优势之一是摆脱 LVM 分区布局强加的限制。你也可以利用 Btrfs 文件系统提供的收发功能在转换后来合并分区。

另见 《Fedora 杂志》: 利用 LVM 回收硬盘空间从 Btrfs 快照中恢复文件 以及 在 Btrfs 和 LVM-ext4 两者之间做选择

了解 Btrfs

建议阅读以下内容对 Btrfs 文件系统是什么有一个基础的了解。如果你没有把握,只有选择重装 Fedora 这种安全的方式。

必须了解的:

有用的资源:

转换步骤

创建一个实时镜像

由于不能转换已挂载的文件系统,我们将通过 Fedora 实时镜像 Live Image 进行。安装 Fedora 镜像写入工具,然后 “烧录” Fedora 33 到你的 U 盘中来创建实时镜像。

释放磁盘空间

btrfs-convert 会在分区的剩余空间重新创建文件系统的元数据,同时保持所有已有的 ext4 文件系统数据还在它当前的位置上。

不幸的是,所需的剩余空间的大小无法提前知道:如果没有足够的空间,转换将会失败(但不会破坏数据)。这里有一些释放空间有用的方法:

  • 利用 baobab 来识别大容量的文件和文件夹,然后移除。如果可能的话,不要手动删除主文件夹以外的文件。
  • 清理旧的系统日志:journalctl –vacuum-size=100M
  • 如果你正使用 Docker,请小心地使用类似 docker volume prunedocker image prune -a 这样的工具。
  • 清理 GNOME Boxes 之类的虚拟机内不用的镜像。
  • 清理不用的软件包和 Flatpak 包:dnf autoremoveflatpak remove –unused
  • 清理软件包缓存:pkcon refresh force -c -1dnf clean all
  • 如果你有把握,你可以谨慎的清理 ~/.cache 文件夹。

转换到 Btrfs

备份你所有有价值的数据,确保你的系统已完全更新,然后重启到实时镜像。运行 gnome-disks 工具找到你所拥有的设备的路径,比如 /dev/sda1(如果你在使用 LVM,它可能看起来有所不同)。检查文件系统然后执行转换:(编者按:以下命令使用 root 用户运行,谨慎使用!)

$ sudo su -
# fsck.ext4 -fyv /dev/sdXX (请替换为你的具体的设备路径)
# man btrfs-convert (阅读它)
# btrfs-convert /dev/sdXX (请替换为你的具体的设备路径)

这将会花十几分钟甚至几个小时,依据分区的大小和是机械硬盘还是固态硬盘。如果你看到错误,你可能需要更多剩余空间。作为最后的手段,你可以尝试 btrfs-convert -n

怎样回滚?

如果因为某些原因转换失败,你的分区将保持在 ext4 文件系统或者它之前的状态。如果你想在成功转换之后回滚,简单如下:

# btrfs-convert -r /dev/sdXX
警告! 如果你做了以下这些事情之一,你将永久失去回滚的功能:碎片整理、均衡或者删除 ext2_saved 子卷。

由于 Btrfs 文件系统的写时复制特性,你可以安全的复制/移动甚至删除文件、创建子卷,因为 ext2_saved 会保持引用旧数据。

挂载和检查

现在这个分区应该已经有了 Btrfs 文件系统。挂载它然后查看你的文件……和子卷!

# mount /dev/sdXX /mnt (请替换为你的具体的设备路径)
# man btrfs-subvolume (阅读它)
# btrfs subvolume list / (使用 -t 以表格方式查看)

因为你已经阅读了 相关的手册页,你应该知道创建子卷快照是安全的,并且有 ext2-saved 子卷作为你之前数据的简易备份。

是时候阅读 Btrfs 系统管理指南了,这样你就不会把常规文件夹和子卷混淆了。

创建子卷

我们希望实现一个“扁平”子卷布局,这和 Anaconda 默认创建的布局相同:

toplevel (卷根目录,不能被默认挂载)
  +-- root (子卷根目录,被挂载到 /)
  +-- home (子卷根目录,被挂载到 /home)

你可以跳过这个步骤,或者使用一个不同的布局。这种特殊结构的优势是你可以轻松的创建 /home 的快照,并且对每个子卷使用不同的压缩和挂载参数。

# cd /mnt
# btrfs subvolume snapshot ./ ./root2
# btrfs subvolume create home2
# cp -a home/* home2/

这里我们已经创建了两个子卷。root2 是一个完整的分区快照,而 home2 开始是一个空子卷,然后我们往里复制内容。(这个 cp 命令不会重复数据,所以会很快。)

  • /mnt 目录(顶层子卷),删除除了 root2home2ext2_saved 之外的所有内容。
  • 重命名 root2home2 子卷为 roothome
  • root 子卷里,清空 home 目录,以便之后我们能够挂载 home 子卷。

如果都做对了,那就很简单了!

修改 fstab 分区表

为了重启之后挂载新卷,必须要修改 fstab,用新的行来代替旧的 ext4 文件系统挂载行。

你可以使用 blkid 命令来找到你的分区的 UUID。

UUID=xx / btrfs subvol=root 0 0 (请替换为你的具体 UUID)
UUID=xx /home btrfs subvol=home 0 0 (请替换为你的具体 UUID)

(注意如果指向的是同一个分区,那么这两个 UUID 是相同的。)

这些都是新安装的 Fedora 33 的默认值。在 fstab 中,你也可以选择自定义压缩和添加类似 noatime 这样的参数。

可以查看 关于压缩参数的维基页面man 5 btrfs 了解所有相关的参数。

chroot 到系统

如果你曾经做过系统恢复,我想你肯定知道这些命令。这里,我们将得到一个 基本上 在你系统里的 shell 提示符,可以访问网络。

首先,我们必须重新挂载 root 子卷到 /mnt 目录,然后挂载 /boot/boot/efi 分区(它们可能有所不同,这取决于你的文件系统布局):

# umount /mnt
# mount -o subvol=root /dev/sdXX /mnt (请替换为你的具体的设备路径)
# mount /dev/sdXX /mnt/boot (请替换为你的具体的设备路径)
# mount /dev/sdXX /mnt/boot/efi (请替换为你的具体的设备路径)

然后我们继续挂载系统设备:

# mount -t proc /proc /mnt/proc
# mount --rbind /dev /mnt/dev
# mount --make-rslave /mnt/dev
# mount --rbind /sys /mnt/sys
# mount --make-rslave /mnt/sys
# cp /mnt/etc/resolv.conf /mnt/etc/resolv.conf.chroot
# cp -L /etc/resolv.conf /mnt/etc
# chroot /mnt /bin/bash
$ ping www.fedoraproject.org

重装 GRUB 及内核

最容易的方法就是重装 GRUB 和 内核,因为它完成了所有必要的配置 —— 现在我们可以访问网络了。所以,在 chroot 环境内部:

# mount /boot/efi
# dnf reinstall grub2-efi shim
# grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg
# dnf reinstall kernel-core
...或者干脆重新生成 initramfs:
# dracut --kver $(uname -r) --force

如果你是支持 UEFI 的系统,这里是适用的。如果你是 BIOS 的系统,请查看下面的文档。重启之前,让我们查看是否一切正常:

# cat /boot/grub2/grubenv
# cat /boot/efi/EFI/fedora/grub.cfg
# lsinitrd /boot/initramfs-$(uname -r).img | grep btrfs

你应该在 grubenvgrub.cfg 有正确的分区 UUID 或指向(grubenv 可能没有更新,如有必要可以编辑它),并在 grub.cfg 中看到 insmod btrfs 配置和在 initramfs 镜像中有 btrfs 模块。

参见: Fedora 系统管理指南中的 重装 GRUB 2验证初始 RAM 磁盘镜像

重启

现在系统能够正常启动。如果不能,别慌,回到实时镜像修复这个问题。最坏的情况下,你可以从那里重装 Fedora 。

首次启动之后

检查你的新 Btrfs 文件系统一切都正常。如果你觉得没问题,你需要回收旧的 ext4 快照使用的空间,进行碎片整理和平衡子卷。后两者可能要花一些时间,并且相当耗费资源。

对此你必须这样挂载顶级子卷:

# mount /dev/sdXX -o subvol=/ /mnt/someFolder
# btrfs subvolume delete /mnt/someFolder/ext2_saved

然后,当机器有空闲时间时,运行这些命令:

# btrfs filesystem defrag -v -r -f /
# btrfs filesystem defrag -v -r -f /home
# btrfs balance start -m /

最后,有一个 “非写时复制” 属性,对于新系统,这个属性是为虚拟机镜像文件夹自动设置的。如果你使用虚拟机的话,可以设置它:

# chattr +C /var/lib/libvirt/images
$ chattr +C ~/.local/share/gnome-boxes/images

这个属性只会对在这些文件夹里的新文件生效。复制镜像并删除原镜像,你可以通过 lsattr 确认结果。

总结

我真心希望你发现这个教程是有用的,并且能够对是否在你的系统上转换为 Btrfs 做出谨慎而明智的决定。祝你成功转换!

欢迎在评论中分享你的经验,或者遇到更深层次的问题,请在 ask.fedoraproject.org 提问。


via: https://fedoramagazine.org/convert-your-filesystem-to-btrfs/

作者:Gergely Gombos 选题:lujun9972 译者:hwlife 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出