简介
在 Linux 中你有 3 块硬盘(或分区)分别为 100G、200G 和 300 G,如何在一个统一的路径下访问 600G 的视频文件夹呢?并且任何一个磁盘数据的损坏是不能影响其他两个磁盘中的数据,也不影响其他磁盘的正常访问。
相信大多数用户会想到几种解决办法:
买个更大的硬盘 ̄□ ̄||
使用 LVM
组 RAID ......
今天我要介绍 mergerfs 工具,它可以将多个 Linux 分区(挂载点)或硬盘组合成一个虚拟驱动器。这样我们就可以将文件丢到 mergerfs 创建的虚拟驱动器中,它会自动将文件分布到不同的挂载点中,而用户看起来就像是放到了一个统一的路径。
该项目来自https://github.com/trapexit/mergerfs
README重要描述如下:
% mergerfs(1) mergerfs user manual
# NAME
mergerfs - a featureful union filesystem
# SYNOPSIS
mergerfs -o<options> <branches> <mountpoint>
# DESCRIPTION
**mergerfs** is a union filesystem geared towards simplifying storage
and management of files across numerous commodity storage devices. It
is similar to **mhddfs**, **unionfs**, and **aufs**.
mergerfs 是一个面向简化跨多个通用存储设备的文件存储和管理的联合文件系统。它类似于 mhddfs、unionfs 和 aufs
# FEATURES
* Configurable behaviors / file placement
* Ability to add or remove filesystems at will
* Resistance to individual filesystem failure
* Support for extended attributes (xattrs)
* Support for file attributes (chattr)
* Runtime configurable (via xattrs)
* Works with heterogeneous filesystem types
* Moving of file when filesystem runs out of space while writing
* Ignore read-only filesystems when creating files
* Turn read-only files into symlinks to underlying file
* Hard link copy-on-write / CoW
* Support for POSIX ACLs
* Misc other things
可配置的行为/文件放置
任意添加或移除文件系统的能力
抵抗单个文件系统故障
支持扩展属性(xattrs)
支持文件属性(chattr)
运行时可配置(通过 xattrs)
与异构文件系统类型兼容
写入时在文件系统空间不足时移动文件
创建文件时忽略只读文件系统
将只读文件转换为底层文件的符号链接
硬链接写时复制/CoW
支持 POSIX ACLs
其他杂项功能
# HOW IT WORKS
mergerfs logically merges multiple paths together. Think a union of
sets. The file/s or directory/s acted on or presented through mergerfs
are based on the policy chosen for that particular action. Read more
about policies below.
mergerfs 在逻辑上将多个路径合并在一起。可以将其视为集合的并集。通过 mergerfs 操作或呈现的文件或目录取决于为特定操作选择的策略
```
A + B = C
/disk1 /disk2 /merged
| | |
+-- /dir1 +-- /dir1 +-- /dir1
| | | | | |
| +-- file1 | +-- file2 | +-- file1
| | +-- file3 | +-- file2
+-- /dir2 | | +-- file3
| | +-- /dir3 |
| +-- file4 | +-- /dir2
| +-- file5 | |
+-- file6 | +-- file4
|
+-- /dir3
| |
| +-- file5
|
+-- file6
```
mergerfs does **not** support the copy-on-write (CoW) or whiteout
behaviors found in **aufs** and **overlayfs**. You can **not** mount a
read-only filesystem and write to it. However, mergerfs will ignore
read-only filesystems when creating new files so you can mix
read-write and read-only filesystems. It also does **not** split data
across filesystems. It is not RAID0 / striping. It is simply a union of
other filesystems.
mergerfs 不支持在 aufs 和 overlayfs 中发现的写时复制(CoW)或白出行为。你不能将只读文件系统挂载并对其进行写操作。然而,当创建新文件时,mergerfs 将忽略只读文件系统,因此你可以混合使用读写和只读文件系统。它也不会在文件系统之间分割数据。它不是 RAID0 / 切割。它只是其他文件系统的联合。
# TERMINOLOGY
* branch: A base path used in the pool.
* pool: The mergerfs mount. The union of the branches.
* relative path: The path in the pool relative to the branch and mount.
* function: A filesystem call (open, unlink, create, getattr, rmdir, etc.)
* category: A collection of functions based on basic behavior (action, create, search).
* policy: The algorithm used to select a file when performing a function.
* path preservation: Aspect of some policies which includes checking the path for which a file would be created.
branch: 池中使用的基本路径。
pool: mergerfs 挂载点。分支的联合。
相对路径(relative path):相对于分支和挂载点在池中的路径。
function: 文件系统调用(打开、取消链接、创建、获取属性、删除目录等)。
category: 基于基本行为(动作、创建、搜索)的一组函数集合。
policy: 执行功能时用于选择文件的算法。
path preservation: 一些策略的方面,包括检查将要创建文件的路径。
安装配置mergerfs
初始化磁盘
我是用的系统是debian 12,除去系统盘外,有4块硬盘分别为100G,150G,170G,200G。下来我需要将这四块硬盘组成一个pool进行挂载,对应用程序提供统一对外存储接口。
目前这些磁盘还没有进行格式化,可以通过fdisk命令或者parted命令进行分区然后格式化为xfs格式。
我这里使用parted进行分区,由于默认没有安装parted,先安装parted工具。
我这里有多块硬盘,如果通过命令一个个初始化在比较花时间,如果硬盘数量更多, 则更为消耗时间,我这里使用脚本创建。大家可以根据自己的需求,修改脚本中的硬盘盘符。
#!/bin/bash
disks=(sdb sdc sdd sde) # 定义要处理的磁盘列表
for disk in "${disks[@]}"
do
parted -s /dev/$disk mklabel gpt # 创建 GPT 分区表
# 创建一个最大大小的主分区
parted -a opt /dev/$disk mkpart primary xfs 0% 100%
sleep 2 # 添加延迟等待系统识别新的分区
# 格式化分区为 xfs 文件系统(使用 -f 选项强制格式化)
mkfs.xfs -f /dev/${disk}1
done
输出结果如下
root@debian:~# ./partition_format.sh
Information: You may need to update /etc/fstab.
meta-data=/dev/sdb1 isize=512 agcount=4, agsize=6553472 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1 bigtime=1 inobtcount=1 nrext64=0
data = bsize=4096 blocks=26213888, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=16384, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
Information: You may need to update /etc/fstab.
meta-data=/dev/sdc1 isize=512 agcount=4, agsize=9830272 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1 bigtime=1 inobtcount=1 nrext64=0
data = bsize=4096 blocks=39321088, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=19199, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
Information: You may need to update /etc/fstab.
meta-data=/dev/sdd1 isize=512 agcount=4, agsize=13107072 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1 bigtime=1 inobtcount=1 nrext64=0
data = bsize=4096 blocks=52428288, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=25599, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
Information: You may need to update /etc/fstab.
meta-data=/dev/sde1 isize=512 agcount=4, agsize=11140992 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1 bigtime=1 inobtcount=1 nrext64=0
data = bsize=4096 blocks=44563968, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal log bsize=4096 blocks=21759, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
root@debian:~# lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
sda
├─sda1 ext4 1.0 48541a60-45c9-4b84-b4de-e96b6462b2f2 750.9M 11% /boot
├─sda2
├─sda5 LVM2_member LVM2 001 eHntBU-4Ajb-VKpF-J7eF-oX4c-m31R-X1f7lj
│ └─vg--swap-lv--swap swap 1 07c055d3-bd64-4af1-8ff2-f8bf04040b41 [SWAP]
└─sda6 LVM2_member LVM2 001 UEKBAJ-gR23-b1Qy-X6d4-yXDs-4Taz-9TH2Ol
└─vg-lv xfs 631c94d0-9a71-470f-b144-857d07a3e801 47.7G 4% /
sdb
└─sdb1 xfs 60efb5e2-3030-4b33-8721-d395611729c9
sdc
└─sdc1 xfs 617cff25-1dd5-465f-b950-eb6bc0a2dfe2
sdd
└─sdd1 xfs 6a92643f-be6e-4804-9986-5a0625d5ec57
sde
└─sde1 xfs d7fb2bfc-6a1c-47db-9e83-e4358ba2ed11
sr0 iso9660 Joliet Extension Debian 12.1.0 amd64 1 2023-07-22-12-15-07-00
root@debian:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 60G 0 disk
├─sda1 8:1 0 953M 0 part /boot
├─sda2 8:2 0 1K 0 part
├─sda5 8:5 0 9.3G 0 part
│ └─vg--swap-lv--swap 254:0 0 9.3G 0 lvm [SWAP]
└─sda6 8:6 0 49.8G 0 part
└─vg-lv 254:1 0 49.8G 0 lvm /
sdb 8:16 0 100G 0 disk
└─sdb1 8:17 0 100G 0 part
sdc 8:32 0 150G 0 disk
└─sdc1 8:33 0 150G 0 part
sdd 8:48 0 200G 0 disk
└─sdd1 8:49 0 200G 0 part
sde 8:64 0 170G 0 disk
└─sde1 8:65 0 170G 0 part
sr0 11:0 1 3.7G 0 rom
此时已经全部创建完成并且格式化为xfs格式。
安装mergerfs
下载mergerfshttps://github.com/trapexit/mergerfs/releases,根据自己的系统版本下载对应的软件。
配置/etc/fstab进行挂载
在mergerfs进行挂载的时候需要先将我们前面初始化的磁盘挂载到本地不同的目录上,然后再将不同的目录进行聚合。
修改/etc/fstab文件,通过blkid输出的uuid进行挂载,分别挂载的mnt中的不同目录,使用的是xfs格式,defaults 是一种用于指定文件系统挂载选项的常见参数,通常包含了一组默认的挂载选项。当在 /etc/fstab 文件中使用 defaults 作为挂载选项时,系统会根据默认设置自动应用多个常见选项。这些选项通常包括:
rw:允许读写权限。
suid:允许 set-user-ID 权限。
dev:允许创建设备文件。
exec:允许执行二进制文件。
auto:在启动时自动挂载。
nouser:普通用户无法卸载此文件系统。
async:采用异步 I/O 操作。
当使用 defaults 作为挂载选项时,实际上是告诉系统应该使用这些默认选项来挂载文件系统。如果你想明确指定每个选项,可以将它们逐个列出,而不是使用 defaults。
第五个字段(备份选项):
0:表示不执行备份操作。
1:表示应该备份此文件系统。
2:通常与磁带设备一起使用,表示备份并排除数据。
第六个字段(fsck 检查顺序):
0:表示不进行文件系统检查。
1:表示在启动时首先对根文件系统进行检查。
2:表示在启动时对其他文件系统进行检查,但在根文件系统之后进行。
配置mergerfs挂载
root@debian:~# mkdir /pool #创建目录pool用于存储统一空间入口
root@debian:~# ls /
bin boot dev etc home initrd.img initrd.img.old lib lib32 lib64 libx32 media mnt opt pool proc root run sbin srv sys tmp usr var vmlinuz vmlinuz.old
root@debian:~#
root@debian:~# cat /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# systemd generates mount units based on this file, see systemd.mount(5).
# Please run 'systemctl daemon-reload' after making changes here.
#
# <file system> <mount point> <type> <options> <dump> <pass>
/dev/mapper/vg-lv / xfs defaults 0 0
# /boot was on /dev/sda1 during installation
UUID=48541a60-45c9-4b84-b4de-e96b6462b2f2 /boot ext4 defaults 0 2
/dev/mapper/vg--swap-lv--swap none swap sw 0 0
/dev/sr0 /media/cdrom0 udf,iso9660 user,noauto 0 0
UUID=60efb5e2-3030-4b33-8721-d395611729c9 /mnt/sdb1 xfs defaults 0 0
UUID=617cff25-1dd5-465f-b950-eb6bc0a2dfe2 /mnt/sdc1 xfs defaults 0 0
UUID=6a92643f-be6e-4804-9986-5a0625d5ec57 /mnt/sdd1 xfs defaults 0 0
UUID=d7fb2bfc-6a1c-47db-9e83-e4358ba2ed11 /mnt/sde1 xfs defaults 0 0
/mnt/sdb1:/mnt/sdc1:/mnt/sdd1:/mnt/sde1 /pool fuse.mergerfs cache.files=partial,dropcacheonclose=true,category.create=mfs 0 0
root@debian:~#
以上fstab参数为推荐参数,下面为解释,可以根据自己的具体需求进行对应的修改。
fuse.mergerfs:这说明了使用 FUSE 文件系统来合并这些挂载点
cache.files 参数用于控制文件缓存的行为,支持以下不同的选项:
libfuse:直接使用 direct_io、kernel_cache 和 auto_cache 值。
off:禁用页面缓存。
partial:在打开文件时清除页面缓存。
full:在打开文件时保留缓存。
auto-full:如果修改时间和大小未更改,则保留缓存。
per-process:仅为与 cache.files.process-names 中的值匹配的进程启用缓存。
默认值为 libfuse。
dropcacheonclose=true:当文件关闭时丢弃页面缓存。
category.create=mfs:创建特定的元数据缓存策略
category.create=POLICY: Sets policy of all FUSE functions in the create category. (default: epmfs)
What policies should I use?
Unless you’re doing something more niche the average user is probably best off using mfs for category.create. It will spread files out across your branches based on available space. Use
mspmfs if you want to try to colocate the data a bit more. You may want to use lus if you prefer a slightly different distribution of data if you have a mix of smaller and larger filesystems.
Generally though mfs, lus, or even rand are good for the general use case. If you are starting with an imbalanced pool you can use the tool mergerfs.balance to redistribute files across the
pool.
If you really wish to try to colocate files based on directory you can set func.create to epmfs or similar and func.mkdir to rand or eprand depending on if you just want to colocate generally
or on specific branches. Either way the need to colocate is rare. For instance: if you wish to remove the device regularly and want the data to predictably be on that device or if you don’t
use backup at all and don’t wish to replace that data piecemeal. In which case using path preservation can help but will require some manual attention. Colocating after the fact can be ac‐
complished using the mergerfs.consolidate tool. If you don’t need strict colocation which the ep policies provide then you can use the msp based policies which will walk back the path till
finding a branch that works.
Ultimately there is no correct answer. It is a preference or based on some particular need. mergerfs is very easy to test and experiment with. I suggest creating a test setup and experi‐
menting to get a sense of what you want.
epmfs is the default category.create policy because ep policies are not going to change the general layout of the branches. It won’t place files/dirs on branches that don’t already have the
relative branch. So it keeps the system in a known state. It’s much easier to stop using epmfs or redistribute files around the filesystem than it is to consolidate them back.
数据写入测试
直接给/pool中写入多个文件进行测试
直接在其他磁盘写入文件
断掉磁盘sdd1,查看数据和pool的情况
继续向pool中写入数据
总结
mergerfs整个的工作过程是将多个磁盘进行统一挂在在同一个目录上,数据写入会逐个磁盘写入。没有任何的冗余机制,任何一块磁盘的数据丢失也不会影响其他磁盘的数据和整个pool的工作。该方式适合多种不同规格的磁盘组成统一的存储空间。
注意:使用mergerfs需要自己对数据进行备份,可以备份至其他磁盘或者外置空间云盘等位置。备份方式可以参考我的这篇文章高效的nas备份之路