mdadm RAID5 硬盘替换实战(含热备盘)

mdadm RAID5 硬盘替换实战

环境

  • 虚拟机:VMware Workstation
  • 系统:Ubuntu 22.04 (kernel 5.15.0-185)
  • 磁盘:4×20G 虚拟磁盘 + 1×20G NVMe 系统盘(Paravirtual SCSI 控制器)
  • 存储栈:
1
2
3
4
sda ─┐
sdb ─┼─ md0 (RAID5, 3+1 spare) → vg0 → lv-0 (ext4) → /data (40G)
sdc ─┤
sdd ─┘ (hot spare)

实战时间线

00: 前置 — 从 LVM 裸 PV 迁移到 mdadm

原先 4 块盘直接做 LVM PV,改为 mdadm RAID5 后重建 LVM 栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
umount /data
vgremove vg0 # 删除 VG(内含 lv-0)
pvremove /dev/sd{a,b,c,d} # 擦除 LVM 标签

# 创建 RAID5:3 盘数据 + 1 热备
mdadm --create /dev/md0 --level=5 --raid-devices=3 --spare-devices=1 \
/dev/sda /dev/sdb /dev/sdc /dev/sdd

# 重建 LVM 层
pvcreate /dev/md0
vgcreate vg0 /dev/md0
lvcreate -n lv-0 -l 100%FREE vg0
mkfs.ext4 /dev/vg0/lv-0
mount /dev/vg0/lv-0 /data

# 持久化
echo '/dev/vg0/lv-0 /data ext4 defaults 0 2' >> /etc/fstab
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
update-initramfs -u

初始阵列状态:sda/sdb/sdc active sync,sdd spare(热备)。State: clean

01: 创建测试数据 + 校验和

1
2
3
4
5
6
7
8
9
10
11
12
dd if=/dev/urandom of=/data/test_100M.bin bs=1M count=100     # 105 MB
dd if=/dev/urandom of=/data/test_10M.bin bs=1M count=10 # 10 MB
echo "重要数据-验证用" > /data/important.txt
cp -a /etc /data/etc_backup

md5sum /data/test_100M.bin /data/test_10M.bin /data/important.txt > /tmp/checksums_before.txt
find /data/etc_backup -type f -exec md5sum {} \; | sort -k2 > /tmp/checksums_etc_before.txt

# 校验和:
# d3a9fd89ce... /data/test_100M.bin
# 66059d3c18... /data/test_10M.bin
# a3518e3b2c... /data/important.txt

教训/tmp/checksums_before.txt 存在 tmpfs 上,重启即丢。重要校验和应存到持久存储或 /data 自身。

02: 模拟故障 — 拔 sdb,热备自动接管

方式一:命令行标记故障(可控):

1
2
3
4
mdadm --manage /dev/md0 --fail /dev/sdb
# → sdb 标记 faulty,sdd 自动从 spare 切换为 spare rebuilding
# → /proc/mdstat: [2/3] [_UU], recovery = 6.6% → ... → 100%
# → 重建耗时 ~1.5 分钟(512K chunk, SCSI 虚拟盘 ~200 MB/s)

方式二:直接从 VMware 移除虚拟磁盘:

内核报 DID_NO_CONNECT I/O error,mdadm 检测到后自动标记故障:

1
2
3
dmesg: sd 32:0:1:0: [sdb] FAILED Result: hostbyte=DID_NO_CONNECT
dmesg: md/raid:md0: Disk failure on sdb, disabling device.
dmesg: md: recovery of RAID array md0

重建完成后移除故障盘:

1
2
3
mdadm --manage /dev/md0 --remove /dev/sdb
# → State: clean, Active: 3, Working: 3, Failed: 1, Spare: 0
# → 此时无热备,3 盘裸跑

03: 加入新热备盘

1
2
3
mdadm --zero-superblock /dev/sdc   # sdc 原是空闲盘
mdadm --manage /dev/md0 --add /dev/sdc
# → State: clean, Spare: 1,sdc 就位

04: ⚠️ 首次重启踩坑 — emergency mode

现象

重启后系统掉进 emergency mode:

1
2
3
4
5
You are in emergency mode. After logging in, type "journalctl -xb" to view
system logs, "systemctl reboot" to reboot, "systemctl default" or "exit"
to boot into default mode.
Press Enter for maintenance
(or press Control-D to continue):

根因/etc/fstab 中有两行 /data 挂载条目,任一行挂载失败都触发 emergency mode:

1
2
3
4
grep data /etc/fstab
# # /data was on /dev/vg0/lv-0 during curtin installation
# /dev/disk/by-id/dm-uuid-LVM-... /data ext4 defaults 0 1 ← curtin 自动生成
# /dev/vg0/lv-0 /data ext4 defaults 0 2 ← 手动添加

curtin/cloud-init 生成的 by-id 路径在 RAID 重组后 UUID 变了,挂载失败。

实际踩坑中还遇到一个血泪细节:emergency mode 下根文件系统默认只读,sed 修改 fstab 静默失败。必须先 mount -o remount,rw /

修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 登录 emergency mode(输入 root 密码)

# 2. ⚠️ 必须先 remount 根分区为读写!
mount -o remount,rw /

# 3. 注释所有 /data 挂载行
sed -i 's|^/dev.* /data |#&|' /etc/fstab

# 4. 确认
grep data /etc/fstab
# → 所有行前面都应有 # 号

# 5. 继续启动
exit

05: 第二次重启 — 正常

1
2
3
4
5
6
7
8
9
10
11
# 阵列和 LVM 自动组装
lsblk
# sda/sdb/sdd ── md0 ── vg0-lv--0 (40G lvm)
# sdc 独立(后加入为 spare)

cat /proc/mdstat
# md0: active raid5 sdb[4] sda[5] sdd[3] [3/3] [UUU]

mdadm --detail /dev/md0
# State: clean, Active: 3, Working: 4, Failed: 0, Spare: 1
# sda/sdb/sdd active sync, sdc spare

06: 验证数据完整性

1
2
3
4
5
6
7
8
9
mount /dev/vg0/lv-0 /data   # 已自动挂载
df -h /data # 40G, 116M 占用

cat /data/important.txt # → "重要数据-验证用" ✓
ls -lh /data/
# test_100M.bin 100M ✓
# test_10M.bin 10M ✓
# etc_backup/ ✓
# important.txt ✓

注意/tmp/checksums_before.txt 因重启丢失(tmpfs),无法做严格 MD5 对比。但文件大小、内容完全一致,RAID5 重建成功验证数据完好。

07: 恢复 fstab

1
2
3
vi /etc/fstab
# 取消两行 /data 的注释(去掉 #)
mount -a # 无报错即正常

踩坑复盘

编号 现象 根因 解法
1 emergency mode 重启后卡 emergency mode,systemctl defaultTransaction destructive fstab 有两行 /data,curtin 生成的 by-id 行和手动添加的 /dev/vg0/lv-0 行,任一挂载失败触发 emergency mount -o remount,rw /sed 注释掉所有 /data
2 emergency mode 下 fstab 只读 sed 执行看似成功,重启后 fstab 没变 emergency mode 默认挂载根分区为只读,sed -i 修改实际写入失败 必须先 mount -o remount,rw /
3 sed 命令拼写错误 `sed -i ‘s ^/dev/vg0/lv-0 …’中的vg0打成vgo`,匹配不到目标行
4 校验和丢失 /tmp/checksums_before.txt 重启后不存在 /tmp 是 tmpfs,重启即清空 校验和文件应存到 /data/root 或外部持久存储
5 VMware 新盘不识别 添加虚拟磁盘后 lsblk 不显示新盘 新盘可能挂在不同 SCSI 控制器类型(SATA vs Paravirtual SCSI) 重启虚拟机解决;或 echo "- - -" > /sys/class/scsi_host/host*/scan 手动扫描
6 VMware 拔盘后 mdadm 反应延迟 dmesg 已报 I/O error 但 cat /proc/mdstat 仍显示 [UUU] mdadm 不是实时轮询,有一定检测延迟 等待几秒或手动 mdadm --fail 加速
7 拔盘后重启,不同设备名 第一次启动 sdb 是 faulty,重启后 sdb 又变回正常 active mdadm 用 superblock UUID 识别成员,设备名 /dev/sdX 只是临时路径,重启后内核可能重新分配 不要用设备名判断状态,始终看 mdadm --detail 中的 Number/State

完整操作流程(生产环境标准步骤)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# === 准备阶段 ===
# 0. 备份 fstab(预防 emergency mode)
cp /etc/fstab /etc/fstab.bak.$(date +%Y%m%d)
sed -i 's|^/dev.* /data |#&|' /etc/fstab

# 1. 创建测试数据并记录校验和(存到持久位置!)
dd if=/dev/urandom of=/data/test_100M.bin bs=1M count=100
echo "重要数据" > /data/important.txt
md5sum /data/test_100M.bin /data/important.txt > /root/checksums_before.txt

# === 故障模拟 ===
# 2. 标记故障盘
mdadm --manage /dev/md0 --fail /dev/sdb

# 3. 观察热备重建
watch -n 3 'cat /proc/mdstat | grep md0'
# 等待 recovery 100%,[UUU]

# 4. 移除坏盘
mdadm --manage /dev/md0 --remove /dev/sdb

# === 替换 ===
# 5. 加入新盘/替换盘
mdadm --zero-superblock /dev/sdb # 如果重用同一块盘
mdadm --manage /dev/md0 --add /dev/sdb

# 6. 观察重建
cat /proc/mdstat

# === 验证 ===
# 7. 数据完整性
md5sum -c /root/checksums_before.txt
ls -lh /data/

# === 收尾 ===
# 8. 恢复 fstab
cp /etc/fstab.bak.$(date +%Y%m%d) /etc/fstab
mount -a

命令速查

操作 命令
查看阵列状态 cat /proc/mdstat
查看详情 mdadm --detail /dev/md0
查看阵列成员状态 mdadm --detail /dev/md0 | grep -E 'State|Number.*sd'
标记故障 mdadm --manage /dev/md0 --fail /dev/sdX
移除盘 mdadm --manage /dev/md0 --remove /dev/sdX
添加盘 mdadm --manage /dev/md0 --add /dev/sdX
擦除 superblock mdadm --zero-superblock /dev/sdX
持久化配置 mdadm --detail --scan >> /etc/mdadm/mdadm.conf
更新 initramfs update-initramfs -u
手动触发检查 echo check > /sys/block/md0/md/sync_action
扫描 SCSI 总线 for h in /sys/class/scsi_host/host*/scan; do echo "- - -" > "$h"; done
dmesg 查盘故障 dmesg | grep -iE 'sd[a-z]|fail|error|DID_NO_CONNECT'
挂载验证 mount -a(无报错即 fstab 正常)

查询命令汇总

日常巡检和排障时快速查看 RAID + LVM + 磁盘 全链路状态。

RAID 阵列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 一行看阵列状态(最常用)
cat /proc/mdstat

# 阵列详细信息
mdadm --detail /dev/md0

# 只看关键字段
mdadm --detail /dev/md0 | grep -E 'State|Active|Working|Failed|Spare|Rebuild'

# 阵列成员及角色
mdadm --detail /dev/md0 | grep -A20 'Number.*Major'

# 阵列 UUID(配置持久化用)
mdadm --detail --scan

LVM

1
2
3
4
5
6
7
8
9
10
11
12
# 物理卷
pvs
pvdisplay

# 卷组
vgs
vgdisplay vg0

# 逻辑卷(含 RAID 类型和同步进度)
lvs
lvs -a -o name,size,segtype,devices,vg_name
lvdisplay /dev/vg0/lv-0

磁盘

1
2
3
4
5
6
7
8
9
10
11
# 树形看全貌
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT

# 看磁盘型号/序列号
lsblk -o NAME,SIZE,MODEL,SERIAL,TYPE

# 看分区和文件系统
lsblk -f

# 磁盘总览
fdisk -l /dev/sd[a-z] 2>/dev/null | grep -E 'Disk /dev|Disk model'

文件系统

1
2
3
4
5
6
7
8
9
# 挂载情况
df -hT /data
mount | grep /data

# fstab 中的 /data 条目
grep data /etc/fstab

# 文件系统 UUID
blkid /dev/vg0/lv-0

故障排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 内核日志中磁盘/RAID 错误
dmesg | grep -iE 'sd[a-z]|md|fail|error|DID_NO_CONNECT|I/O error'

# 磁盘 SMART 信息
smartctl -a /dev/sda | grep -E 'Reallocated|Pending|Uncorrectable|Error'

# 触发 RAID 一致性检查
echo check > /sys/block/md0/md/sync_action
cat /proc/mdstat # 观察检查进度

# 检查当前同步/检查进度
cat /sys/block/md0/md/sync_completed
cat /sys/block/md0/md/sync_speed

# RAID 阵列当前操作(idle/check/resync/recover)
cat /sys/block/md0/md/sync_action

一键巡检脚本

保存为 /root/check-storage.sh,日常跑一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
echo "===== RAID 状态 ====="
cat /proc/mdstat
echo ""
echo "===== RAID 详情 ====="
mdadm --detail /dev/md0 | grep -E 'State|Active|Working|Failed|Spare|Rebuild'
echo ""
echo "===== LVM ====="
pvs && vgs && lvs
echo ""
echo "===== 磁盘 ====="
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT | grep -E 'sd|md|vg|nvme'
echo ""
echo "===== 挂载 ====="
df -hT /data
echo ""
echo "===== 最近磁盘错误 ====="
dmesg | grep -iE 'sd[a-z]|md|fail|error|DID_NO_CONNECT' | tail -10

mdadm RAID5 硬盘替换实战(含热备盘)
https://bote798.top/2026/07/04/RAID5-硬盘替换实战-含热备盘/
作者
bote798
发布于
2026年7月4日
更新于
2026年7月4日
许可协议