> For the complete documentation index, see [llms.txt](https://huataihuang.gitbook.io/cloud-atlas-draft/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://huataihuang.gitbook.io/cloud-atlas-draft/os/linux/storage/filesystem/btrfs/btrfs_startup.md).

# Btrfs文件系统快速起步

> 本文是在CentOS 7上实践部署Btrfs文件系统，实现单磁盘卷管理和RAID功能

## 安装工具包

* RedHat EPEL / CentOS

```
yum install btrfs-progs -y
```

* debian / ubuntu

```
apt-get install btrfs-tools -y
```

## 单磁盘btrfs操作

对于构建分布式文件系统的底层文件系统，不需要构建RAID，可以采用单盘操作。这里的案例是采用 `/dev/sda4`主分区作为btrfs

```
fdisk /dev/sda
```

创建磁盘分区 `/dev/sda4`

* 使用Btrfs格式化文件系统

```
mkfs.btrfs /dev/sda4
```

此时提示

```
/dev/sda4 appears to contain a partition table (dos).
Error: Use the -f option to force overwrite.
```

这是因为这个分区是一个以前删除的包含文件系统的分区（最早这个分区是ext4文件系统分区），所以重新创建同一位置的分区而没有事先擦除过（可以使用 `dd if=/dev/zero of=/dev/sda4 bs=512 count=1024`或者使用`wipefs`），就会报告上述错误。实际是一种提示你可能划分错分区的告警。如果确定分区没有错误，可以强制创建新文件系统（`mkfs.btrfs -f /dev/sda4`），或者使用`wipefs`工具来完成旧分区信息擦除。

```
wipefs -a -f /dev/sda4
```

```
Options:
 -a, --all           wipe all magic strings (BE CAREFUL!)
 -f, --force         force erasure
```

此时提示

```
/dev/sda4: 2 bytes were erased at offset 0x000001fe (dos): 55 aa
/dev/sda4: calling ioclt to re-read partition table: Invalid argument
```

再次执行`partprobe`扫描分区，然后重新创建btrfs文件系统就可以成功。可以看到输出提示如下

```
Turning ON incompat feature 'extref': increased hardlink limit per file to 65536
Turning ON incompat feature 'skinny-metadata': reduced-size metadata extent refs
fs created label (null) on /dev/sda4
    nodesize 16384 leafsize 16384 sectorsize 4096 size 1.04TiB
```

> 参考 [Error when creating BTRFS Filesystem](http://unix.stackexchange.com/questions/128512/error-when-creating-btrfs-filesystem)

## 检查btrfs文件系统

```
btrfs filesystem show /dev/sdb4
```

显示输出

```
Label: none  uuid: e9191083-94ea-4709-a776-218c473c2bc1
    Total devices 1 FS bytes used 112.00KiB
    devid    1 size 1.04TiB used 2.04GiB path /dev/sda4
```

## 挂载btrfs文件系统

* 挂载btrfs文件系统方法

```
mkdir /data
mount /dev/sda4 /data
```

* 如果要启用btrfs的压缩功能（默认是`zlib`）

可以使用`-o compress=`参数。默认的`zlib`压缩方式的压缩比较高，如使用`lzo`压缩方式则速度较快且CPU负载较轻。使用压缩模式，特别是`lzo`压缩模式，可以提高数据通过性能。注意，btrfs不会压缩已经在应用层压缩过的文件（如视频，音乐，图像等）。

如果内核版本 v4.14+ ，则推荐使用 zstd 压缩算法。

```
mount -o compress=lzo /dev/sda4 /data
mount -o compress=zlib /dev/sda4 /data
mount -o compress=zstd /dev/sda4 /data
mount -o compress /dev/sda4 /data
```

然后添加`/etc/fstab`设置如下

```
/dev/sda4    /data    btrfs    defaults,compress=lzo  0    1
```

> 注意：根据btrfs官方文档 [Compression](https://btrfs.wiki.kernel.org/index.php/Compression) 目前已经支持3种压缩算法，并且从内核 v.4.14 开始，可以采用 ZSTD 压缩算法，能够获得性能和压缩率的较好平衡。具体对比可以参考 [Btrfs Zstd Compression Benchmarks On Linux 4.14](https://www.phoronix.com/scan.php?page=article\&item=btrfs-zstd-compress\&num=4)
>
> 如果没有指定算法，默认的压缩算法是zlib

## 文件系统故障恢复

如果有一个btrfs文件系统故障，可以通过`recovery`挂载参数挂载，此时会尝试seek并找找寻文件系统根的可用副本：

```
mount -o recovery /dev/sda4 /data
```

## 扩容和缩容btrfs文件系统

* 缩小2G空间

```
btrfs filesystem resize -2g /data
```

* 缩小500M空间

```
btrfs filesystem resize -500m /data
```

* 扩大1g空间

```
btrfs filesystem resize +1g /data
```

## 多磁盘btrfs

Btrfs也支持多块磁盘构建RAID

* meta磁盘可以采用单磁盘

```
mkfs.btrfs -m single /dev/sdb
```

> `-m`表示`metadata`

* 如果要构建RAID0模式的metadata磁盘（这样性能更好）

```
mkfs.btrfs -m raid0 /dev/sdb /dev/sdc /dev/sdd
```

* 如果要构建冗余的数据磁盘（RAID1）同时构建没有冗余的单metadata磁盘

```
mkfs.btrfs -m raid0 -d raid1 /dev/sdb /dev/sdc /dev/sdd
```

* 如果需要构建同时数据冗余的metadata磁盘和数据磁盘，则只要使用`-d raid1`就可以，因为`meatdata`磁盘默认就是RAID1

```
mkfs.btrfs -d raid1 /dev/sdb /dev/sdc /dev/sdd
```

* 也可以使用RAID10（`-m raid10`或`-d raid10`），此时需要至少4快磁盘。不过，对于btrfs，不强制要求磁盘容量一样大小。

## 增加或删除物理磁盘

可以在线增加磁盘或者删除磁盘，非常方便

```
btrfs device add /dev/sde /data
```

然后检查文件系统

```
btrfs filesystem show /dev/sde
```

此时可以看到`/dev/sde`设备虽然被添加，但是还没有被使用。如果使用的是**非**`RAID0`模式，可以使用`balance`命令来重新平衡数据和metadata

```
btrfs filesystem balance /data
```

可以删除掉`没有损坏`(**intact**)的硬盘（但是剔除故障磁盘会比较麻烦，见后述）

```
btrfs device delete /dev/sdc /data
```

### 剔除故障磁盘

要移除一个`故障`（**failed**）的磁盘，先umount文件系统，

```
umount /data
```

然后挂载成降级模式

```
mount -o degraded /dev/sdb /mnt
```

这样就可以移除故障的磁盘。

如果构建了RAID的磁盘需要一定数量的磁盘（例如RAID1要求双磁盘，RAID10要求4块磁盘），可需要先添加一块`intact`磁盘 - 这里添加一块替换磁盘`/dev/sdf`

```
btrfs device add /dev/sdf /data
```

**只有确保有足够的`intact`设备才可以执行以下的替换命令**

```
btrfs device delete missing /data
```

## 更改RAID级别

Btrfs支持在线更改RAID级别，例如最初数据和metadata是RAID0，可以在线更改成RAID1

```
btrfs blance start -dconvert=raid1 -mconvert=raid1 /data
```

## 创建子卷

* 可以在现有卷下创建子卷

在btrfs中，可以创建卷的子卷，并且可以创建子卷的快照，或者挂载子卷来替换上层的卷

```
btrfs subvolume create /data/libvirt
```

显示输出

```
Create subvolume '/data/libvirt'
```

此时可以看到在 `/data` 目录下创建了一个子目录 `libvirt`

```
ls -lh /data
```

显示输出

```
total 0
drwxr-xr-x 1 root root 0 Nov 22 17:58 libvirt
```

> 注意：这个是子卷，这个`libvirt`子卷的`subvolid`是257

* 检查 `/data` 的子卷

```
btrfs subvolume list /data
```

显示输出

```
ID 257 gen 6 top level 5 path libvirt
```

* 可以创建子卷的子卷

```
btrfs subvolume create /data/libvirt/images
```

此时显示

```
Create subvolume '/data/libvirt/images'
```

* 此时检查卷`/data`可以看到显示出新的子卷`libvirt/images`

```
btrfs subvolume list /data
```

显示输出

```
ID 257 gen 8 top level 5 path libvirt
ID 258 gen 8 top level 257 path libvirt/images
```

## 挂载子卷

> Btrfs支持直接挂载子卷而不需要挂载父卷

* 可以使用`subvolid`来挂载子卷

```
mount -o subvolid=258 /dev/sda4 /var/lib/libvirt/images
```

* 也支持`subvol`来挂载子卷

```
mount -o subvol=libvirt/images /dev/sda4 /var/lib/libvirt/images
```

挂载之后，可以检查文件系统挂载，会发现和ZFS类似的磁盘设备`/dev/sda4`挂载模式

```
/dev/sda4       1.1T   21G  1.1T   2% /data
/dev/sda4       1.1T   21G  1.1T   2% /var/lib/libvirt/images
```

* 设置默认挂载子卷

如果希望子卷 `258` subvolid 默认挂载到`/data`目录（而不是`/dev/sda4`），则使用如下命令

```
btrfs subvolume set-default 258 /data
```

这样就不需要每次都输入`-o subvolid=258`参数了

如果要恢复原先的top-level volume作为挂载，则设置`subvolid=0`

```
btrfs subvolume set-default 0 /data
```

## 删除子卷

子卷可以在**卷**路径挂载的时候被删除，例如

删除前检查`/data`卷的子卷

```
btrfs subvolume list /data
```

显示如下

```
ID 257 gen 8 top level 5 path libvirt
ID 258 gen 82 top level 257 path libvirt/images
```

现在删除子卷 `libvirt/images`

```
btrfs subvolume delete /var/lib/libvirt/images
```

提示报错

```
Delete subvolume (no-commit): '/var/lib/libvirt/images'
ERROR: cannot delete '/var/lib/libvirt/images' - Inappropriate ioctl for device
```

我发现不应该使用挂载的路径作为删除路径，而是应该使用 `/data/libvirt/images` 即卷路径来删除。

```
btrfs subvolume delete /data/libvirt/images
```

提示报错

```
Delete subvolume (no-commit): '/data/libvirt/images'
ERROR: cannot delete '/data/libvirt/images' - Device or resource busy
```

这次显示至少表明**卷**路径是正确的，只是设备繁忙（其实就是还没有umount卷挂载）。所以还是采用先`umount`掉卷挂载，然后删除子卷

```
umount /var/lib/libvirt/images
btrfs subvolume delete /data/libvirt/images
```

这次显示输出正确

```
Delete subvolume (no-commit): '/data/libvirt/images'
```

再检查`/data`卷可以看到只有子卷`libvirt`了

```
#btrfs subvolume list /data
ID 257 gen 85 top level 5 path libvirt
```

## 使用`/etc/fstab`挂载btrfs

> 请参考[在btrfs文件系统上部署kvm](https://github.com/huataihuang/cloud-atlas-draft/tree/6f3204fffc11cf006abd394631e2598d98b415c3/virtual/kvm/deployment_and_administration/deploy_kvm_using_btrfs/README.md)，下文我们挂载`/data/libvirt`子卷到`/var/lib/libvirt`目录，以这个`/data/libvirt`作为测试。

* 挂载`/data/libvirt`子卷（实际操作参考[在btrfs文件系统上部署kvm](https://github.com/huataihuang/cloud-atlas-draft/tree/6f3204fffc11cf006abd394631e2598d98b415c3/virtual/kvm/deployment_and_administration/deploy_kvm_using_btrfs/README.md)，需要做一些辅助步骤确保数据腾挪到`/data/libvirt`子卷）
* 配置`/etc/fstab`

```
/dev/sda4        /data        btrfs        defaults      0 1
/dev/sda4        /var/lib/libvirt    btrfs        subvol=/data/libvirt,defaults,noatime    0 1
```

* 挂载`/data`

```
mount /data
```

* 挂载`/var/lib/libvirt`

```
mount /var/lib/libvirt
```

出现报错

```
mount: mount(2) failed: No such file or directory
```

但是发现将 `/data/libvirt` 这个btrfs子卷修改成对应的`subvolid`却能够正常挂载

```
/dev/sda4        /data        btrfs        defaults,noatime      0 0
/dev/sda4        /var/lib/libvirt    btrfs        subvolid=257,defaults,noatime    0 0
```

挂载后

```
/dev/sda4       1.1T   21G  1.1T   2% /data
/dev/sda4       1.1T   21G  1.1T   2% /var/lib/libvirt
```

仔细检查了一下使用`mount`命令的参数`-o subvol`，原来这个子卷参数不能包含`top-level`的目录，应该使用

```
mount -o subvol=libvirt /dev/sda4 /var/lib/libvirt
```

而 **不是**

```
mount -o subvol=/data/libvirt /dev/sda4 /var/lib/libvirt
```

所以正确的`/etc/fstab`应该配制成

```
/dev/sda4        /var/lib/libvirt    btrfs        subvol=libvirt,defaults,noatime    0 0
```

## 快照

Btrfs最有用的功能是在线创建子卷快照，这个功能可以实现系统回滚以及创建一致性的备份。

> 对于大数据磁盘的备份，一个非常关键的要求就是数据一致性：在备份时间点需要获取整个磁盘的瞬间快照，确保完整的备份包含了这个时间点的所有数据。

* 在创建快照前，先准备2个测试文件

```
touch /var/lib/libvirt/test1 /var/lib/libvirt/test2
```

* 现在创建一个当前时间点的快照

```
btrfs subvolume snapshot
```

## 参考

* [A Beginner's Guide To btrfs](https://www.howtoforge.com/a-beginners-guide-to-btrfs)
* [Managing BTRFS in CentOS 7](https://jamesnbr.wordpress.com/2016/06/05/managing-btrfs-in-centos-7/)
* [Chapter 3. Btrfs (Technology Preview)](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Storage_Administration_Guide/ch-btrfs.html)
