从群晖到飞牛:921G Btrfs 数据卷的「无损」迁移实录

一次真實的遷移記錄:群暉 DSM 7.2 的 volume_1(921G Btrfs)整盤插到飛牛 fnOS 上,全程只讀、不寫源盤一個字節,400GB 數據 7 小時拷完。

中間踩過的坑、試錯的死路、以及最終爲什麼只有 btrfs restore 能走通,都寫在下面。


一、背景

用了幾年黑群暉,決定換飛牛 fnOS。數據不想走”外接硬盤中轉”那一套(要來回倒兩輪),也不想在中間再搭一臺機器。

最理想的做法是:把群暉的數據盤直接插到飛牛機器裏,在飛牛裏把數據讀出來、拷到飛牛自己的存儲空間,然後群暉這組盤原樣收起來——將來想回去還能插回去接着用。

聽起來很簡單,實際上卡了整整一晚上。

二、環境

項目 內容
源 群暉 DSM 7.2,單盤 931.5G,volume_1 = 921G Btrfs
目標 飛牛 fnOS,系統盤 15G + 數據盤 3.6T
源卷元數據 label 2024.05.11-14:52:46 v69057,transid 4516191
源卷實際用量 bytes_used = 399,898,472,448(約 372.5 GiB)
工具 btrfs-progs v6.2,內核 6.18.18.c1126-trim

三、第一步:先認清盤的結構

群暉的存儲是三層套娃,必須一層層剝,跳步就會得到”wrong fs type”這種毫無信息量的報錯:

物理盤  sdb3
  └─ mdadm RAID1  md2           ← 群暉的軟 RAID(SHR / Basic 也是 md)
      └─ LVM PV   vg1
          └─ LV   volume_1      ← 921G
              └─ Btrfs          ← 真正的文件系統在這裏

看一眼現狀:

cat /proc/mdstat        # md2 是否 active、是否 degraded
pvs; vgs; lvs -a -o +segtype,devices
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,MOUNTPOINT

我的實際輸出(簡化):

sdb                              931.5G disk
├─sdb1                             8G part  linux_raid_member <host>:trim
├─sdb2                             2G part  linux_raid_member DiskStation:1
└─sdb3                         921.3G part  linux_raid_member <host>:2
  └─md2                        921.3G raid1 LVM2_member
    ├─vg1-syno_vg_reserved_area    12M lvm
    └─vg1-volume_1                921G lvm   btrfs  2024.05.11-14:52:46 v69057

小提示:vg1-volume_1 顯示出來了不等於能用。LVM 的 Attr 裏第 5 位是 a 纔是 active;-wi-a----- 表示已激活但沒被打開,-wi-a----- 和 -wi-ao---- 只差”有沒有掛載中”。

激活:

vgchange -ay vg1
ls -l /dev/mapper/

四、第二步:爲什麼 mount 一定失敗

這時候所有人(包括我)的第一反應都是:

mount -o ro /dev/mapper/vg1-volume_1 /mnt/syno1

結果:

mount: /mnt/syno1: wrong fs type, bad option, bad superblock on /dev/mapper/vg1-volume_1,
       missing codepage or helper program, or other error.

dmesg 裏纔是真相:

BTRFS: device label 2024.05.11-14:52:46 v69057 devid 1 transid 4516191
       /dev/mapper/vg1-volume_1 (253:2) scanned by mount (4100)
BTRFS critical (device dm-2 state ES): corrupt leaf: root=1 block=487157137408 slot=0,
       invalid root flags, have 0x40000000 expect mask 0x1000000000000001
BTRFS error (device dm-2 state ES): read time tree block corruption detected on logical 487157137408 mirror 1
BTRFS error (device dm-2 state ES): read time tree block corruption detected on logical 487157137408 mirror 2
BTRFS error (device dm-2 state ES): open_ctree failed: -5

翻譯一下:

  • 不是壞盤,也不是文件系統壞了。 兩個 mirror 報一模一樣的錯,說明兩邊數據完全一致——真損壞不會這麼整齊。
  • 群暉在自己的 Btrfs 裏給 root item 加了一個私有標誌位 0x40000000(用來標記共享文件夾/快照之類的狀態)。
  • 主線的 btrfs tree-checker 只認 BTRFS_ROOT_SUBVOL_RDONLY / BTRFS_ROOT_SUBVOL_DEAD 兩個合法位,看到 0x40000000 直接判 EUCLEAN,中止掛載。
  • 這個校驗大約在內核 5.4 之後變嚴格,所以老內核(如 Ubuntu 18.04 的 4.15)能掛,新內核不能。

於是所有”常規補救”都註定無效:

mount -o ro ...                      # ❌
mount -o ro,rescue=all ...           # ❌
mount -o ro,usebackuproot ...        # ❌
mount -t btrfs -o sb=1 ...           # ❌

它們都繞不過 tree-checker。

順帶記一個坑:noload 是 ext4 的選項,btrfs 對應的叫 norecovery / nologreplay。我看網上教程寫了 -o ro,noload,內核直接回 btrfs: Unknown parameter 'noload'。

五、四條死路(別試,會掉數據)

做法 後果
btrfs check --repair 真改元數據。論壇上有人跑完,卷徹底報廢
btrfs rescue zero-log / super-recover 都會寫盤;你的問題不在日誌/超級塊,跑了只會更糟
在飛牛界面點”創建存儲空間”指向這塊盤 等於格式化,921G 當場歸零
去掉 -o ro 讀寫掛載 群暉的 Btrfs 被其他內核寫過之後,插回群暉大概率報”存儲空間已損毀”

另外一個隱藏坑:網上很多教程的這行命令是筆誤——

mount /dev/mapper/vg1-volume_1 > /mnt/syno1

> 是 shell 重定向,不是參數。你會得到 bash: /mnt/syno1: Is a directory,而且 mount 根本沒執行。

六、第三步:正解是 btrfs restore

核心認知的轉變:

掛載是內核在做(要過 tree-checker),btrfs restore 是 btrfs-progs 用戶態在做。
後者不做那麼嚴的 flags 校驗,而且只讀——它不會往源盤寫一個字節。

它本來是給”文件系統損壞時搶救數據”用的工具,正好解決這種”內核不認但數據完好”的場景。

關鍵參數

btrfs restore -i -m -S -o /dev/mapper/vg1-volume_1 /vol1/1000/<user>/syno_restore
參數 作用 說明
-i 忽略讀錯誤,繼續拷 遇到壞塊不中斷,能救多少救多少
-m 恢復屬主/權限/時間 不加的話拷出來全是 root、權限錯亂
-S 恢復子卷 群暉的共享文件夾都是子卷(@syno 等)
-o 目標已存在則覆蓋 斷點續跑必須加,重跑不會重複佔空間
-x 恢復 xattr 不要加! 群暉的 xattr 目標端存不了,會刷屏拖慢
-d 幹跑,只打印不寫 先驗證路徑正則很有用

後臺跑,別讓 SSH 斷了白乾

400G 要跑幾個小時,必須脫離終端:

cd /vol1/1000/<user>
setsid nohup btrfs restore -i -m -S -o \
  /dev/mapper/vg1-volume_1 \
  /vol1/1000/<user>/syno_restore \
  > /vol1/1000/<user>/restore.log 2>&1 &

# 驗證真的脫離了終端:PPID 應該是 1,TTY 是 ?
ps -o pid,ppid,tty,etime,cmd -C btrfs

三個容易踩的點:

  1. 一定要 sudo -i 或 sudo bash -c。普通用戶跑 nohup 會在你的家目錄寫 nohup.out,家目錄不存在就直接失敗退出(而且什麼都沒拷)。
  2. 顯式寫 > restore.log,別依賴默認的 nohup.out。
  3. 日誌別寫到 /var/log,會撐爆根分區。目標盤上有空間,就寫目標盤。

只想拷某幾個目錄

先幹跑確認匹配,再去掉 -d:

btrfs restore -d -i -m -S -o --path-regex '^/(|@syno(/.*)?)$' \
  /dev/mapper/vg1-volume_1 /目標目錄

七、實戰時間線

從 2026-10-29 22:12 開始,到 10-30 05:06 結束,約 6 小時 54 分:

時間 已寫 速度 備註
22:12 0 — 啓動
22:39 52G ~26MB/s 初期,小文件多
23:14 76G ~3MB/s 大量小文件(照片/縮略圖),最慢
00:38 129G ~20MB/s 起伏
01:46 175G ~5MB/s 又一段小文件
02:59 186G ~1MB/s 幾乎停滯(懷疑卡在 @eaDir 縮略圖)
03:42 237G ~35MB/s 開始出現大文件
03:59 321G ~84MB/s 大文件段,速度拉滿
05:06 388G — 完成

結論:btrfs restore 的速度完全取決於文件大小分佈,不是盤慢。小文件多的時候 1~5MB/s,全是大文件能跑到 80~100MB/s。所以預估時間時要看你的數據構成——全是照片和文檔的話,做好跑一晚上的心理準備。

日誌裏還會大量出現:

offset is 4096
offset is 8192
offset is 65536
Skipping snapshot 65e18cc66456f60b2e20137bfbe740371fc629bfff6d89aeafdd3ecb81de1649

這兩個都不是錯誤:

  • offset is N 是它處理每個 extent 的流水信息;
  • Skipping snapshot ... 是 -S 模式下跳過群暉的快照(快照是舊數據的副本,你只要當前數據)。

真正要擔心的是 ERROR / Couldn't read tree root / can't read superblock。

八、第四步:覈對與權限

1. 對賬

# 源端真實佔用量
btrfs inspect-internal dump-super -f /dev/mapper/vg1-volume_1 \
  | awk '/^bytes_used/{print $2; exit}'
# 372.5 GiB 級別 → 399898472448

# 目標端
du -sb /vol1/1000/<user>/syno_restore
df -h /vol1

我這裏:du ≈ 377G,源端 bytes_used ≈ 372.5 GiB,基本一致;df 顯示 389G,多出來的是 Btrfs 自身的元數據開銷,屬於正常。

2. 目錄結構

拷完的目錄長這樣:

syno_restore/
└── @syno/                 ← 群暉的"卷根",共享文件夾都在這裏面
    ├── photo/  music/  video/  docker/  homes/  chat/ ...
    ├── @docker/ @database/ @appconf/ @appdata/ ...   ← DSM 內部數據
    └── @eaDir/            ← 縮略圖緩存,可以刪

@syno 是群暉的隱藏卷根,@ 開頭的是 DSM 自己的;真正的用戶數據在 @syno/photo、@syno/music 這些目錄裏。

3. 權限(這一步不做,飛牛界面裏進不去)

btrfs restore -m 會原樣保留群暉的權限,而群暉給 @syno 掛的是 0000:

d--------- 1 root root  @syno

飛牛的用戶是 uid=1000,所以必須改:

chown -R 1000:1000 /vol1/1000/<user>/syno_restore
chmod -R u+rwX   /vol1/1000/<user>/syno_restore

-R 在幾十萬文件上會跑十幾到幾十分鐘,屬正常。改完飛牛的文件管理、SMB 就能正常瀏覽了。

九、踩坑清單

  1. mount 失敗先看 dmesg,mount 自己的報錯永遠是一句”wrong fs type”,沒信息量。
  2. 兩個 mirror 報同樣的錯 = 大概率不是壞盤,是軟件層面的判定。
  3. noload 是 ext4 的,btrfs 用 norecovery。
  4. 網上教程裏的 mount ... > /目標,> 是筆誤。
  5. btrfs restore 的 -m 和 -o 都要加,前者保權限,後者保斷點續跑。
  6. -x 不要加,群暉 xattr 會刷屏。
  7. 必須 sudo,否則 nohup 連日誌都寫不出來,命令直接失敗。
  8. 要 setsid,否則 SSH 一斷前功盡棄;用 ps 確認 PPID=1。
  9. 別用 btrfs check --repair,它是寫操作,能把能救的卷改死。
  10. 別在飛牛界面把群暉的盤”加入存儲空間”,那是格式化。
  11. 群暉盤不要讀寫掛載,插回群暉會出問題;只讀 + btrfs restore 纔是無損。
  12. @eaDir 是縮略圖,佔空間又不重要,遷移完可以刪。

十、遷移之後:要不要把群暉盤徹底轉成飛牛存儲空間

兩條路,看你急不急:

A. 保守路線(我選的)

  • 只讀讀出數據 → 拷到飛牛自己建的存儲空間(另一塊盤)
  • 群暉那組盤原樣收起來,將來想回群暉,插回去就能開機
  • 代價:多佔一塊盤的容量

B. 徹底轉換

  • 先按 A 把數據拷出來並校驗
  • 確認無誤後,在飛牛裏把這塊盤格成自己的存儲空間
  • 但沒有第二塊盤的話,中間沒有任何冗餘,一旦出錯就是雙份丟失

另外要明白一點:飛牛的”存儲空間”是它自己的 Btrfs + mdadm 佈局,沒法直接”導入”群暉的 LVM/Btrfs 卷。所謂”掛載群暉盤到飛牛存儲空間”,實際只能是”掛載成普通目錄”或”拷進飛牛的空間”,沒有第三種。

十一、命令速查

# ---- 1. 認清結構 ----
cat /proc/mdstat
mdadm --assemble --scan
pvs; vgs; lvs -a -o +segtype,devices
vgchange -ay vg1
ls -l /dev/mapper/

# ---- 2. 確認失敗原因(只讀)----
mount -o ro /dev/mapper/vg1-volume_1 /mnt/x
dmesg | tail -30
blkid /dev/mapper/vg1-volume_1
btrfs inspect-internal dump-super -f /dev/mapper/vg1-volume_1 | head -30

# ---- 3. 只讀搶救 ----
setsid nohup btrfs restore -i -m -S -o \
  /dev/mapper/vg1-volume_1 /目標目錄/syno_restore \
  > /目標目錄/restore.log 2>&1 &
ps -o pid,ppid,tty,etime,cmd -C btrfs

# ---- 4. 對賬 ----
btrfs inspect-internal dump-super -f /dev/mapper/vg1-volume_1 | awk '/^bytes_used/{print $2; exit}'
du -sb /目標目錄/syno_restore
df -h

# ---- 5. 修權限 ----
chown -R 1000:1000 /目標目錄/syno_restore
chmod -R u+rwX    /目標目錄/syno_restore

# ---- 6. 收尾,準備把盤還給群暉 ----
umount /mnt/x 2>/dev/null
vgchange -an vg1
mdadm --stop /dev/md2
poweroff   # 再拔盤

寫在最後

整個過程最大的教訓不是”命令怎麼敲”,而是先搞清楚失敗發生在哪一層:

  • mount 報錯 → 看 dmesg;
  • dmesg 報 tree block corruption → 看是”真損壞”還是”校驗不認”;
  • 兩個 mirror 完全一致 + 錯誤信息裏出現了具體的 flag 值 → 基本可以判定是軟件判定問題,不是數據問題。

一旦確認數據是好的、只是內核不認,思路就從”修文件系統”轉成”繞開內核把它讀出來”——btrfs restore 就是幹這個的。

最後提醒一句:任何”修復”類命令(--repair、zero-log、super-recover)都是寫操作。在數據還沒拷出來之前,唯一正確的動作是”只讀”。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注