文中所有 IP、序列号均已脱敏,思路和命令不受影响。

起因:不想把数据交给不信任的系统

我的主力 NAS 之前跑的是飞牛(fnOS)。用下来功能没得挑,但有个心结始终解不开——它是国产闭源系统,数据主权这种事,等厂商真做了什么再想撤就晚了。所以决定迁回黑群晖(Xpenology),装完固定版本、以后也不升级了。反正我所有服务都在 Docker 里,迁移主要是存储和数据搬家的事。

结果这趟「简单的搬家」,在硬件上给我上了一课。

一个意外的好消息:存储格式其实兼容

动手前我最怕的是存储格式不兼容——以为飞牛是裸 btrfs,群晖认不出会强制格式化。上机一看 lsblk,发现自己想错了:

sda1 → linux_raid_member → md → LVM2_member → btrfs → /volX

飞牛的存储结构和群晖几乎同构mdadm(单盘 RAID1)+ LVM + btrfs 三层,md superblock 也是 1.2 版本。这意味着数据盘插到群晖上,理论上能用命令行手动挂载读出来,不必先搬走再搬回。

于是方案定了:

  1. 先备份不可再生的东西(服务配置、密码库、种子文件)
  2. 物理拔掉数据盘,用空盘装 DSM、建存储池
  3. 插回数据盘,命令行只读挂载,把数据灌进新池

第 2 步的拔盘是铁律——群晖装机时会在它能看到的每一块盘上划系统分区,数据盘不隔离就开装,13T 直接报废。

备份:几分钟的事,别省

不可再生的数据几乎全在配置盘上,才几个 G。用 tar 打包整个 Docker 目录,再对几个 sqlite 数据库做在线一致性快照(不用停容器):

import sqlite3
s = sqlite3.connect(f"file:{src}?mode=ro", uri=True)
d = sqlite3.connect(dst)
s.backup(d)   # 一致性快照,避免热备份拿到撕裂的中间态

密码库、索引器配置、辅种记录逐个存好,双份放在 NAS 和电脑上,全部校验通过。这一步零风险、完全可逆,是整个迁移里唯一不该图省事的环节。

诡异登场:怎么数着数着盘就少了

麻烦从这里开始。我这台是 6 块 16T + 2 块 SSD。现象是这样的:

  • 飞牛时代:6 块 16T 只稳定认到 4 块,存储管理里两块显示「已移除」「已损坏」
  • 拔掉数据盘、用 RR 引导启动(此时机内 5 块 16T):lsblk 一次认全 5 块
  • 装完 DSM、插回数据盘(6 块 16T):用户在存储管理器里只看到 5 块

到这一步,我犯了本文最该记下的错误。

我看到 RR 环境认全了 5 块盘,就武断地下了结论:「是飞牛的软件问题,换系统就好了」,还信心满满地写进了排查记录。

问题在于——RR 里那 5 块,是数据盘被拔掉后的 5 块。我验证的是「5 块盘」的场景,而故障只在「6 块盘」时才出现。我测的配置,和出故障的配置根本不是同一个。

dmesg 打脸:真凶是电源

装完 DSM,我 SSH 进去准备只读挂载数据盘。/proc/partitions 显示内核层面 6 块 SATA 全在,数据盘的飞牛分区也完整——一度以为稳了。可当我尝试组装那块盘的阵列、开始读取时,/dev/md20 直接报 Input/output error,数据盘的分区设备 sata4p1 凭空消失。

赶紧翻 dmesg,真相一览无余:

ata5.00: failed command: READ FPDMA QUEUED ... (ATA bus error)
ata5: hard resetting link
ata2: COMRESET failed (errno=-16)
ata1: COMRESET failed, set COMRESET fail flag
ata5: SATA link down (SStatus 0 SControl 310)
ata5: device unplugged sstatus 0x0

注意关键点:报错同时发生在 ata1、ata2、ata5 多个链路上。 一块盘出问题是盘或线,多块盘的链路同时复位、掉线,几乎只有一个解释——供电塌了。 6 块盘一起读写时,12V 供电瞬间扛不住,多个盘一起从总线上掉下来。

再回头看全部现象,一条线全串起来了:

场景 盘数 结果
飞牛 6 块 只稳 4 块
RR 引导 5 块 全稳
DSM 冷启动 6 块 顺序上电时认全
DSM 加压力 6 块 开始掉链路

供电能力的天花板,正好卡在第 5 块和第 6 块之间。 冷启动时磁盘顺序上电,功耗被摊开,所以都认得到;一旦并发读写,6 块盘同时拉电流,瞬时功耗越过电源的坎,链路就崩了。后来用户自己也验证:只上 5 块盘全稳,上第 6 块就掉——把结论彻底钉死。

为什么「软件问题」这个判断是错的

这是我这次最该反省的地方。「换个 Linux 环境跑 lsblk 能认全盘」——这个观察本身没错,但我拿它当了「软件问题」的证据,犯了两个错:

  1. 验证了错误的配置。RR 认全的是 5 块盘,而故障出在 6 块盘。我用 A 场景的成功,去否定 B 场景的故障,逻辑上根本不成立。
  2. 把「症状消失」当成了「病根找到」。少一块盘时不掉盘,只能说明少一块盘时供电够用,推不出「盘和电源都没问题」。

真正的证据是 dmesg 里那串链路复位——它直接指向硬件层,而且指向了「多盘同时受影响」这个供电特有的模式。排障时,用来下结论的那次测试,必须和故障发生的条件完全一致;否则测得再干净,也是假验证。

数据安全:只读挂载是硬保证

插上一个来源不明、结构陌生的盘,最怕手一抖把它写坏。所以整个探查过程我坚持两条:

  • 组装阵列一律带 --readonly,从内核层面禁止写入
  • 挂载一律 mount -o ro

即便中途出了 I/O 错误,因为全程只读,数据盘上的 13T 一个字节都没被动过。mdadm --examine 也确认了阵列状态是 clean——数据完好,坏的只是供电。

顺带一个 DSM 的小坑:它的 LVM 带设备过滤器,默认拒绝扫描外来的 md 设备,得用 --config 覆盖过滤器才能读到飞牛的卷组。这是后话,等电源修好再续。

结论与处置

  • 病根:电源(一块入门级老电源)带不动满配 6 块企业盘的并发功耗
  • 不用换机箱:机箱背板「3 块盘共用一路供电」是正常设计,好电源带得动
  • 换电源即可:一块全新的金牌电源,两路独立的大 4pin(Molex)供电,一块背板一路,Molex 载流强于 SATA 供电口,正是为多盘背板准备的
  • 再加一道保险:BIOS 开启错峰上电(Staggered Spin-Up),把开机瞬间 6 块盘同时启动的功耗峰值摊平

装好新电源后的第一件事,不是急着拷数据,而是满载压力测试盯着 dmesg 看链路稳不稳。稳了,再挂盘、灌数据。硬件不稳的时候去搬 13T,等于拿数据赌运气。

写在最后

这趟折腾最值钱的收获,不是「飞牛迁群晖」的操作步骤,而是那个「验证了错误配置」的教训。我们常常在下意识里,用一个「差不多」的测试去代替真正该做的那个测试,然后把它的结果当成结论。 5 块盘和 6 块盘,差的不只是一块盘,而是整个供电系统在不在临界点上——这一块之差,就是「软件问题」和「硬件问题」的天壤之别。

dmesg 不会撒谎,撒谎的是急着下结论的人。