PVE zfs mirror 磁盘故障恢复
这两天我遇到了 RAID 两块盘一起出故障的情况。这里记录一下故障缓解直到彻底恢复的过程。

两天前的凌晨,正在玩手机的我发现家里断网了。唤醒待机中的电脑,发现没有获取到 IPv4 地址。手动配上 IP 地址后打开路由器的管理界面,没发现什么问题,就尝试重启 dnsmasq, 但没有起作用。于是我决定重启整个 openwrt 路由器。

然而路由器并没有完成重启,反而,现在连 luci 管理界面都挂了。尝试 ssh 登录路由器,以及 web/ssh 登录 PVEW 母机(我的 openwrt 是运行在 PVE 上的虚拟机),都没有成功,只能去 reset PVE 母机。

重启后网络恢复了,但是检查 PVE 母机发现,两块硬盘组成的 zfs mirror degraded 了。 sdb 彻底失效,连 smart 数据都读不出来;同时 sda 的 smart 数据里有 pending sectors 和 offline uncorrected sectors. 当时我就觉得两块盘同时故障,可能恢复不了了。于是决定买两块盘替换掉故障硬盘,同时尽量拯救数据。不过事已至此,深夜就不要搞变更操作了,先睡觉,其它都第二天再说吧。

故障止损

首先,随着这台 PVE 母机自动启动的只有至关重要的 openwrt 路由器,其它的虚拟机都是手动启动的。目前这个状态下我就没有去启动其它虚拟机了。

ZED Notification

ZFS 是有一个 zed 服务监听 zfs 相关的事件并发送通知的。默认应该是通过邮件发送,但是我没有配置 MTA (邮件还是太复杂了)。现在我决定要赶快把它配上。

看了一下 zed 的配置文件,很好,它是自带 pushover 支持的。我本来就在使用 pushover 的服务,这里就直接配上 pushover 的 token, 重启 zed.service 即可。

数据备份

第二天下单了两块新硬盘 + UAS 硬盘盒,同时开始准备备份数据。

故障的这两块硬盘其实是 SMR 盘,所以我一开始就没把重要数据放在上面。上面的数据其实就是 PVE 系统、一个 openwrt 虚拟机、一个运行 ubnt controller 的 LXC. 另外还有几台虚拟机跑在这个母机上,但存储是通过 iSCSI 挂载在 NAS 上的。

查了一下 PVE 的备份命令 vzdump 的手册,发现它可以把备份写到 stdout. 那么最好的备份方式就是通过 ssh vzdump 把备份直接保存在我的台式机上,避免再往已经出问题的硬盘上写入大量数据。备份命令为:

ssh root@<pve-host> vzdump <vmid> --compress zstd --stdout > <vmid>.vma.zst

Openwrt 虚拟机的磁盘只有十几 MB, 很快完成了。 运行 ubnt controller 的 LXC 更大一点,不过也顺利完成了备份,没有触发 I/O 错误。

替换故障硬盘

收到硬盘后,首先关机用新硬盘换掉失效的 sdb. 开机后首先需要对硬盘进行分区。

创建分区

一开始我准备用 sfdisk --dump <old> | sfdisk <new> 直接把旧硬盘的分区表复制过去。但我买的新硬盘比旧硬盘大,复制分区表过去后发现没办法把分区扩大到整个硬盘的大小。(可能是 GPT 分区表里面还记录了整个磁盘的大小?)于是只能手动把分区抄过去。

PVE 安装的时候会在硬盘上创建 3 个分区: BIOS BOOT, EFI SYSTEM PARITION, 和根分区。其中 BIOS BOOT 分区不是 4K 对齐的。这让我用分区工具手动创建的时候很不好操作。所以我就不创建 BIOS BOOT 分区了,反正我不需要 legacy boot.

替换 zpool 设备

执行 zpool status rpool 命令(其中 rpool 是这个 zpool 的名称),会显示当前的状态:

# zpool status rpool 
  pool: rpool
 state: DEGRADED
status: One or more devices could not be used because the label is missing or
        invalid.  Sufficient replicas exist for the pool to continue
        functioning in a degraded state.
action: Replace the device using 'zpool replace'.
   see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-4J
  scan: resilvered 1.21G in 00:15:09 with 0 errors on Sat Jul  8 17:18:50 2023
config:

        NAME                                       STATE     READ WRITE CKSUM
        rpool                                      DEGRADED     0     0     0
          mirror-0                                 DEGRADED     0     0     0
            ata-ST***********************H9-part3  ONLINE       0     0     0
            52***************90                    UNAVAIL      0     0     0  was /dev/disk/by-id/ata-ST***********************ZX-part3

其中, UNAVAIL 的是我们要替换的设备。替换的命令是:

zpool replace rpool 52***************90 /dev/disk/by-id/ata-HGST_HU********************YM-part3

其中,52***************90 是前面 zpool status 命令显示的 NAME, /dev/disk/by-id... 是新的设备分区。执行后 zfs 会开始在后台 resilver, 即重建 mirror. 可以执行 zpool status 命令查看进度。我这里需要复制 29 GB 的数据,即使是 SMR 盘,也不会花很长时间。

不久后手机上收到了 pushover 的推送通知,resilver 顺利完成了。还好 sda 的 pending sectors 和 offline uncorrected sectors 没有影响到数据。

重建引导

为了达到整个系统的高可用,除了根分区的 ZFS 是 mirror 的,PVE 还需要在两个硬盘上都建立 ESP, 安装上 systemd-boot、内核、initramfs, 并注册到 UEFI 启动项中。之前分区的时候已经创建了 ESP, 现在只要用 PVE 自带的工具就可以一键完成。

proxmox-boot-tool format /dev/sdb2
proxmox-boot-tool init /dev/sdb2

执行 init 的过程中会提示有磁盘 does not exist, 这是被替换掉的旧设备。根据提示去编辑 /etc/kernel/proxmox-boot-uuids, 删除其中的旧设备。

执行完后用 efibootmgr -v 检查一下,发现旧的硬盘还在里面。执行 efibootmgr --delete-bootnum -b <num> 删掉对应的旧启动项。

替换 sda

sda 有 pending sectors 和 offline uncorrected sectors, 也是处于非常不健康的状态,所以也要替换掉它。替换的过程和前面替换 sdb 完全一致。

扩大文件系统

这次换的新硬盘比旧硬盘大,所以还需要扩大文件系统。执行命令 zpool set autoexpand=on rpool 打开 autoexpand. 但是好像没有触发 expand. 试了一下 zpool reopen rpool 也没效果。最后用

zpool offline rpool ata-HGST_HU********************YM-part3
zpool online rpool ata-HGST_HU********************YM-part3
zpool offline rpool ata-HGST_HU********************5M-part3
zpool online rpool ata-HGST_HU********************5M-part3

给两块硬盘分别切换 offline/online, 触发了 expand.

Expand 完成后执行 zpool set autoexpand=off rpool 关掉 autoexpand.

处理旧硬盘

两块旧硬盘,一块还能访问的,插到 UAS 硬盘盒里去执行 secure erase. 另一块彻底失效的,拆掉螺丝打开外壳,毁掉盘面。附上硬盘照片。

opened hard disk case

可以看到这是一块单盘双面的硬盘。另外有意思的是,以前我以为磁头是用弹簧固定在磁头架上的,只要断电就会回弹。但实际上这块硬盘的磁头拨到盘面上以后,是不会自动回弹的,所以断电紧急停靠大概是依赖电容存储的能量来驱动电机收回磁头。

磁盘表面也真的是光滑到极致了,在划破磁盘表面之前,我去转动轴承,都看不出来盘面有没有跟着旋转。

故障分析

首先,这里根因肯定在于 SMR 硬盘。我知道 SMR 不靠谱,但没想到这么不靠谱。根据 resilver 时的统计信息,我这个 zfs 上面就只有 29 GB 数据,会持续产生写入的只有 ubnt controller 使用的 mangodb. SMART 数据显示总共有 400 TB 的写入量,我很怀疑这个数据不准确。这两块硬盘在我这样的轻度负载下,运行了两年多,还没满 3 年保修期就坏了。

其次,我没有配置 zed 故障告警是问题很大的。那天凌晨当机可能已经不是故障的第一现场了。如果我能早点发现问题,可能就不会像现在这样一块硬盘失效,另一块硬盘告警这么吓人了。但是现在要配置告警通知也很麻烦,一般普遍支持的方式都是邮件告警。但现在商业邮箱注册基本上都要验证手机号,我又不想和自己共用主力邮箱(担心数据安全),要自己搭建 MTA 那就更加复杂。还好现在 zed 支持 pushover 之类基于 HTTP API 的通知服务。


最后修改于 2025-08-14