☰
Linux软RAID+LVM+磁盘配额:文件服务器存储扩容与故障恢复实战
2026/10/26 3:59:17 网站建设 项目流程

手上有四块 4T 的硬盘,要给内部文件服务搭一套存东西的地方,需求提得很朴素:空间不够了能在线加,单块盘坏了数据不能丢,每个人最多只能用 20G。听上去是三件事,落到实现上其实是三套机制叠在一起——底下用 mdadm 做软 RAID 阵列兜住硬件故障,中间用 LVM 逻辑卷把容量池化方便随时扩容,上面再用磁盘配额把每个用户的用量卡住。这三层单独拿出来任何一个都不复杂,难的是把它们串成一条链路之后,各种细节会互相咬合:阵列的元数据、卷组的 PE 大小、文件系统的类型、挂载选项的写法、fstab 里那一行参数的顺序,任何一处写错,轻则配额不生效,重则重启进不了系统。

这篇内容就是把这套组合拳从头到尾打一遍。适合已经会基本的 Linux 磁盘操作、但没真正在生产环境里把 RAID、LVM、配额叠起来用过的朋友;也适合那些照着教程敲完命令、结果 quotaon 报错、mdadm 重启后阵列变成 md127、lvextend 之后 df 看不到空间的同学。文章里的每一步都会说清楚为什么这么做,参数为什么是这个值,以及我实际踩过的那些坑长什么样。

1. 三件事三个层次:先把 RAID、LVM、配额的职责边界划清楚

1.1 一个朴素需求拆出来的三层结构

很多人第一次接触这套东西会 confused:既然 LVM 能做镜像(lvcreate --type raid1),为什么还要 mdadm?既然 RAID 卡能做热备,为什么还要 LVM?答案在于这三层解决的问题根本不在一个维度上。

RAID 阵列解决的是物理层面的可靠性和吞吐。它关心的是盘、是通道、是条带怎么切、校验块放在哪。它不知道什么是"用户",也不知道什么叫"文件"。对上层来说,一块 RAID5 阵列就是一个块设备,和一块单盘在接口上没区别。

LVM 解决的是容量层面的灵活性。它把物理块设备抽象成物理卷(PV),再池化成卷组(VG),最后切出逻辑卷(LV)。它关心的是"我还剩多少空闲 PE"、"能不能把这个 LV 在线挪到另一块盘上"。它对数据的完整性一无所知,RAID 该做的冗余它一概不管。

磁盘配额解决的是使用层面的约束。它挂在文件系统上,盯着的是某个 UID 或 GID 写了多少块、建了多少个 inode。它完全不关心底下是单盘、RAID 还是 LVM。

三层各管一段,互不越界。这个边界划清楚之后,后面所有的配置顺序就都顺理成章了:先有块设备(RAID),再有容量池(LVM),最后才是文件系统上的约束(配额)。

1.2 每层能解决什么、不能解决什么

我习惯用一张表把边界写清楚,团队里新来的人看一眼就明白了:

层次解决什么解决不了什么
RAID(mdadm)单盘故障不丢数据、读性能聚合容量不能跨阵列灵活切分、不能在线缩小
LVM在线扩容、跨盘池化、快照、迁移不做任何冗余,一块 PV 坏了整个 LV 可能报废
配额限制单用户/组的空间与 inode 用量不能防误删、不能做备份、对 root 基本无效

这张表里最容易让人误解的是最后一行。配额对 root 是无效的,因为 root 拥有 CAP_SYS_RESOURCE 能力,可以无视配额限制继续写。所以如果你的服务是以 root 身份跑的,配额形同虚设。这一点后面第 4 章还会展开。

另一个常见误解是把快照当备份。LVM 快照本质是写时复制,源卷被改得越多,快照卷消耗越快;快照卷写满的那一刻,快照直接失效。它适合"我要升个级,出问题 10 分钟内回滚",不适合"我要保留三个月前的数据"。

1.3 我为什么坚持这个叠放顺序

有人会问:能不能反过来,LVM 底下垫 RAID,还是 RAID 上面盖 LVM?这两种说法其实是一回事,只是在描述方向。物理上一定是 RAID 在下、LVM 在上,因为 LVM 需要看到一个稳定的块设备,而 mdadm 创建出来的 /dev/md0 正好就是。

反过来做——先 LVM 后用 mdadm 组阵列——技术上不是不行,但没有任何意义。mdadm 组阵列需要的是裸块设备,你在 LV 上再组 RAID,等于在已经抽象过的层上再做一次物理冗余,除了增加一层故障点之外什么也得不到。

真正需要斟酌的是另一个问题:要不要在 LVM 之上再开条带(lvcreate -i)?如果底下的 RAID5 已经是条带了,上层再条带一次,等于把数据切两遍,小文件场景下反而可能因为跨盘寻道降低性能。我的做法是:底层 RAID 已经做了条带,上层 LV 就老老实实整块分配,别再叠。

还有一点值得说:并不是所有场景都需要三层齐全。如果只有两块盘、预算有限、数据重要性一般,直接 RAID1 加文件系统就够了,LVM 和配额都是额外的心智负担。三层结构真正的价值在于"我需要在线扩容,而且扩容的时候不能停机"——这个需求一旦成立,LVM 就不可省。

2. mdadm 建阵列:动手之前先算清容量与风险账

2.1 RAID 级别的选择不是看性能参数表

网上关于 RAID 级别的对比表满天飞,但真正做决策的时候,起作用的往往不是那些 IOPS 数字,而是三个很实际的问题:坏了盘之后多久能恢复、恢复期间再坏一块会怎样、我能不能接受这个风险窗口。

RAID0 不用讨论,一块盘坏全盘报废,只适合临时缓存。

RAID1 两块盘互为镜像,可用容量只有一半,但重建快、风险低。两块 4T 盘做 RAID1,可用 4T,重建时间大概几个小时。适合系统盘、小规模关键数据。

RAID5 三块盘起步,可用容量是 (N-1) 块。四块 4T 做 RAID5,可用 12T。代价是写惩罚:一次小随机写,要读旧数据、读旧校验、写新数据、写新校验,四次 IO 才完成一次逻辑写。更麻烦的是重建窗口——4T 盘在 100MB/s 左右的重建速率下,理论时间约 11 小时,实际因为业务 IO 抢占会被拉得更长。而 RAID5 在降级状态下是没有冗余的,这十几个小时里再坏一块,数据就没了。

RAID6 多一块校验盘,可用 (N-2) 块,能扛住同时坏两块。四块盘做 RAID6 只剩 8T,容量损失大,但重建期间的安全性高得多。盘越大、重建越慢,RAID6 的价值就越大。

RAID10 是镜像加条带,可用 N/2,重建时只影响一对镜像,速度极快。四块 4T 做 RAID10 只有 8T,但如果你的场景是数据库、虚拟机镜像这种随机写密集的负载,RAID10 的实际表现往往比 RAID5/6 好得多。

我这次的场景是文件归档服务,写入以顺序大文件为主,读多写少,四块盘。最终选了 RAID5 加一块热备盘——也就是五块盘,四块组阵列一块做 spare。这个组合的意思是:任何一块盘坏了,mdadm 自动拿 spare 顶上开始重建,不需要人半夜爬起来换盘。代价是容量,但如果连一块备盘的预算都没有,那这套架构本身就不该上。

2.2 分区、对齐与 chunk size 的三个细节

第一个细节:盘要不要分区?直接mdadm --create /dev/md0 ... /dev/sdb /dev/sdc用整盘也能跑,但我更推荐先分区再用分区组阵列。原因很实际——将来盘坏了要替换,如果原来用的是整盘,新盘插上去直接组回去就行;但如果这块盘上将来可能还要装系统或者做别的用途,有分区表会灵活得多。更重要的是,分区的时候可以显式指定起始扇区,保证 4K 对齐。

用 fdisk 建分区时,默认起始扇区是 2048,也就是 1MiB 偏移,这个值对 4K 扇区的盘来说是正确对齐的。分区类型要设成fd(Linux raid autodetect),这样开机时内核知道这块分区是给阵列用的。命令大致是这样:

# 对 sdb/sdc/sdd/sde 分别执行 fdisk /dev/sdb # n 新建 -> p 主分区 -> 1 -> 回车默认起始 -> 回车默认结束 # t 改类型 -> fd # w 保存

第二个细节:chunk size。--chunk=128表示每个条带块 128KB。这个值决定了数据在盘之间怎么切。小 chunk 适合小文件、随机 IO 多的场景,因为单个 IO 更容易落在一块盘上;大 chunk 适合大文件顺序读,因为一次读能把整条带填满。默认值通常是 512K,对归档服务来说偏大,我一般设 128K 到 256K 之间。这个参数建好之后改不了,想改只能重建阵列,所以建之前想清楚。

第三个细节:bitmap。--bitmap=internal会在阵列里写一份位图,记录哪些区域是脏的。作用是在意外断电后重建时,只需要同步脏块,而不是全盘重扫。对于超过 1T 的阵列,我强烈建议开这个,代价是牺牲一点点写性能。如果没有 bitmap,一次非正常关机之后的重建可能要好几个小时。

2.3 创建、观察同步、写配置一条龙

把上面这些想清楚之后,创建命令就是一条:

mdadm --create /dev/md0 \ --level=5 \ --raid-devices=4 \ --spare-devices=1 \ --chunk=128 \ --bitmap=internal \ /dev/sd[b-e]1

注意这里--raid-devices=4和后面列出的 5 个设备之间的关系:前 4 个是阵列成员,第 5 个自动成为热备。设备列表的顺序会影响数据布局,但 RAID5 有校验,顺序本身不影响容量。

创建完成的瞬间阵列就开始做初始同步了。cat /proc/mdstat能看到进度:

md0 : active raid5 sde1[4](S) sdd1[3] sdc1[2] sdb1[1] 11718638592 blocks super 1.2 level 5, 128k chunk, algorithm 2 [4/4] [UUUU] [=>...................] resync = 5.2% (152627200/2930270464) finish=280.1min speed=165432K/sec

这个同步过程对四块 4T 盘来说大概要几个小时。这段时间阵列是可用的,但性能会受影响。如果时间紧,可以调大同步速度上限:

echo 300000 > /proc/sys/dev/raid/speed_limit_max

同步完成、状态变成[UUUU]之后,把阵列配置写进配置文件,否则重启之后 mdadm 可能认不出来:

# Debian/Ubuntu 系 mdadm --detail --scan >> /etc/mdadm/mdadm.conf # RHEL/CentOS 系 mdadm --detail --scan >> /etc/mdadm.conf

然后是关键的一步,很多人漏掉:更新 initramfs。阵列信息在开机早期就要被识别出来,如果 initramfs 里没有新的配置,系统启动时找不到 /dev/md0,后面的 LVM 全都会报错。

# Debian/Ubuntu update-initramfs -u -k all # RHEL/CentOS dracut -f

2.4 故障演练:拔盘、替换、重建

阵列建好不演练等于没建。我习惯在正式放数据之前做一次完整故障演练,这样真出事的时候手不慌。

演练分四步。第一步,人为标记一块盘故障:

mdadm --manage /dev/md0 --fail /dev/sdc1 mdadm --manage /dev/md0 --remove /dev/sdc1

执行完立刻看 /proc/mdstat,应该显示[4/3] [UU_U],并且自动重建开始,因为备盘顶上去了。第二步,观察重建进度,确认速度符合预期。第三步,模拟换盘:把故障盘拔掉,插入新盘,分区成 fd 类型,然后加回阵列:

mdadm --manage /dev/md0 --add /dev/sdc1

这时候 sdc1 会变成新的热备盘,等下一次有盘坏了再顶上。第四步,确认mdadm --detail /dev/md0里的状态恢复成[UUUU],并且 spare 数量对得上。

我踩过的坑是:新加的盘忘了建分区表,直接--add /dev/sdc,结果阵列里一半成员是分区一半是整盘,虽然能跑,但后面要替换的时候特别别扭。所以替换盘的处理流程一定要和初始建阵列保持一致。

还有一个值得提前知道的现象:重建期间,如果业务压力大,重建速率会被压得很低,一块 4T 盘重建一整天都不奇怪。如果这个服务对可用性要求高,重建期间最好主动限流,把speed_limit_max调小一点,宁可慢也别把业务拖死。

3. 在阵列之上铺 LVM:划分策略比命令本身更重要

3.1 PV/VG/LV 三层结构到底在做什么

LVM 的三层概念被讲烂了,但很多人记住了名词没记住逻辑。物理卷 PV 是"被 LVM 接管的块设备",它的本质是在设备头部写一段元数据,标记这块设备归 LVM 管。卷组 VG 是"PV 的集合,切成一堆固定大小的物理扩展块 PE"。逻辑卷 LV 是"从 VG 的 PE 池里划出来的连续或非连续的一段"。

关键点在于:LV 的线性映射意味着它底下的 PE 可以来自不同的 PV。这就是 LVM 能在线扩容的根本原因——只要 VG 里还有空闲 PE,或者能往 VG 里加新 PV,LV 就能变大,而且完全不需要动已有数据。

对照我们的场景:底下是 /dev/md0 一块 12T 的 RAID5 阵列。先把它变成 PV:

pvcreate /dev/md0

然后建卷组。这里有个参数值得说一下:-s指定 PE 大小,默认 4M。

vgcreate -s 8M vg_data /dev/md0

为什么我改成 8M?因为 4M 的 PE 在 12T 的卷组里会产生三百多万个 PE,vgdisplay -v输出会长到刷屏,而且每个 PE 的元数据都要占地方。8M 的 PE 让管理更清爽。代价是分配粒度的最小单位变成 8M,对动辄几十 G 的 LV 来说完全可以忽略。反过来,如果你的场景需要频繁创建几十兆的小 LV(比如容器卷),那 4M 甚至 1M 更合适。

这里有个反直觉的点:PE 大小一旦建好,就没法改了,只能重建 VG。所以-s这个参数在建 VG 的时候要想清楚,它取决于你未来最小的分配单元有多大。

3.2 卷组怎么切,PE 大小要不要改

切 LV 的策略比命令重要得多。同一个 12T 的 VG,可以切成一个 12T 的大 LV,也可以切成 home、data、backup 三个 4T 的 LV。选择的依据是什么?

我的原则是按"生命周期和配额策略"切分。如果两类数据需要不同的备份频率、不同的配额规则、或者一方扩容时另一方不该跟着变,那它们就该是两个 LV。反过来,如果所有数据都是同一个服务的,共享同一套配额和备份策略,切成一个 LV 反而更简单,因为你可以用 xfs/ext4 的目录配额(project quota)在文件系统内部做细分,不必在 LVM 层切。

这次的场景是文件服务,只有一个用途,所以我切了两个 LV:一个给用户数据,一个给系统日志和临时目录。命令:

lvcreate -L 200G -n lv_log vg_data lvcreate -l 100%FREE -n lv_home vg_data

注意-l 100%FREE和-L的区别:小写-l按 PE 数量或百分比分配,大写-L按字节大小分配。用100%FREE把所有剩余空间吃干净,这是常见的做法,但如果你预料到后面还要用快照(快照需要 VG 里有空闲空间),至少要留 10% 到 20%。我这里之所以敢吃满,是因为规划里还有第五块盘可以随时加进 VG。

格式化和挂载:

mkfs.ext4 -L HOME /dev/vg_data/lv_home mkdir -p /home mount /dev/vg_data/lv_home /home

LV 的设备路径有两个等价写法:/dev/vg_data/lv_home和/dev/mapper/vg_data-lv_home。前者是 udev 创建的符号链接,后者是内核设备映射器的真实名称。两种都能用,fstab 里我通常用 UUID,避免卷组重命名之后挂载失败。

3.3 扩容的正确姿势与缩容的雷区

扩容是 LVM 最常用的操作,流程分两步:先扩 LV,再扩文件系统。很多人只做了第一步就去看 df,发现没变,以为出问题了。

# 第一步:扩 LV lvextend -L +500G /dev/vg_data/lv_home # 第二步:扩文件系统(ext4) resize2fs /dev/vg_data/lv_home

更省事的写法是用-r参数让 lvextend 自动调用对应的 resize 工具:

lvextend -r -L +500G /dev/vg_data/lv_home

-r在 ext4 上等价于 resize2fs,在 xfs 上等价于 xfs_growfs。我建议所有扩容都用-r,少一步忘了的风险。

如果 VG 里的空闲 PE 不够了,得先加 PV:

# 新盘建好分区和阵列之后 pvcreate /dev/md1 vgextend vg_data /dev/md1 lvextend -r -l +100%FREE /dev/vg_data/lv_home

这里有个容易忽略的细节:vgextend之后 VG 里就有了新 PE,但 LV 不会自动用到它们,必须显式 lvextend。而且如果新 PV 的大小和 PE 大小不是整数倍,会有一点点空间浪费,这一点vgextend完成时会打印出来。

缩容是雷区。ext4 支持缩容,但顺序绝对不能错:必须先缩文件系统,再缩 LV。反过来做,LV 变小了而文件系统还以为自己有那么多块,直接就是数据损坏。

umount /home e2fsck -f /dev/vg_data/lv_home resize2fs /dev/vg_data/lv_home 1500G lvreduce -L 1500G /dev/vg_data/lv_home

xfs 更干脆:直接不支持缩容。如果你的文件系统是 xfs,想缩小唯一的路是备份、重建、恢复。这也是选文件系统时要想清楚的一点——我的 /home 选 ext4 就是为了保留缩容的可能性,尽管实际上很少用。

3.4 快照:能救急但不是备份

LVM 快照的原理是写时复制。创建快照的瞬间,快照卷和源卷共用同一份数据;之后源卷上任何被修改的块,会先把旧内容复制到快照卷,再写入新内容。读快照卷时,没改过的块直接读源卷,改过的块读快照卷。

创建命令:

lvcreate -s -L 20G -n lv_home_snap /dev/vg_data/lv_home

这个 20G 就是 COW 空间。它的大小取决于两件事:源卷有多大、快照存活期间会改多少数据。如果快照存在一天,期间有 25G 的数据被改,快照就写满了,写满的那一刻快照变成不可用状态,直接失效。

所以判断标准是:估计一下快照存活期间的最大写入量,留一倍余量。做升级回滚的话,存活时间只有几十分钟,20G 到 50G 通常够;如果是给一个每天写几百 G 的库做快照,那快照卷得开到接近源卷的大小。

挂载快照验证内容:

mkdir -p /mnt/snap mount -o ro,nouuid /dev/vg_data/lv_home_snap /mnt/snap

nouuid这个选项是必要的,因为快照和源卷的 UUID 相同,不加这个参数 xfs 会拒绝挂载。ext4 也有类似问题,通常用-o ro就够,但保险起见可以一起加。

用完记得及时删:

umount /mnt/snap lvremove /dev/vg_data/lv_home_snap

快照不删会一直占用 VG 空间,而且随着源卷写入,它占用会越来越大,最后可能把整个 VG 撑满,导致其他 LV 无法扩容。这是我在实际环境里见过的最常见的 LVM 事故之一。

4. 磁盘配额:ext4 和 xfs 是两套完全不同的玩法

4.1 配额生效的三个前提条件

配额不是一个开关,它需要三个条件同时满足,缺一个就白配。

第一个条件:内核支持。现代发行版的内核默认都带 quota 支持,grep QUOTA /boot/config-*能看到CONFIG_QUOTA=y。这个一般不是问题。

第二个条件:文件系统挂载时启用了配额选项。ext4 需要usrquota和grpquota(或者 journaled 版本usrjquota=aquota.user,jqfmt=vfsv1),xfs 需要uquota和gquota。挂载选项写在 /etc/fstab 里,改完要重新挂载或者重启才生效。

第三个条件:配额数据库被初始化。ext4 需要quotacheck扫描一遍生成 aquota.user/aquota.group,然后quotaon打开;xfs 在挂载时启用了 quota 选项就会自动完成,没有单独的 quotacheck 步骤。

这三个条件里,第三个最容易出错。很多人 fstab 写对了,mount | grep home也能看到 usrquota,但quotaon就是报Cannot find quota file on /home,就是因为没跑 quotacheck。

还有一个隐藏条件值得提:配额对 root 无效。就算你给 root 设了硬限制,root 依然能写超。这是设计如此,因为 root 有 CAP_SYS_RESOURCE。所以如果你的服务以 root 跑,配额这套东西就没意义,得先把服务降权到普通用户。

4.2 ext4 路线:quotacheck 与 edquota 的完整流程

ext4 的配额流程分五步,我按顺序写一遍。

第一步,改 fstab 加上挂载选项:

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /home ext4 defaults,usrquota,grpquota 0 2

改完先别急着重启,用mount -o remount /home让它立刻生效,然后mount | grep /home确认选项在。

第二步,生成配额数据库:

quotacheck -cugm /home

参数含义:-c创建,-u用户,-g组,-m强制以读写方式重挂载(因为扫描需要写 aquota 文件)。执行完 /home 下会出现 aquota.user 和 aquota.group 两个文件。

这里有个细节:如果 /home 很大,quotacheck 会扫很久,而且会消耗不少 IO。生产环境建议在业务低峰做,或者第一次挂载就做,别等数据堆到几个 T 才想起来。

第三步,打开配额:

quotaon -vug /home

-v显示详细信息,-u -g分别启用用户和组配额。输出应该是group quota on /home (/dev/mapper/vg_data-lv_home) is on这样的两行。

第四步,给用户设限制:

edquota -u user01

这会在编辑器里打开一个类似这样的界面:

Disk quotas for user user01 (uid 1001): Filesystem blocks soft hard inodes soft hard /dev/mapper/vg_data-lv_home 10240 20480 25600 320 0 0

blocks 单位是 1KB。soft=20480 表示 20MB 软限制,hard=25600 表示 25MB 硬限制。inode 那两列限制的是文件数量,0 表示不限。

如果不想进编辑器,用 setquota 一条命令搞定:

setquota -u user01 20480000 25600000 0 0 /home

单位是 KB,20480000 KB 就是 20G,25600000 KB 就是 25G。

第五步,设置宽限期:

edquota -t

这个控制的是软限制的宽限期。默认 7 天。意思是用户超过软限制之后,有 7 天时间自行清理;7 天后如果还是超的,就按硬限制处理,写不进去了。这个参数可以按用户设,也可以全局设。

查询结果:

repquota -as /home

-a是所有启用了配额的文件系统,-s是显示成友好单位(M、G),不然你会看到一长串数字。

4.3 xfs 路线:xfs_quota 没有 quotacheck

xfs 的配额是另一套逻辑,第一次用 ext4 思路去操作 xfs 一定会懵。

第一,没有 quotacheck。xfs 在挂载时如果检测到配额选项,会自己扫描并建立统计信息,不需要单独的初始化命令。这看起来省事了,但也意味着第一次挂载一个巨大的 xfs 文件系统时会有一个明显的延迟。

第二,挂载选项名字不一样:uquota,gquota(或者等价的usrquota,grpquota,xfs 都认)。如果要做目录级别的配额,还要加pquota。

第三,没有 quotaon/quotaoff 的常规用法。xfs 的配额是挂载即生效的,只能通过xfs_quota -x -c "off"之类的命令临时关闭,通常不需要。

所以 xfs 的流程只有三步。

第一步,fstab 加选项:

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /home xfs defaults,uquota,gquota 0 0

注意 xfs 的 dump/pass 通常写0 0,因为它不做类似 fsck 的开机检查。

第二步,remount 之后用 xfs_quota 设限制:

# 查看当前状态 xfs_quota -x -c 'state' /home # 查看报告 xfs_quota -x -c 'report -h' /home # 给 user01 设限制:软 20G,硬 25G,inode 软 10 万硬 12 万 xfs_quota -x -c 'limit -u bsoft=20g bhard=25g isoft=100000 ihard=120000 user01' /home

注意 xfs_quota 的大小可以带单位后缀(k/m/g),这一点比 ext4 的纯数字要友好。

第三步,设宽限期:

xfs_quota -x -c 'timer -u -b 7d' /home

-u用户,-b块配额,7d七天。

查报告:

xfs_quota -x -c 'report -h' /home

输出里会列出每个用户已用块数、软硬限制、宽限期剩余时间。-h是 human readable。

有个坑要提前说:xfs_quota 的命令必须在-x模式下才能执行管理类操作,不加-x只能做查询。我第一次用的时候不知道,敲了半天limit一直提示命令不存在,折腾了快半小时。

4.4 软硬限制、宽限期与 inode 配额的实际用法

soft 和 hard 的关系,打个比方:软限制是"提醒线",硬限制是"断电闸"。越过软限制,系统允许你继续写,但会开始计时;宽限期过了还在软限制之上,就按硬限制处理。硬限制是绝对的,一到就写不进去,返回Disk quota exceeded。

实际用法上,我一般把硬限制设成软限制的 1.2 到 1.25 倍。差距太小,用户稍微写多一点就撞墙,体验很差;差距太大,软限制就失去了提醒的意义。

inode 配额经常被忽略,但它比块配额更容易出问题。一个用户可能只用了 10G 空间,但建了 500 万个 2KB 的小文件,块配额远远没到,inode 先耗尽了。文件服务、邮件系统、缓存目录这类场景一定要设 inode 配额。

我给的常见配置是:块软限制 20G、硬 22G,inode 软限制 50 万、硬 60 万。50 万个文件平均 40KB 左右,跟块限制大致匹配。如果你的场景是大量小文件(比如缩略图、日志碎片),inode 限制要往上调。

查询某个用户的当前用量:

quota -u user01 # ext4/xfs 通用(xfs 需要 quota 包) repquota -as /home # ext4 xfs_quota -x -c 'quota -u user01' /home # xfs

还有一个实际经验:配额设好之后,用dd造数据验证。造 21G 数据应该能顺利写完(超过软限制但没到硬限制),造 23G 应该在中途报错。不验证一遍,你永远不知道限制有没有真的生效。

5. 全链路验证:从写满配额到拔盘重建的完整演练

5.1 配额压测:怎么造数据最快

配额的验证不能只看 repquota 的输出,必须实际写一遍。造数据最快的办法是 dd 加 fallocate。

# 切到目标用户,造 21G 实际数据(超过软限制但没到硬限制) dd if=/dev/zero of=/home/user01/testfile bs=1M count=21000 status=progress

注意用dd而不是fallocate。fallocate是预分配,在 xfs 上会直接记账但可能绕过实际写入的检测,ext4 上也有类似行为。要测真实配额,用 dd 写真实数据更可靠。

看到 21000MB 全部写完,说明软限制生效但没拦截,符合预期。然后写第二个文件:

dd if=/dev/zero of=/home/user01/testfile2 bs=1M count=5000 status=progress

这次应该在写到约 1.5G 的时候报No space left on device或者Disk quota exceeded。如果没报错,说明配额没生效,回头检查三件事:挂载选项有没有 usrquota、quotaon 有没有执行成功、这个文件属主是不是 user01。

压测完记得清理,不然配额一直占着:

rm -f /home/user01/testfile /home/user01/testfile2

清理后 repquota 的已用值不会立刻归零,因为配额统计是异步更新的,等几秒或者sync一下再看。

5.2 阵列降级与恢复的完整观察

配额的验证相对简单,阵列的验证才是重头戏,因为它涉及到数据完整性。

我用一个 5G 的测试文件做基准:

dd if=/dev/urandom of=/home/user01/raidtest bs=1M count=5000 md5sum /home/user01/raidtest

记下这个 md5,然后开始故障演练。

第一步,标记一块盘故障并移除,观察阵列状态:

mdadm --manage /dev/md0 --fail /dev/sdc1 mdadm --manage /dev/md0 --remove /dev/sdc1 watch -n 2 cat /proc/mdstat

应该能看到状态从[UUUU]变成[UUU_],然后备盘自动顶上,紧接着开始重建,状态变成[UUUU]但后面带recovery进度。

第二步,在重建过程中持续读取那个 5G 文件,验证降级状态下数据可读:

md5sum /home/user01/raidtest

结果应该和之前一致。如果不一致,说明 RAID5 的校验计算有问题,这种情况极少,但真出现就要立刻停止写入,检查是不是阵列元数据损坏。

第三步,等重建完成,再次 md5sum 确认一致。

第四步,把拔掉的盘插回去,分区、加回阵列做热备,再验证一次。这三轮下来,你对手上这套阵列的行为就有了底。

有一个现象要提前有心理准备:重建期间 IO 性能会明显下降,md5sum 从几十秒变成几分钟都是正常的。别以为是出问题了。

5.3 扩容一次走到黑:从加到 VG 到文件系统可见

扩容的验证要把整条链路走完,不能只看 lvextend 的输出了事。

假设新加了一块盘 /dev/sdf,建好分区 /dev/sdf1 之后:

# 第一步:组新阵列(如果是单盘可以跳过,直接用分区做 PV) mdadm --create /dev/md1 --level=1 --raid-devices=2 /dev/sdf1 /dev/sdg1 # 第二步:加入 LVM pvcreate /dev/md1 vgextend vg_data /dev/md1 # 第三步:扩 LV(配合 -r 自动扩文件系统) lvextend -r -l +100%FREE /dev/vg_data/lv_home # 第四步:验证 df -h /home

df 的输出应该显示 /home 的容量增加了。如果没变,检查三件事:lvextend 有没有真的成功、-r有没有报错(xfs 上如果源文件系统是只读挂载会失败)、vgs显示的空闲 PE 是否已经被吃掉。

还有一个细节:如果扩容的时候 /home 正在被写入,lvextend -r的 resize 步骤可能需要文件系统支持在线扩展。ext4 和 xfs 都支持在线扩展,这一点不用担心;但如果遇到 ext4 在线扩展失败,通常是文件系统有未清理的错误,得先 umount 再 e2fsck。

5.4 重启验证:这是最容易翻车的一步

前面所有的验证都是在运行时做的,真正检验配置正确性的是重启。

重启前先做三件事。第一,mount -a手动跑一遍,看看 fstab 有没有语法错误,能不能成功挂载。这一步能挡住 90% 的开机进不了系统的问题。

第二,cat /etc/mdadm/mdadm.conf(或 /etc/mdadm.conf)确认阵列配置写进去了,并且 UUID 和当前阵列对得上。第三,确认 initramfs 更新过了,lsinitramfs里能找到 mdadm.conf。

做完这三件事再reboot。

重启之后按顺序检查:

cat /proc/mdstat # 阵列是否自动组装 lsblk # 块设备树是否正确 vgs # 卷组是否识别 mount | grep home # 挂载选项是否包含配额 quotaon -p /home # 配额是否自动启用 repquota -as /home # 配额统计是否正常 df -h /home # 容量是否正确

这七个检查里,最容易出问题的是阵列自动组装。如果 /proc/mdstat 显示的是 /dev/md127 而不是 /dev/md0,说明 mdadm.conf 没生效或者 initramfs 没更新。这种情况 LVM 会找不到 /dev/md0,直接导致 /home 挂不上,系统可能卡在紧急模式。

遇到这种情况的救急办法是:在紧急模式里手动mdadm --assemble --scan,然后vgchange -ay,再mount -a,先让系统起来,再回头修配置。

还有一个容易被忽略的点:如果 /home 是独立挂载的,fstab 里建议加nofail选项(配合x-systemd.device-timeout=10),这样即使 /home 挂不上,系统也能正常起来进单用户,方便排查。但如果是根文件系统所在的挂载点,就绝对不能加 nofail。

6. 十个翻车现场:这套架构最容易踩的坑

6.1 挂载与 fstab 相关的四个坑

坑一:fstab 写完直接重启。这条听起来低级,但我见过太多次。正确做法是改完 fstab 先mount -a,没有任何输出才算通过。mount -a会尝试挂载所有 fstab 里没挂载的条目,语法错、UUID 写错、选项不支持,都会在这一步暴露出来。

坑二:用设备名而不是 UUID。/dev/sdb1这种写法在盘序变化之后会指向别的设备,轻则挂载失败,重则挂错盘。LVM 的 LV 设备名/dev/vg_data/lv_home相对稳定,但卷组重命名之后也会失效。统一用blkid拿到的 UUID 最稳妥。

坑三:ext4 的配额选项写成了 xfs 的。usrquota是 ext4 的写法,uquota是 xfs 的,虽然两个文件系统互相能认一部分别名,但写错的时候挂载会直接失败,报unknown mount option。反过来 xfs 写usrquota也能认,但最好按各自的标准写法来。

坑四:journaled quota 配错。ext4 的高级玩法是usrjquota=aquota.user,jqfmt=vfsv1,这种模式在崩溃恢复时一致性更好。但前提是 aquota.user 文件已经存在,而且文件系统不支持时挂载会失败。想用这个模式,必须先按普通 quota 流程把 aquota.user 建出来,再改成 journaled 选项重新挂载。顺序错了就是挂不上。

6.2 mdadm 与 LVM 相关的三个坑

坑一:忘记更新 initramfs。这是重启后阵列变成 md127 的头号原因。阵列信息在开机早期就要被识别,如果 initramfs 里的 mdadm.conf 是旧的,内核找不到对应 UUID 的阵列,就会给一个新编号。

坑二:整盘和分区混用。初始建阵列用分区,后来加盘用整盘,虽然 mdadm 不报错,但mdadm --detail里成员类型不一致,将来替换的时候容易搞混。统一用分区,分区类型统一设成fd。

坑三:LVM 快照没删导致 VG 满了。快照会持续消耗 VG 空间,一个 20G 的快照在源卷大量写入之后可能占满 20G,如果 VG 剩余空间不足,后面lvextend就会失败。养成习惯:快照用完立刻lvremove,并且定期vgs看一眼剩余空间。

还有一个更隐蔽的坑:lvreduce之前没有先resize2fs。这个顺序错误直接导致数据损坏,而且现场往往不会立刻报错,等下次访问到被截断的区域才暴露。缩容的操作我现在的习惯是:umount、e2fsck -f、resize2fs 缩文件系统、lvreduce 缩 LV、再 e2fsck 检查、最后重新挂载。六步一步都不能省。

6.3 配额相关的三个坑

坑一:quotaon报Cannot find quota file。原因就是没跑quotacheck。ext4 必须先quotacheck -cugm生成 aquota 文件,再 quotaon。xfs 没有这一步,别在 xfs 上跑 quotacheck,会报文件系统不支持。

坑二:配额对 root 无效。给 root 设了限制,结果dd照样写超,然后怀疑配额坏了。这是设计如此,root 有 CAP_SYS_RESOURCE。要么把服务降权到普通用户,要么用 cgroup 的 blkio 限制,配额这条路对 root 走不通。

坑三:只设了块配额没设 inode 配额。用户写了几百万个小文件,块配额才用了 2G,但 inode 已经把整个文件系统的 inode 耗尽,这时候连 root 都建不了新文件。inode 配额的意义就在这里,它防的不是空间,是文件数量。

这十个坑里,我自己踩过至少六个,其中"忘记更新 initramfs 导致重启进紧急模式"和"快照没删撑爆 VG"这两个,都发生在周六凌晨。后来我的做法是把这些检查项写成一张清单,任何存储变更之前先过一遍,别指望自己每次都记得住。

清单不长,就六行:fstab 改完先 mount -a、阵列配置变更后更新 initramfs、快照用完就删、缩容先缩文件系统、配额操作前确认文件系统类型、变更前先备份分区表和 mdadm --detail 的输出。这六条挡住的麻烦,比我后面写多少排错脚本都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询