在硬碟數量有限的情況下把 ZFS 從 Mirror 轉移到 RAID-Z2

去年年底的時候發現家裡 NAS 的硬碟空間又要不足了,於是打算來添加新的硬碟來擴展空間。

但在預算不足的情況下,希望能直接從原先兩顆硬碟的 ZFS Mirror 轉移成四顆硬碟的 RAID-Z2(不知道 RAID-Z2 的同學可以直接想成是 RAID 6 就行),但對熟悉 ZFS 的朋友們應該早就知道這是癡人說夢了,ZFS 除了 Stripe 跟 Mirror 以外就沒有可以直接互相轉換的方法了。

常見的做法應該是先增加四顆硬碟組成 RAID-Z2 後,直接把舊硬碟的資料倒過去新 Dataset 上,但這對於預算不足的我來說實在有點困難,而且資料轉移後會讓原先的兩顆硬碟變成冗員也沒有辦法加回新建好的 Dataset 中。

於是我想了一個方法可以讓我在只添加兩顆硬碟的情況下轉移資料到 RAID-Z2 ——

!!警告!! 這個方法對資料來說是很危險的,有可能會讓你的資料在不知不覺中丟失!

背景知識

首先,先來簡單地說說 Mirror 及 RAID-Z2 是什麼東西。

如果對 ZFS 不熟悉的同學們,可以想像 Mirror 跟 RAID-Z2 其實就是 RAID 1 與 RAID 6 的存在,至於 RAID 1/6 是什麼東西呢?

RAID 1 (Mirror)

RAID 1 其實就是將其下的硬碟做為互相的備份,所有的硬碟上的資料都會是完全相同的,如此一來任何硬碟因故離線都不會對資料造成影響,但缺點就是無論你使用多少硬碟組成 RAID 1,你可以使用的空間永遠跟一顆硬碟的使用一樣。

RAID 5 (RAID-Z)

而 RAID 6 呢?在解釋 RAID 6 之前先來談談 RAID 5 吧,以下圖所示,RAID 5 會將資料平均分散的放置在其下的硬碟中,並且會輪流地在每一顆硬碟中插入對應資料的奇偶校驗碼(Np1作為對資料的驗證及保全,當有任何一顆硬碟掉線而處於不可用狀態時,消失的資料可以透過剩餘之資料及校驗碼來回推及重建:

RAID 5

故此,RAID 5 可以在有一顆硬碟掉線的情況下維持其運作正常。

RAID 6 (RAID-Z2)

而 RAID 6 是 RAID 5 的進階版本,在 RAID 5 的奇偶校驗碼上又再加入另一種校驗碼(Nq),如此一來就能確保在有兩顆硬碟掉線的情況下維持運作,如下圖所示:

RAID 6

至於 RAID 5/6 能夠使用的硬碟空間分別為(N - 1)(N - 2)

如果懂 RAID 的同學可能會問那既然已經有 RAID 1 了為什麼不直接改成 RAID 10 就好了?

一來是因為 RAID 10 中如果離線的硬碟剛好屬於同一個 RAID 1 裡面的話,這些資料就會因為運氣不好而完全消失了,RAID 6 巧妙的避開了這個問題,任意兩顆硬碟離線都還能確保資料的完整性。但 RAID 10 還是很常見的模式,因為 RAID 6 必須同時維護兩種校驗碼的關係,對於運算資源的要求相較於 RAID 10 要來的高上許多。

二來其實是因為 OpenZFS Developer Summit 20172 中有人提出了對於擴張 RAID-Z 的思路3,讓 RAID-Z 可以透過添加硬碟來擴張,這樣以後我想要再添加硬碟進去的話就變得是有可能的了(雖然不知道還要多久才會實做就是了……)。

思路

既然瞭解了 RAID 6 是怎麼運作了之後,我們就可以來想一下如何把兩顆 Mirror(RAID 1)硬碟透過添加兩顆新硬碟的方式來組成 RAID-Z2(RAID 6)了。

接下來要提出的解決方案其實對資料的完整性來說其實是非常的危險的,所以我希望在操作的同時可以保留兩份完整資料的備份,不過 Mirror 兩顆硬碟時就是同時有兩份完整資料了,所以我們就從此為起點來思考如何操作吧!

  1. 先假定原先的硬碟為 sdasdb,其組成一個 ZFS Mirror,暫時稱為 old_pool
  2. 在 NAS 上插入新的兩顆硬碟 sdcsdd,並且建立兩個號稱與硬碟一樣大的 loop device loop0loop1,並組成一個 RAID-Z2 new_pool
  3. 離線 new_pool 中的 loop0loop1
  4. 拷貝 old_pool 中的資料進 new_pool
  5. old_pool 拆解成 Stripe,將 sda 移出 Mirror
  6. sda 格式化後加入 new_pool 以取代離線中的 loop0
  7. 等待重建完畢
  8. old_pool 摧毀,並將 sdb 格式化後加入 new_pool 以取代離線中的 loop1
  9. 等待重建完畢即完成轉移

依照上面的步驟的話,在第 4 步時,資料會變成三份(old_pool 中有兩份、new_pool 有一份),因此在將 sda 格式化時還會有兩份資料(sdbnew_pool),這是滿足前面定下的條件的。

那麼在 sdb 格式化後不就只剩下一份資料了嗎?!
但是大家可以想成既然 RAID-Z2 可以在掉線兩顆硬碟的時候還是能在及格邊緣運作的話,只掉一顆的 RAID-Z2 相比之下還能有再掉一顆的餘力,儘管真的掉了也還來得及重建,就想像成那個狀態的 new_pool 有兩份備份吧!(迷:簡直亂來)

好的!在大家接受上面的說法之後(?),我們可以開始動手了!

開始施工

如果熟悉 ZFS 操作的同學應該在理解了上面步驟的危險性跟過程之後,應該就可以自己動手做了,不過還是稍微列一下實際的步驟吧。

由於我的 NAS 是採用 Ubuntu 建置的,所以以下範例都是以 Ubuntu 為準,使用其他系統的大家要稍微注意這點。

建立新 RAID-Z2 zpool

首先在把新硬碟插上去之後,建立 new_pool

$ cd /tmp
$ dd if=/dev/zero of=fake1.img bs=1 count=0 seek=4T
$ dd if=/dev/zero of=fake2.img bs=1 count=0 seek=4T
$ sudo losetup /dev/loop0 /tmp/fake1.img
$ sudo losetup /dev/loop1 /tmp/fake2.img
$ sudo zpool create -o ashift=12 new_pool raidz2 /dev/loop0 /dev/loop1 /dev/sdc /dev/sdd
$ sudo zpool offline new_pool /dev/loop0
$ sudo zpool offline new_pool /dev/loop1
$ sudo losetup -d /dev/loop0
$ sudo losetup -d /dev/loop1
$ rm fake1.img fake2.img

建立 loop device 時要注意 dd 出來的映像檔我們並不會實際作為寫入用途(其實也沒有空間放與硬碟一樣大的映像檔),所以我們會把 count 設為 0 並且指定 seek 為硬碟大小(本例中為 4T)。

使用較新的 Ubuntu(18.04+)時,預設會啓用 snaps4,這會導致系統會預設掛載了幾個 loop device,可以先使用 losetup 來查看有哪些 loop 已經被使用了,注意不要重複使用已經被用掉的編號了。

拷貝資料

熟悉 ZFS 操作的同學可能會直接使用 zfs send 配上 zfs receive 來拷貝 dataset,不過我這邊打算順便重新規劃 dataset 的配置,所以採用的是 rsync,這個步驟基本上就看同學想要怎麼使用了,是一個不需要怎麼說明的步驟。

# After dataset creation
$ sudo rsync -av /old_pool /new_pool

複製結束後檢查一下兩邊資料是否相同即可進行下一步。

拆解 Mirror

$ sudo zpool detach old_pool /dev/sda
$ sudo parted /dev/sda mklabel gpt
$ sudo zpool replace new_pool /dev/loop0 /dev/sda

接下來就等待 new_pool 的 resilver 結束後,再將剩下的硬碟加入。

加入最後的硬碟

待 resilver 結束後,我們要把最後一顆硬碟加入新 zpool 裡了:

$ sudo zpool destroy old_pool
$ sudo parted /dev/sdb mklabel gpt
$ sudo zpool replace new_pool /dev/loop1 /dev/sdb

等待 resilver 結束後,就完成這次的手術了!

後記

這次介紹的方法其實對資料是很危險的,如果當中有任何一份資料沒有被正確的拷貝進新 zpool 時,這個資料就會隨著舊 zpool 被摧毀的同時消失了……

而且為了不要在資料拷貝進新 zpool 之後面臨 RAID-Z2 掉硬碟造成資料遺失的情況,所以採取先拆解 Mirror 並只加入一顆硬碟做 resilver 的方式,批次進行以確保資料不會不小心在硬碟意外離線時消失。

總之,這樣操作的方式其實是很危險的,但是在預算不足的情況下拿寶貴資料來賭一把,只能儘量確保資料的完整了……
如果你們當中有人對這樣需要拿資料來賭博的我感到憐憫並且想施以幫助的話,這裡是我的捐款帳號……

參考資料

《ZFS - ArchWiki》wiki.archlinux.org/index.php/ZFS
《losetup 用法》man.linuxde.net/losetup
《ZFS / RAIDZ Capacity Calculator》wintelguy.com/zfs-calc.pl


  1. 相關資訊請見 《奇偶校驗碼 - 維基百科,自由的百科全書》 ↩︎
  2. 有關於 OpenZFS Developer Summit 2017 的介紹及議程記錄請見 OpenZFS 的 Wiki 頁面 ↩︎
  3. 有關該講題的投影片請見這裡 ↩︎
  4. 由 Canonical 所提出之新一代應用程式散佈解決方案,詳情請參與其官方網站 Snapcraft ↩︎
因主題更新,留言功能暫時停用中。