Btrfs文件系统已经逐渐被各种Linux发行版本支持(作为系统分区格式),Btrfs具备CoW(写时复制)的特性,相比于之前的很多文件系统增添了很多特殊的功能,本文对其中的常用功能进行了介绍。 但是由于文件系统操作不当容易丢失数据,操作之前记得做好额外备份。

写时复制

写时复制(Copy-on-write, CoW)指了在多个调用者请求相同资源时,只有在某个调用者试图修改资源的内容时,系统才会为其复制一份专用副本。这样没有写操作的时候,就不会有多余的副本被创建。1 CoW的缺点之一在于对于像VM镜像、数据库文件这样的就地更改(updated-in-place)的文件,会导致写入碎片化2所以对于这一类数据,不妨建一个子卷然后禁用CoW来储存他们。(别忘了修改fstab

Btrfs默认启用写时复制,要停止使用写时复制,使用nodatacow选项,但是这一更改只会影响新创建的文件,对于已有文件(夹)使用下列命令进行修改,但仍存在一些细节问题,使用前务必参见参考资料中关于此节的详细描述3

chattr +C </path/to/file/or/folder>

子卷(Subvolume)4

Btrfs通过Subvolume来实现在备份时排除某些文件夹。

挂载子卷

通过设置挂载的选项可以挂载指定的子卷:

mount -o subvol=<subvol> <device> <mount_path>

子卷的修改操作

列出子卷

btrfs subvolume list -p path

使用后会列出对应path下的所有子卷,其数量可能会很多,因为所有的快照也以subvolume的形式储存,有意思的是Docker镜像也被保存为了subvolume:

创建子卷

btrfs subvolume create </path/to/subvolume>

这里的path指的是子卷的绝对路径,比如当前挂载了@/mnt/@目录下,则使用路径/mnt/@/home创建出来的子卷为@/home

删除子卷

btrfs subvolume delete /path/to/subvolume

如果只移除文件目录,而不使用btrfs subvolume delete命令并不会真正删除一个子卷。

默认子卷

# 获取默认子卷
btrfs subvolume get-default /
# 设置默认子卷
btrfs subvolume set-default <subvolume-id> /

临时挂载

# 使用路径挂载
mount -t btrfs -o subvol=<subvolume> </mount/point>
# 使用id挂载
mount -t btrfs -o subvolid=<id> </dev/device> </mount/point>

Btrfs子卷组织形式的探究5

在openSUSE中查看当前的Btrfs的子卷,可能会显示大量的子卷,因为snapshot实际也是通过子卷来实现的,另外值得注意的是Docker镜像也被作为snapshot独立开了:

~$ sudo btrfs subvolume list /
ID 256 gen 90 top level 5 path @
ID 257 gen 113574 top level 256 path @/var
...
ID 263 gen 113574 top level 256 path @/home
ID 266 gen 112569 top level 256 path @/.snapshots
ID 298 gen 98293 top level 257 path @/var/lib/docker/btrfs/subvolumes/ce11ad5...    # docker镜像
...

其中@代表了文件系统的根(rootfs),但事实上它也仍然是一个snapshot,最顶层的卷是以0为标号的子卷,不过通常不使用。 同时默认的/同样也不是@子卷,一般也是某一个子卷,只是默认被挂载为了/,通过查看默认子卷可以得知:

~$ btrfs subvolume get-default /
ID 267 gen 113599 top level 266 path @/.snapshots/1/snapshot  # 一个snapshot被作为默认子卷,挂载为了文件系统的 `/` 目录

可见当前系统的/实际上是一个路径为@/.snapshots/1/snapshot的子卷,真正的@在openSUSE中是隔离开的,作为独立的根来储存需要永久保存的子卷。

创建子卷的正规步骤

正如上述讨论,由于目前的系统目录也是一个(临时)快照。 如果我们此时要创建一个子卷,不可以建立在一个一个已有的快照下,否则在进行rollback操作后就不能再删除这个子卷了。正确的操作因该是将这个子卷建立在@子卷下。

sudo mount /dev/sda2 -o subvol=@ /mnt
sudo btrfs subvolume create /mnt/usr/important
sudo umount /mnt

快照

Btrfs的快照是建立在其“写时复制”的功能基础上的。 创建快照可以使用如下命令:

btrfs subvolume snapshot </path/to/source> </path/to/dest>

对于openSUSE,目标目录通常为/.shapshots,这一目录为默认的统一存放快照的目录。 另外添加参数-r可以创建只读快照,在只读快照上再创建一个快照可以获得只读快照的一个可写版本。

注意快照不是递归包含的,意味着子卷里的子卷在快照中会是空目录。 这也是为什么openSUSE下部分目录被排除在默认的snapper备份之外:它们都被创建为了额外的子卷,由于上述非递归性,他们在对/创建的快照中均被忽略了。

Btrfs启用压缩6

在openSUSE中是支持Btrfs的压缩功能的,通过mount的参数可以启用压缩:

mount -o compress </dev/sdx> </mount/point>

compress的默认规则是:如果你创建了一个文件,Btrfs压缩后发现压缩率低,那对于之后的写入它都不再会进行压缩。如果不希望这样,可以使用compress-force。 对于已经写入的文件,均不会被压缩,压缩仅对新写入的文件有效

压缩有三种算法可选:

  1. lzo:压缩率低但是CPU资源占用少。
  2. zlib:压缩率高但是资源占用多。
  3. zstd:旧版本内核和GRUB引导对其缺乏支持,暂时忽略。

fstab中永久启用压缩,并指定压缩算法(算法以不指定):

UUID=1a2b3c4d /home btrfs subvol=@/home,compress=lzo  0   0

使用snapper进行管理7

snapper通过一系列的配置来管理Btrfs分区,配置文件默认位于/etc/snapper/configs/下。 默认的方案只为/创建快照,且内容还要排除名下的子卷。

创建一个新的配置

sudo snapper -c <config-name> create-config </path>

这一操作会创建一个快照并从/etc/snapper/config-templates/default获取一套默认配置。

配置快照的设置

见openSUSE相关文档的对应章节,以获得更准确的信息。

使用snapper -c home set-config "<KEY>=<value>"来修改设置。