raidz2 轉移心得

為什麼轉移

目前我的 Gentoo server 上是 3 顆 4T SSHD (sda1, sdb1, sdc1), 合成一個 raidz2 pool. 現在要再加上一個 disk, 才發現... raidz, raiz2, raidz3 目前沒法按需增加 disk, 所以, 原本打算一個一個 disk 加上的做法是行不通的, 只能重做 pool 後將資料轉移上去了~

但問題來了, 目前只有一個空 disk, 而目前 raidz2 線上的 disk 是 3 個, 怎麼辨?

要回答這個問題, 要先瞭解 raidz2 提供了兩份 redundant, 在建立 pool 時要求至少要指定 3 個 disk 才能建立, 這點是確定的. 但因為 disk 可能會有出問題的時候, 這時候 disk 就會 offline (在 zfs 中記錄的 disk 總數不變), 然後需要 replace, 但, 不能被 remove or detach (從 zfs 記錄中減少 disk 總數).

因此, 3 個 disk 有兩份 redundant, 所以容量只有一個 disk 的空間, 要維持 raidz2 的運行, 最少只需要一個 disk 即可. 其他兩個都可以 offline. 但就算如此, 還是少一個 disk.

zfs 中之所以稱組成 pool 的 storage 為 vdev, 就是因為這些 device 不一定要是真的 disk, 用建立在其他 file system 之上的 file 也是可以的. 所以, 我們可以在建立新 pool 時, 先用 sparse files 充當 disks, 滿足 zfs 的要求, 然後再將這些 files 給 offline & 用 disk 去 replace.

操作步驟

建立新 pool

  1. 將 sdc1 offline, 指令為
    zpool offline poolname sdc3
    雖然我們也可以一併把例如 sdb3 offline, pool 中只留一個盤 (3 個盤, 但在轉移過程中其中一個盤出問題時就沒有安全保帳了.
  2. 查詢 sdc3 or sdd3 的大小, 指令為
    lsblk -b
    假設大小都皆為 4T
  3. 因為沒有多餘的 disk, 所以建立 sparse file 替代 sda3, sdb3. 指令為
    $ truncate -s 4T /root/fake-sda3 && truncate -s 4T /root/fake-sdb3
    這樣, 會建立 2 個大小為 4T 的 sparse file
  4. 建立新的 zpool, 將 sdc3, sdd3, /root/fake-sd[ab]3 加入
    $ zpool create newpool raidz2 sdc3 sdd3 /root/fake-sda3 /root/fake-sdb3 -o ashift=12 -O mountpoint=none -O compression=lz4 -R /root/newpool
    -o ashift=12 設定 zpool block size. 跟 disk sector size 相同會有 performance 最佳. 例如 disk sector size 為 4096, ashift=12 (2^12 = 4096). 如果為 512, ashift=9 (2^9 = 512)
    -O mountpoint 將根 dataset 的 mountpoint 設為 none. 這樣也讓新建的 dataset 的 mountpoint 都會被設為 none
    -O compression=lz4 開啟 lz4 壓縮
    -R /root/newpool 指定 newpool 的相對 root. 這樣, newpool 中指定了 mountpoint

轉移資料

  1. 對舊 pool 做個 snapshot
    zfs snap -r [email protected]
  2. 使用 zfs send 備份利器進行資料同步
    zfs send -R [email protected] | zfs receive -d newpool
    -R 代表將指定 dataset 的資料加上 properties 一併發送
    -d 去掉 dataset 最前面的部份, 例如 oldpool/root/debian 會成為 root/debian
  3. 此時因為是對 read only 的 snapshot 做 send, 所以 server 可接著正常服務
  4. 完成上一步後, 再做最後一次資料同步. 此時資料量應該是少得多, 所以等待時間應該相對短. 先將 server 上會寫入資料的服務停下來, 例如進到 rescue mode
    systemctl isolate rescue.target
  5. 再建個 snapshot
    zfs snap -r [email protected]
  6. 進行增量轉移
    zfs send -I [email protected] [email protected] | zfs receive -dF newpool
    -I 告知 zfs 只發送 [email protected] [email protected] 間差異的部份
    -F 知知 zfs 接收時如果 dataset 有變更的話自動 rollback

用新 pool 開機

現在新 pool 上有資料了, 接下來想法子用新 pool 開機. 因為目前 grub 沒法從 raidz/raidz2/raidz3 開機, 所以要將 /boot 獨立出來的. 又因為系統有 UEFI, 所以還需要一個 fat3 的 partition 用做放置 /boot/efi, 以 sda 為例, prtition 切分如下

  • sda1: 128M, fat32, /boot/efi, 開啟 grub_bios flag
  • sda2: 256M, ext4, /boot, 開啟 boot flag
  • sda3: sda 剩餘空間, zfs

先前我們將新 pool 放在了 /root/newpool 中, 準備 chroot 進去

$ cd /root/newpool
$ mount /dev/sda2 boot
$ mount /dev/sda1 boot/efi
$ mount -R /proc proc
$ mount -R /dev dev
$ mount -R /sys sys
$ chroot .

更新 pool cache

$ rm /etc/zfs/zpool.cache
$ zpool set cachefile=/etc/zfs/zpool.cache newpool

更新 initramfs

$ genkernel --zfs initramfs

安裝 grub 並產生設定檔

$ grub2-install /dev/sda
$ grub2-mkconfig -o /boot/grub/grub.cfg
$ sync & sync
$ exit

將舊 pool 中的 fs 的 canmount 設為 noauto 以免重啟後用到舊 pool, 然後, 重啟.

如果重啟的時候 pool import 不進來, 可能的問題有

  • grub 的 bug: 產生的 kernel command line 不完整, 例如你開機用的 zfs 為 newpool/gentoo, grub 會產生 root=ZFS=/gentoo, 前面少了 pool name. 暫時的解法是, 在開機時 grub 的選單上按 e, 編輯 kernel command line 改為 root=ZFS=newpool/gentoo. 成功開機後, 編輯 /etc/default/grub 在變量 GRUB_CMDLINE_LINUX 中加上 root=ZFS=newpool/gentoo 再執行 'grub2-mkconfig -o /boot/grub/grub.cfg` 更新 gurb 設定即可.
  • host id 不 match: 解法是在進到 initramfs 的 rescue 環境中, 配合 -f 強制 import pool (但不用做 mount 動作, 例如 zpool import -fN newpool) 再按 ctrl+d 或執行 exit 讓開機流程接下去即可.

將 sda3 & sdb3 加到新 pool 中

成功開機後, 確認

  1. 沒用上舊 pool
  2. 新 pool 中的資料是正確可用的

那就可以將舊 pool 刪了

$ zpool destroy oldpool

然後將 sda3, sdb3 加進新 pool 中 replace 掉 sparse file

$ zpool replace newpool /root/fake-sda3 sda3
$ zpool replace newpool /root/fake-sdb3 sdb3

這樣 zfs 就會在 background 對 sda3, sdb3 進行 resilvering, 時間很長, 不快, 但系統這時候是能提供服務的. 以我為例, 資料量 3.18T (compression=lz4) 花了 18 小時又 44 分鐘, 相當於一秒不到 50MB/s, 但過程比起 mdadm + lvm2 來得安全簡單.