把 Linux 存储这摊事儿捋明白,是我近几年带运维团队时最常做的事儿。新人入职,我大概率会先扔给他一块坏硬盘或者一个说大不大说小不小的分区扩容需求,让他自己去折腾。为什么?因为存储这块儿太容易出“看似没问题,实际埋大雷”的状况了。今天不聊虚的,就围绕 mdadm 做的软 RAID,以及配合 LVM 的逻辑卷管理,把从规划到落地、再到救急的完整链路掰开揉碎讲一遍。这篇文章适合刚接手服务器运维的人,也适合那些用过 LVM 扩容但一直没搞懂 RAID 底层原理的开发者和运维工程师。你会搞清楚 RAID 和 LVM 到底各管哪一段,为什么要叠加着用,以及真正遇到磁盘故障时怎么不慌不忙地把数据捞回来。
1. 方案选型:为什么是 mdadm + LVM,而不是二选一
很多人刚接触服务器存储时,会陷入一个非此即彼的误区:用了 RAID 就不需要 LVM,或者为了图省事直接用 LVM 而不做冗余。这两个工具压根不在一个层面上,解决的问题也不一样。
RAID 解决的是“盘坏了怎么办”和“单盘性能不够怎么办”的问题。通过把多块物理盘组合成一个逻辑设备,实现数据冗余或者并行读写。mdadm 是 Linux 下做软件 RAID 的标准工具,它不依赖昂贵的 RAID 卡,直接用 CPU 和内存来完成数据条带化和校验计算,非常适合中小型服务器和成本敏感的项目。
LVM 解决的是“分区不够用怎么办”和“分区大小不合理怎么办”的问题。传统分区方案里,你给 /home 分了 200G,给 /var 分了 50G,结果 /home 用了一半不到,/var 却爆了。这时候想调整,传统分区方案下非常痛苦,甚至要备份重来。LVM 把物理分区抽象成物理卷(PV),再汇总成卷组(VG),最后从卷组里切割出任意大小的逻辑卷(LV),这一切都可以在系统运行状态下动态调整。
这两者组合起来,才是一个完整的存储解决方案。底层用 RAID 把多块物理盘变成一块可靠的大盘,上层用 LVM 把这块大盘变成可以灵活切割和伸缩的空间池。我在实际项目里见过不少只用 LVM 不用 RAID 的案例,一旦某块物理盘挂了,整个卷组连带所有逻辑卷全部遭殃,那场面真的惨烈。反过来,只用 RAID 不做 LVM 的话,将来扩容和数据迁移的灵活性会大打折扣。所以我的建议一直很明确:物理层做 RAID,逻辑层做 LVM,各司其职。
2. mdadm 软 RAID 核心实操解析
2.1 先想清楚要哪种 RAID 级别
mdadm 支持的 RAID 级别不少,但日常用得最多的就四种:RAID 0、RAID 1、RAID 5、RAID 10。很多人背过它们的区别,但真的上手选型时还是会纠结。我直接给结论。
RAID 0 是把数据均匀条带化到所有盘上,读写性能最好,磁盘利用率 100%,但没有任何冗余,任何一块盘挂了,整个阵列数据全没。它只适合存缓存、临时数据、可重建的内容,比如视频渲染的中间帧、日志聚合的临时索引。
RAID 1 是镜像,数据同时写到两块盘上,冗余度最高,坏一块盘数据不丢,读性能有提升,但写性能等于单盘,磁盘利用率只有 50%。它适合放系统盘、数据库日志、配置类数据。
RAID 5 是条带加分布式校验,至少需要三块盘。校验信息分散存储在每块盘上,允许坏一块盘而不丢数据,磁盘利用率是 (N-1)/N。它兼顾了性能、容量、冗余,是很多通用业务服务器的首选。但要注意,RAID 5 在坏盘后的重建过程中,如果另一块盘也出问题,数据就悬了。所以有条件的场景,我更推荐 RAID 10。
RAID 10 是先镜像后条带,需要偶数块盘,至少四块。它结合了 RAID 1 的冗余和 RAID 0 的性能,允许每组镜像中坏一块盘,整体利用率 50%。数据库服务器、高负载业务系统,我基本无脑选 RAID 10。
我的选型逻辑很简单:系统盘用 RAID 1,业务数据盘用 RAID 10,如果预算实在紧张且数据可重建,才考虑 RAID 5。RAID 0 除非明确知道自己在干什么,否则别碰。
2.2 从格式化到组阵列的完整步骤
规划确定后,假设我们有四块空闲硬盘:/dev/sdb、/dev/sdc、/dev/sdd、/dev/sde,要做 RAID 10。我会先把每块盘的分区表清干净,然后创建 RAID 分区。
# 用 wipefs 擦除旧的文件系统签名,避免干扰 wipefs -a /dev/sdb /dev/sdc /dev/sdd /dev/sde # 也可以用 fdisk 交互式操作,但脚本化用 parted 更高效 parted /dev/sdb --script -- mklabel gpt parted /dev/sdb --script -- mkpart primary 0% 100% parted /dev/sdb --script -- set 1 raid on这里解释一下为什么要设置分区类型为 raid,而不是直接把整块裸盘丢给 mdadm。虽然 mdadm 可以基于整块盘工作,但用分区有两个好处:一是可以在分区级别预留一定的空间或者做对齐;二是在某些主板上,带分区表标识的盘更容易被管理工具识别。实测下来,直接用整块盘出问题的概率不大,但养成用分区的习惯,后续如果要做替换盘或者排查,心理踏实得多。
四块盘都准备好后,创建 RAID 10 阵列:
# 创建 /dev/md0,级别 raid10,4 块盘 mdadm --create /dev/md0 --level=raid10 --raid-devices=4 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 # 查看阵列创建进度 cat /proc/mdstat # 等待同步完成后,检查阵列详情 mdadm --detail /dev/md0创建过程会触发后台同步,对于大容量盘来说可能需要几十分钟到几个小时。这段时间系统可以正常用,但阵列的读写性能会有所下降。我遇到过有人以为卡死了,直接 reboot,结果阵列处于不一致状态,反而更麻烦。所以创建前一定要耐心,创建后要盯着 /proc/mdstat 看进度。
2.3 让阵列配置永久生效
阵列创建完成后,如果不做额外操作,重启后内核虽然能识别,但 /dev/md0 的编号和组装过程可能不稳定。正确做法是把配置写入 mdadm.conf。
# 生成配置 mdadm --detail --scan >> /etc/mdadm.conf # 更新 initramfs,确保引导阶段能识别阵列 update-initramfs -u # Debian/Ubuntu # 或 dracut --force # RHEL/CentOS这一步很多人会漏掉,然后在第一次重启后发现系统起不来,或者阵列变成了 inactive 状态,才回头补。initramfs 一定要更新,否则内核加载时没有 mdadm 的配置,根本不知道去组装 /dev/md0,数据都在盘上但系统认不出来。
3. 基于 RAID 阵列搭建 LVM 逻辑卷
3.1 为什么 LVM 要放在 RAID 之上
阵列建好后,/dev/md0 相当于一块大硬盘。传统做法是直接在这个设备上分区、格式化、挂载,但这样做之后,如果将来空间不够,要么重新组阵列,要么用非常繁琐的方式调整分区。把 LVM 加在这层之上,就是为了给这个“大硬盘”增加动态伸缩的能力。
我的习惯是,在 /dev/md0 上创建物理卷,加入卷组,然后按照业务需求切割逻辑卷。这样我面对的就不再是一块固定的磁盘,而是一个可以随时调整的空间池。
举个实际场景。某业务系统有三块数据盘组成的 RAID 5,总容量约 8T。传统分区方案里,我把 6T 分给了文件存储,2T 分给了数据库目录。结果半年后,文件存储那边已经用了 5.8T,数据库目录才用了 800G。这种时候,如果当初用的是 LVM,直接从数据库目录的卷里缩出来 1T 给文件存储,几分钟搞定,在线操作,不用停服务。而如果没有 LVM,光是倒数据、重新规划分区、迁移,就够折腾一个通宵。
3.2 PV、VG、LV 三层结构的落地命令
还是接着上面的 RAID 10 阵列,/dev/md0 建好后,初始化 LVM:
# 创建物理卷 pvcreate /dev/md0 # 创建卷组,名称用 vg_data vgcreate vg_data /dev/md0 # 查看卷组信息 vgdisplay vg_data # 创建逻辑卷,先划分一个 2T 的数据卷 lvcreate -L 2T -n lv_apps vg_data # 也可以直接用全部剩余空间建一个卷 # lvcreate -l 100%FREE -n lv_apps vg_data格式化并挂载:
# 根据文件系统需求格式化。xfs 和 ext4 在扩容能力上有区别,后面专门说 mkfs.xfs /dev/vg_data/lv_apps # 挂载 mkdir -p /data/apps mount /dev/vg_data/lv_apps /data/apps # 写入 fstab 实现开机自动挂载,用 UUID 更稳妥 blkid /dev/vg_data/lv_apps这里有个细节我要强调:逻辑卷的路径有两种写法,/dev/vg_data/lv_apps 是软链接形式,实际设备名可能是 /dev/dm-x。写 fstab 时,用 UUID 或完整逻辑卷路径都可以,但千万别直接用它自动生成的 /dev/dm-x,因为系统启动时设备编号可能变化。
3.3 xfs 和 ext4 在 LVM 上的扩容差异
这是新手最容易踩坑的地方。ext4 文件系统既可以扩大也可以缩小,而 xfs 只能扩大不能缩小。如果你提前知道将来可能要缩容某个文件系统,一开始就选 ext4。
扩容逻辑卷时,xfs 和 ext4 的命令也不同。xfs 要先扩容 LV,再执行 xfs_growfs;ext4 是先扩容 LV,再执行 resize2fs。
# 假设 lv_apps 要从 2T 扩到 3T lvextend -L +1T /dev/vg_data/lv_apps # xfs 在线扩容 xfs_growfs /data/apps # ext4 在线扩容 resize2fs /dev/vg_data/lv_appsxfs_growfs 后面跟的是挂载点,resize2fs 后面跟的是设备路径,这个区别我见过太多人搞混。还有一点,如果你在 fstab 里用了 xfs 的 pquota 特性,扩容时要确保挂载选项里带上对应的参数,否则扩容后 quota 信息异常。
4. 从零到一的完整实操流程
4.1 环境规划与磁盘准备
最近一次帮一个业务团队部署新的存储节点,需求是:跑容器化应用,需要 12T 裸容量,要求能容忍至少一块盘故障,并且未来半年可能扩展到 20T。
我选择了四块 4T 的 SAS 盘做 RAID 10,总容量 8T,上层 LVM 划分逻辑卷。为什么不直接上 RAID 6?因为四块盘做 RAID 6 只剩一半容量,而且 RAID 6 的重建性能开销更大,对于容器化应用这种随机读写偏多的场景,RAID 10 的延迟表现更稳定。
磁盘规划如下:
| 设备 | 用途 | 分区类型 |
|---|---|---|
| /dev/sdb | RAID 10 成员盘 | fd (Linux raid autodetect) |
| /dev/sdc | RAID 10 成员盘 | fd |
| /dev/sdd | RAID 10 成员盘 | fd |
| /dev/sde | RAID 10 成员盘 | fd |
| /dev/md0 | RAID 10 阵列 | LVM PV |
| vg_data | 卷组 | - |
| lv_docker | 容器数据卷 | xfs |
| lv_backup | 备份数据卷 | ext4 |
4.2 完整命令序列实录
清理磁盘并创建分区:
for disk in sdb sdc sdd sde; do wipefs -a /dev/$disk parted /dev/$disk --script -- mklabel gpt parted /dev/$disk --script -- mkpart primary 0% 100% parted /dev/$disk --script -- set 1 raid on done创建 RAID 10:
mdadm --create /dev/md0 --level=raid10 --raid-devices=4 \ /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 # 查看同步进度,等待完成 watch -n 5 cat /proc/mdstat同步完成后,搭建 LVM:
pvcreate /dev/md0 vgcreate vg_data /dev/md0 # 划分逻辑卷:lvm 元数据默认存放在卷组内,lvcreate 时可以指定 --metadatasize,但默认值足够用 lvcreate -L 6T -n lv_docker vg_data lvcreate -l 100%FREE -n lv_backup vg_data # 格式化 mkfs.xfs /dev/vg_data/lv_docker mkfs.ext4 /dev/vg_data/lv_backup # 挂载 mkdir -p /var/lib/docker /opt/backup mount /dev/vg_data/lv_docker /var/lib/docker mount /dev/vg_data/lv_backup /opt/backup # 写入 fstab,用 UUID echo "UUID=$(blkid -s UUID -o value /dev/vg_data/lv_docker) /var/lib/docker xfs defaults 0 0" >> /etc/fstab echo "UUID=$(blkid -s UUID -o value /dev/vg_data/lv_backup) /opt/backup ext4 defaults 0 0" >> /etc/fstab整个过程下来,系统层面完全在线操作,没有停机窗口。容器服务重新配置数据根目录后正常启动,读写延迟符合 RAID 10 的预期。
4.3 性能验证与监控配置
阵列建好后,不要急着上业务,先跑一轮性能基准测试。我习惯用 fio 做简单的读写压测:
# 顺序读 fio --name=seqread --rw=read --bs=1M --size=4G --numjobs=4 --runtime=60 --group_reporting # 随机写 fio --name=randwrite --rw=randwrite --bs=4K --size=4G --numjobs=4 --runtime=60 --group_reportingRAID 10 的随机写性能应该远高于单盘,如果测试结果和单盘差不多,就要检查阵列是否还在同步、控制器是否有瓶颈、盘本身是否故障。
监控方面,我会在 crontab 里加一个定期检查 mdstat 和 LVM 状态的脚本,一旦阵列降级或卷组空间不足,立刻告警:
# 每天凌晨检查一次 RAID 状态 0 2 * * * /root/scripts/check_raid.sh脚本内容很简单,就是抓取 /proc/mdstat 中是否有 [U_] 或 [_U] 这类降级标记,以及 vgdisplay 里 Free PE 的数量是否低于阈值。
5. 故障模拟、扩容实战与高频坑位记录
5.1 磁盘故障模拟与阵列恢复全过程
我每次帮团队搭完存储,都会建议做一次故障演练。因为只有在真实模拟过坏盘的情况下,你才知道自己的监控告警、备件流程、恢复步骤是否靠谱。这里给出完整的故障恢复流程。
假设 /dev/sdc 出现故障,可以用 mdadm 手动模拟:
# 模拟故障,把 sdc1 标记为 failed mdadm /dev/md0 --fail /dev/sdc1 # 查看阵列状态,此时应该能看到 [2/3] 或者类似降级标记 cat /proc/mdstat mdadm --detail /dev/md0此时系统还能正常工作,读写走的是剩下三块盘,RAID 10 的镜像组里坏了一块,数据无损。接下来移除故障盘:
mdadm /dev/md0 --remove /dev/sdc1物理更换新盘后,重新分区并加入阵列:
# 新盘可能是 /dev/sdc,也可能因为槽位变化变成其他设备名 parted /dev/sdc --script -- mklabel gpt parted /dev/sdc --script -- mkpart primary 0% 100% parted /dev/sdc --script -- set 1 raid on # 加入阵列 mdadm /dev/md0 --add /dev/sdc1 # 此时阵列会自动开始重建,观察进度 watch -n 5 cat /proc/mdstat重建过程中,阵列性能会下降,这是正常现象。如果是热插拔环境,务必确认新盘在系统里已经被正确识别,不要因为设备名错位,把一块好盘给 format 了。
5.2 容量不够时如何在线扩容
LVM 最大的卖点就是在线扩容。当 vg_data 空间不足时,有两条路。如果你有空的物理盘,可以直接扩到卷组里:
# 新盘 /dev/sdf,先做成 RAID 1 镜像,或者直接作为 PV 加入 # 这里假设加了一块单盘做 PV(注意,单盘没有冗余,只适合放可重建数据) pvcreate /dev/sdf1 vgextend vg_data /dev/sdf1 # 然后把空间分配给逻辑卷 lvextend -L +2T /dev/vg_data/lv_backup resize2fs /dev/vg_data/lv_backup如果卷组内有空闲未分配的 PE,那就更简单了,直接扩展逻辑卷即可。唯一要注意的是,扩展 xfs 的逻辑卷后,需要执行 xfs_growfs 更新文件系统,否则 df 里看到的还是旧容量。
我在生产环境里遇到过这样一个问题:lvextend 成功了,resize2fs 也成功,但 df 显示容量没变。排查了很久,发现是 fstab 挂载选项里指定了 usrquota 和 grpquota,导致 resize2fs 在重算 quota 信息时没有更新到超块。解决办法是先卸载或用 remount 清掉 quota 选项,再执行扩容。这个问题比较小众,但值得记一笔。
5.3 还原那些年踩过的坑:mdadm.conf 与设备名变化
做运维这些年,存储相关的故障我处理过不少,以下这些问题出现频率最高,也是面试时我常拿来考人的。
第一个坑:重启后阵列变成 inactive。原因基本就是 /etc/mdadm.conf 没有正确配置,或者 initramfs 没有更新。还有一个隐蔽的原因是多块盘同时掉线,导致内核在引导时无法自动组装阵列。这时候不要慌,手动使用 mdadm --assemble --scan 尝试恢复,如果有完整元数据,一般能找回来。
第二个坑:fstab 里用了 /dev/md0,但重启后系统识别不到。因为 md0 的编号不是固定的,如果有多套阵列,系统可能按扫描顺序给 md1、md2。正确的做法是在 mdadm.conf 里用 UUID 或设备路径定义阵列,同时在 fstab 里用 UUID 或 LVM 逻辑卷名,而不是直接用 /dev/mdX。
第三个坑:LVM 卷组显示 incomplete。这通常发生在多块盘组成 VG、其中一块盘故障的时候。系统启动时发现缺少 PV,卷组就无法激活。解决方法是先把故障盘的 PV 从 VG 中移除,重新挂载其他正常的 PV,然后激活卷组。
# 查看 PV 状态 pvdisplay # 移除故障 PV(注意,这是破坏性操作,仅在其他副本可用时执行) vgreduce vg_data /dev/sdc1 # 激活卷组 vgchange -ay vg_data第四个坑:误删了 LVM 元数据。虽然可以用 vgcfgrestore 恢复,但前提是你备份过 VG 元数据。养成习惯,在每完成一次 LVM 配置变更后,执行:
vgcfgbackup -f /backup/lvm/vg_data_backup_$(date +%F).vg这个命令极其便宜,但关键时刻能救命。我见过有同事折腾了三天最后靠这份备份恢复了整个卷组。
5.4 深度问答:看着像“面试题”但都是实战须知
问:RAID 5 坏一块盘后,为什么建议尽快换盘重建,而不是拖到业务低峰?
答:RAID 5 只允许坏一块盘。在重建完成前,整阵列处于脆弱状态。这期间如果再来一块盘故障,数据几乎无法找回。而且重建期间所有盘都在高强度读写,故障概率反而会上升。所以坏盘后要立刻处理,别赌运气。
问:LVM 的 PE 大小影响什么?
答:PE 是 LVM 分配空间的最小单位,默认 4MB。PE 越小,空间分配越灵活,但元数据会膨胀;PE 越大,适合大容量卷,后续缩容时的粒度更粗。如果你的场景需要频繁调整逻辑卷大小,建议在 vgcreate 时指定较小的 PE,比如 4MB 或 8MB,否则最小分配单位太大,空间会浪费。
问:RAID 阵列的分区对齐有什么用?
答:现代硬盘和 SSD 的逻辑块大小是 4K,分区起始扇区如果不是 4K 的整数倍,IO 就会产生读改写放大,性能会有明显下降。Linux 的 parted 分区工具默认对齐已经是 1MiB,基本不用手动干预。但如果你用 fdisk 老式交互创建分区,要注意扇区起始位置,通常设置成 2048 是安全的。
问:如何判断一块盘是不是快坏了?
答:SMART 信息是首要参考。重点关注 Reallocated_Sector_Ct(重映射扇区数)、Current_Pending_Sector(待重映射扇区数)、UDMA_CRC_Error_Count(传输错误计数)。这三个值只要持续增长,哪怕阵列还没提示降级,都要提前做替换规划。我一般会在巡检脚本里加入对这三个 SMART 属性的监控。
问:有没有必要在 LVM 之上再叠加一层文件系统加密或压缩?
答:如果安全要求高,可以在 LV 层用 dm-crypt 做加密,但这会带来额外的 CPU 开销和复杂度。压缩文件系统比如 ZFS 或 btrfs 有自己的压缩特性,但和 RAID + LVM 的组合方案不太兼容。我的建议是,如果没有明确的合规要求,别引入不必要的复杂度,存储方案越简单越可靠。
6. 实测tips:让这套组合在长期运行中更稳
最后分享几个平时不会写在文档里、但实际运行中非常有用的经验。
第一,RAID 阵列的同步和重建速度,可以通过调整 mdadm 的 sync speed 来限制。生产环境中,重建会让业务 IO 变慢,如果业务优先,可以临时把速度限制调低:
echo 20000 > /proc/sys/dev/raid/speed_limit_min echo 50000 > /proc/sys/dev/raid/speed_limit_max等到夜深人静业务低峰,再放开限制让它加速跑。
第二,LVM 的 snapshot 功能很多人只用来做备份前的快照,其实也可以用来做临时环境。在快照上跑一些有风险的数据变更,确认没问题后合并,出问题就直接删除快照回滚。但要注意,快照会占用卷组空间,如果快照空间满了,快照会自动失效,做好监控别让它默默爆掉。
第三,如果你用的是 SSD 做 RAID,要考虑耐磨和性能问题。mdadm 本身没有 SSD 感知能力,所以尽量别把不同品牌、不同寿命状态的 SSD 混在一个阵列里。同时,想追求单盘性能的话,可以在 mdadm.conf 里为阵列设置 read-ahead 和 write-behind 参数,比如 --write-behind=256 配合 --bitmap=internal,可以在断电后加快重建速度,同时提升一定的写性能。不过这些参数需要你根据自己的硬件和业务特点做一次压测对比,不能盲目抄。
第四,fstab 挂载时,我强烈建议在挂载选项里加上 noatime。对存储服务器来说,atime 更新产生的额外写入完全没意义,关掉后能减少大量不必要的 IO。
这套 mdadm + LVM 的组合方案,看起来每一步单独拎出来都不复杂,但把它组合成一个能长期稳定运行的系统,需要你对底层原理、常见故障、恢复路径有足够清晰的认识。我自己也是踩了无数次坑,从数据丢失的边缘爬回来之后,才真正把这些知识点串成了一条线。希望这篇文章能让你在规划自己的服务器存储时,少走一些弯路,至少不要在深夜两点对着一个 inactive 的阵列发呆。