將 ZFS 儲存池從 RAIDZ1 遷移至 RAIDZ2
我最近升級了家中的 TrueNAS 伺服器,並將 18 TB 的資料從一個 4 顆磁碟的 RAIDZ1 ZFS 儲存池遷移至新的 RAIDZ2 儲存池。
最巧妙的地方在於,我只用了額外三顆 8 TB 的磁碟就完成遷移,而且過程中完全沒有將資料轉移到外部儲存裝置。
要在不將資料搬移至外部儲存裝置的情況下,從 RAIDZ1 升級到 RAIDZ2 並不容易,原因如下:
- 你無法直接在原地將 RAIDZ1 儲存池轉換為 RAIDZ2 儲存池。
- 你無法將 ZFS 儲存池縮減為更少的磁碟數量。
- 你無法將 18 TB 的資料塞進由三顆 8 TB 磁碟組成的 RAIDZ2 儲存池(也就是那三顆新磁碟)。
我是怎麼做的
步驟 0:初始狀態
一開始,我擁有一個 4x8TB 的 RAIDZ1 ZFS 儲存池,已使用了其 23 TB 容量中的 18 TB。為了這次遷移,我購買了三顆整修過的 8 TB 磁碟。
步驟 1:借用一顆磁碟來建立 RAIDZ2 儲存池
首先,我從原本的 RAIDZ1 儲存池中移除一顆磁碟,使其處於降級狀態。
接著,我使用以下磁碟建立一個新的 5x8TB RAIDZ2 儲存池:
- 我的三顆新磁碟
- 來自 RAIDZ1 儲存池的一顆磁碟
- 一個 8 TB 的稀疏檔案,用來充當假磁碟
這個稀疏檔案位於我的 /tmp 目錄中,儘管該處的檔案系統實際上無法儲存 8 TB 的資料。這沒關係,因為這個稀疏檔案只是暫時的。這是一個取巧的方法,讓我得以建立一個 5 顆磁碟的儲存池,但我不會往裡面寫入任何資料。
步驟 2:將假磁碟離線
接下來,我將 RAIDZ2 儲存池中的假磁碟(也就是那個 8 TB 的稀疏檔案)設為離線。
將假磁碟離線後,儲存池仍然保持健康狀態,因為 RAIDZ2 在缺少兩顆磁碟的情況下仍可正常運作。
步驟 3:將 RAIDZ1 儲存池的快照遷移至 RAIDZ2 儲存池
我為 RAIDZ1 儲存池中的所有資料集建立快照,並使用 zfs send 將快照遷移至新的 RAIDZ2 儲存池。
步驟 4:刪除舊儲存池
在確認已成功將資料遷移至 RAIDZ2 儲存池後,我便刪除舊的 RAIDZ1 儲存池,這讓我多出了三顆可用磁碟。
步驟 5:用舊磁碟替換假磁碟
我使用來自舊 RAIDZ1 儲存池的一顆磁碟來替換 RAIDZ2 儲存池中的假磁碟,使其不再缺少磁碟。
步驟 6:用舊磁碟擴充新儲存池
最後,我使用 ZFS 擴充功能將舊 RAIDZ1 儲存池中剩下的兩顆磁碟加入新的 RAIDZ2 儲存池,最終在一個健康的 7x8TB RAIDZ2 儲存池上獲得總計 33 TB 的可用儲存空間。
更新:更安全的策略
讀者們提出了一個我認為比原本策略更佳的變化版本:
- 使用三顆新磁碟與兩個假磁碟(稀疏檔案)建立一個 5x8TB 的 RAIDZ2 儲存池。
- 將 RAIDZ2 儲存池中的假磁碟設為離線。
- 將資料從舊的 RAIDZ1 儲存池遷移至新的 RAIDZ2 儲存池。
- 將 RAIDZ1 儲存池中的一顆磁碟離線,並用它來替換 RAIDZ2 儲存池上的一個假磁碟。
- 刪除 RAIDZ1 儲存池。
- 將已刪除的 RAIDZ1 儲存池中剩下的三顆磁碟遷移至 RAIDZ2 儲存池。
我喜歡這個策略,因為它在整個遷移過程中都能承受任何單一磁碟故障。在我原本的流程中,雖然 RAIDZ2 儲存池中的任何磁碟故障都不會影響遷移,但若在遷移期間 RAIDZ1 儲存池中的任何磁碟損壞,整個儲存池就會故障。
這個新策略唯一的缺點是,我不像原本的策略那樣實際測試過它。
為什麼要從 RAIDZ1 換到 RAIDZ2?
我於 2022 年用一個 4x8TB 的 RAIDZ1 儲存池建置了我的家用 TrueNAS 伺服器。當時我對 RAIDZ1 感到安心,因為它可以容忍一顆磁碟故障,而且我認為四顆磁碟中同時有兩顆故障的機率很低。
我當時沒考慮到的是,每當我向儲存池新增磁碟時,資料遺失的機率就會隨之增加。我不擔心四顆磁碟中有兩顆同時故障,但如果我有六顆或八顆磁碟,同時故障的可能性就開始讓人擔憂了。
每向 RAIDZ1 儲存池新增一顆磁碟,都會讓日後遷移至 RAIDZ2 變得更加困難,因為這意味著新的儲存池必須更大,而可供新儲存池使用的實體磁碟插槽卻更少了。
RAIDZ2 可以在不遺失資料的情況下容忍兩顆磁碟同時故障。即使我擴充到 10 顆磁碟(目前伺服器機殼的最大容量),同時有三顆磁碟故障的機率低到讓我無需擔心。
為什麼從 RAIDZ1 切換到 RAIDZ2 很困難?
ZFS 不支援從 RAIDZ1 切換到 RAIDZ2。RAIDZ 模式在建立儲存池時就已決定,之後便無法變更。
最直覺的解法是多買五顆磁碟,建立一個 RAIDZ2 儲存池,然後將所有資料搬過去。這會讓我最終擁有 4 顆原有磁碟加上 5 顆新磁碟,共 9 顆磁碟,而我原本只想要 6 到 7 顆。
如果你在網路上搜尋如何將 RAIDZ1 儲存池轉換為 RAIDZ2,大家都會告訴你要用上述的直覺方法,或是將所有資料先搬到一台備用的 ZFS 伺服器上,刪除 RAIDZ1 儲存池,在原地建立一個 RAIDZ2 儲存池,再將資料遷移回去。但如果你跟我一樣,手邊並沒有一台閒置的 18 TB ZFS 伺服器隨意放在某艘遊艇上呢?
之所以沒有太多關於更有效率地從 RAIDZ1 切換到 RAIDZ2 的指引,是因為我的解法依賴於RAIDZ expansion(RAIDZ 擴充),這項功能直到六個月前才在 ZFS 中推出。
從 RAIDZ1 遷移至 RAIDZ2:實作過程
理論上,我的 RAIDZ1 到 RAIDZ2 遷移計畫很單純,但在實際執行時卻遇到了一些小插曲。
我在此分享詳細記錄了確切的指令與輸出,供任何想重現此流程的人參考。
步驟 1:使用隨身碟進行演練遷移
我的遷移計畫包含一些高風險的操作。在將其套用於存放所有資料的主要 TrueNAS 伺服器之前,我先在一台測試伺服器上試驗過。
我在舊的迷你電腦上安裝了 TrueNAS Core 25.04,然後接上了 7 支 USB 隨身碟。

在一台備用的 TrueNAS 伺服器和七支 USB 隨身碟上測試我的遷移策略
這並非完美的實驗,因為這些隨身碟的大小各不相同,但流程確實可行。這讓我有信心在正式環境的伺服器上嘗試遷移計畫。
步驟 2:備份我的資料
我在前面說過我從未將資料搬到外部儲存裝置,但嚴格來說並不完全正確。我確實有使用外部儲存裝置進行備份,只是最後沒有派上用場。
我已經使用restic 執行每夜備份,但那是在檔案系統層級,而非 ZFS 層級。換句話說,如果我在遷移過程中損毀了 ZFS 儲存池,就必須重新建立每個 ZFS 資料集,再從備份還原檔案,而無法透過支援 ZFS 的備份直接還原所有資料。
儘管我每天都會備份,仍有一類資料我不會備份:媒體檔案。我是個資料囤積者,所以我保留了所有 DVD 和藍光光碟的原始映像檔。因為誰知道哪天我會不會突然想看我那張 1998 年電影 There’s Something About Mary(《哈啦瑪莉》)DVD 中的導演講評呢?

我磁碟伺服器上的大部分空間都存放著我轉檔的 DVD 和藍光影片。
包含原始光碟映像檔和已編碼的影片檔,我在 TrueNAS 伺服器上約有 15 TB 的電影和影集。我沒有將這些檔案備份到雲端儲存空間,因為即使使用 Wasabi 或 Backblaze B2 這類低成本的雲端儲存供應商,每月也要花費約 90 美元。
我總是告訴自己,如果真的遺失了媒體資料,大不了再從原版光碟全部重新轉檔。但在這次遷移開始時,一想到要一片片重新轉檔並重新編碼超過 500 張光碟,我決定破例短期備份這些媒體檔案,以防遷移過程中出現任何問題。
哪裡可以備份 15 TB 的資料三天?
我只需要備份 15 TB。如果能找到一家按日或按小時計費的廠商,費用應該相當實惠。
我知道有兩家雲端廠商提供原生的 ZFS 備份服務:
- rsync.net:最低以一個月計費(15 TB 要價 150 美元)。
- zfs.rent:需要我將自己的硬碟寄給他們。
因此,這兩個選項都不適合只備份幾天的情境。
雖然有工具可以將 ZFS 備份到相容於 S3 的儲存空間,但我不想嘗試,因為感覺太複雜。如果沒有一台具備 15 TB 容量的備用伺服器,我也無法驗證備份是否真的有效。
我決定使用平常用的 restic 備份指令碼,將媒體檔案備份到一家以按日或更短時間為單位計費的雲端儲存廠商。以下是我找到的選項:
- Amazon S3(Standard):0.76 美元/TB/天
- Google Cloud Storage(Standard):0.66 美元/TB/天
- Cloudflare R2:0.50 美元/TB/天
- Backblaze B2:0.20 美元/TB/天
- Hetzner storage boxes:0.09 美元/TB/天
- 我很不幸地是在完成遷移後才發現這個選項。
我選擇了 Backblaze B2,不知為何,我那 15 TB 的資料在 Backblaze 那端變成了 24.6 TB。
我花了將近一週的時間才將所有資料上傳到 Backblaze,因此資料存放的時間比預期的還久,不過月底帳單只比平常多幾美元,所以我也不確定他們是如何計算費用的。
警告:請檢查你的雲端儲存廠商的最低保留期限政策
我最初在一場失敗的遷移嘗試中將資料備份到 Wasabi。好吧,這沒什麼,我只需要為幾天的備份支付約 30 美元給 Wasabi。結果 Wasabi 雖然支援按日計費,但你備份的任何資料都必須支付至少 90 天的費用。所以,原本 30 美元的 Wasabi 帳單,瞬間變成了 300 美元。
我寫信向 Wasabi 客服求情,他們卻給了我一個奇怪的建議:刪除你的帳號。
顯然,你可以透過刪除整個 Wasabi 帳號來規避其最低儲存期限要求。這不是什麼取巧或漏洞,而是 Wasabi 客服的官方指引。
不過,在我還沒進一步爭取的情況下,另一位 Wasabi 的客服人員加入了這張工單,告訴我不需要刪除帳號。作為一次性的通融,他們會退還我超額的費用(感謝 Wasabi!)。
步驟 3:更新 TrueNAS
這次遷移需要 ZFS 的RAIDZ expansion 功能,該功能包含在OpenZFS 2.3.0 以及 TrueNAS 的24.10(Electric Eel)版本中。
我從 TrueNAS 的 Shell 驗證我的 ZFS 版本:
root@truenas:~# zfs --version
zfs-2.3.0-1
zfs-kmod-2.3.0-1注意:更新時請檢查你的發行分支。
我不小心跳過了更新步驟,因為 TrueNAS 告訴我我已經是最新版本。結果發現,那只是在我的發行分支上的最新版本,而該分支預設仍停留在 24.x。所以,如果你沒看到 ZFS 2.3.0 或更新版本,可能需要更新 TrueNAS 並選擇較新的主要版本。
步驟 3:識別磁碟
為了開始這次遷移,我需要識別所有磁碟,以便在 ZFS 命令列工具中引用它們。
顯示磁碟最簡單的方法是使用 fdisk 工具:
fdisk --list我覺得透過 TrueNAS 中的 Storage > Disks 儀表板查看更為方便:
根據上方的螢幕截圖,我的磁碟 ID 如下:
- 現有儲存池中的磁碟:
sda、sdc、sde、sdf - 新磁碟:
sdb、sdg、sdh
步驟 4:找出最弱的磁碟
遷移過程中風險最高的階段,是從我從舊的 RAIDZ1 儲存池借用一顆磁碟到將資料遷移至新的 RAIDZ2 儲存池之間的這段時間。從 RAIDZ1 儲存池中移除一顆磁碟會使其進入降級狀態。如果在資料遷移期間舊儲存池中的其他任何磁碟損壞,我就會遺失資料。
由於這段空窗期風險很高,我希望從舊儲存池借來建立新儲存池的那顆磁碟是其中最弱的一顆。RAIDZ2 儲存池可以容忍這顆弱磁碟故障,但 RAIDZ1 儲存池不行。
為了找出最弱的磁碟,我執行了以下快速的 bash 指令碼來查詢磁碟的SMART 診斷資料:
for drive in /dev/sd?; do
[ -e "$drive" ] && echo -e "\n=== $drive ===" && smartctl -A $drive | \
grep -E '(Power_On_Hours|Wear_Leveling|Media_Wearout|Reallocated_Sector)'
done=== /dev/sda ===
5 Reallocated_Sector_Ct 0x0033 100 100 050 Pre-fail Always - 0
9 Power_On_Hours 0x0032 032 032 000 Old_age Always - 27228
=== /dev/sdb ===
5 Reallocated_Sector_Ct 0x0033 100 100 005 Pre-fail Always - 0
9 Power_On_Hours 0x0012 100 100 000 Old_age Always - 423
=== /dev/sdc ===
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
9 Power_On_Hours 0x0032 077 077 000 Old_age Always - 20998
=== /dev/sdd ===
9 Power_On_Hours 0x0032 100 100 000 Old_age Always - 29606
=== /dev/sde ===
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
9 Power_On_Hours 0x0032 067 067 000 Old_age Always - 29599
=== /dev/sdf ===
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
9 Power_On_Hours 0x0032 067 067 000 Old_age Always - 29603
=== /dev/sdg ===
5 Reallocated_Sector_Ct 0x0033 100 100 005 Pre-fail Always - 0
9 Power_On_Hours 0x0012 100 100 000 Old_age Always - 141
=== /dev/sdh ===
5 Reallocated_Sector_Ct 0x0033 100 100 005 Pre-fail Always - 0
9 Power_On_Hours 0x0012 100 100 000 Old_age Always - 147根據這些結果,所有磁碟看起來都相當健康。唯一有差異的指標是通電時數:
- 現有磁碟
sda:032sdc:077sde:067sdf:067
- 新磁碟
sdb:100sdg:100sdh:100
新磁碟的健康度皆為 100%,這很合理,因為它們都是剛整修過的。sda 的數值最低,僅有 32%,因此我將它指定為最弱的磁碟,並優先將它移至新的儲存池。
步驟 5:建立穩定的磁碟識別碼
/dev/sdX 路徑在重新啟動後並不穩定。一顆在 /dev/sda 的磁碟,在下次重新啟動後可能會變成 /dev/sdf。我不確定 ZFS 是否會將磁碟解析為更穩定的識別碼,但我不想冒任何風險。
我撰寫了這個 bash 函式,將 /dev/sdX 路徑轉換為該磁碟的穩定識別碼:
get_disk_id() {
local dev=$1
local target="/dev/$dev"
for path in /dev/disk/by-id/*; do
if [ -L "$path" ] && [ "$(readlink -f "$path")" = "$target" ] &&
[[ "${path: -2:1}" != ":" ]]; then
echo "$path"
return 0
fi
done
echo "Disk ID not found for device: $dev" >&2
return 1
}要使用我的 get_disk_id bash 函式,我只要給它 sdX 識別碼,它就會回傳該磁碟的穩定路徑:
$ get_disk_id sda
/dev/disk/by-id/ata-HGST_HUS728T8TALE6L1_VGGGYUEG為了讓我的流程可重複使用,我將磁碟 ID 儲存至環境變數:
# Old disks
DISK_1="$(get_disk_id sda)"
DISK_2="$(get_disk_id sdc)"
DISK_3="$(get_disk_id sde)"
DISK_4="$(get_disk_id sdf)"我也為新磁碟建立環境變數:
# New disks
DISK_5="$(get_disk_id sdb)"
DISK_6="$(get_disk_id sdg)"
DISK_7="$(get_disk_id sdh)"我還建立了一個環境變數來指向現有的 RAIDZ1 儲存池:
OLDPOOL='pool1'步驟 6:將最弱的磁碟離線
在動手之前,我先確認舊儲存池是健康的,且磁碟符合預期:
$ sudo zpool status ${OLDPOOL}
pool: pool1
state: ONLINE
scan: scrub repaired 68K in 10:40:08 with 0 errors on Wed May 14 06:25:26 2025
config:
NAME STATE READ WRITE CKSUM
pool1 ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
sde2 ONLINE 0 0 0
sdf2 ONLINE 0 0 0
sdc2 ONLINE 0 0 0
sda2 ONLINE 0 0 0
errors: No known data errorssda2(DISK_1)是我想移至新 RAIDZ2 儲存池的最弱磁碟,因此我執行以下指令:
DISK_TO_OFFLINE='sda2'
sudo zpool offline "${OLDPOOL}" "${DISK_TO_OFFLINE}"檢查 zpool status 後,我看到將磁碟離線已使儲存池進入降級狀態,符合我的預期:
$ sudo zpool status ${OLDPOOL}
pool: pool1
state: DEGRADED
status: One or more devices has been taken offline by the administrator.
Sufficient replicas exist for the pool to continue functioning in a
degraded state.
action: Online the device using 'zpool online' or replace the device with
'zpool replace'.
scan: scrub repaired 68K in 10:40:08 with 0 errors on Wed May 14 06:25:26 2025
config:
NAME STATE READ WRITE CKSUM
pool1 DEGRADED 0 0 0
raidz1-0 DEGRADED 0 0 0
sde2 ONLINE 0 0 0
sdf2 ONLINE 0 0 0
sdc2 ONLINE 0 0 0
sda2 OFFLINE 0 0 0
errors: No known data errors步驟 7:清除最弱的磁碟
建立 RAIDZ2 儲存池時會遇到一個小障礙。如果我嘗試使用那顆最弱的磁碟(DISK_1)來建立新儲存池,ZFS 會告訴我它已屬於另一個儲存池。即使我已將該磁碟離線,ZFS 仍堅持認為我的 RAIDZ1 儲存池擁有該磁碟。
為了讓 RAIDZ1 儲存池不再宣稱擁有這顆最弱的磁碟,我需要將其完全清除。
我定義一個環境變數來追蹤我要搬移的磁碟:
MOVED_DISK="${DISK_1}"然後我清除磁碟上的所有資料:
wipefs 指令,因為它會在毫無確認的情況下清除整顆磁碟。# Be careful! This command completely wipes the disk with no confirmation.
wipefs --all "${MOVED_DISK}"步驟 8:使用稀疏檔案建立假磁碟
我可以建立一個 4x8TB 的 RAIDZ2 儲存池,但那樣只有 16 TB 的可用容量,不足以存放我的 18 TB 資料。
因此,我建立一個 5x8TB 的 RAIDZ2 儲存池,只不過其中第五顆磁碟並非真實存在。ZFS 允許我使用稀疏檔案作為磁碟來建立 ZFS 儲存池。
要用稀疏檔案建立假磁碟,我需要知道儲存池中其他磁碟的確切大小,這可以透過 fdisk 查詢:
$ fdisk --list | grep "^Disk.*bytes"
Disk /dev/sdf: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk /dev/sdd: 111.79 GiB, 120034123776 bytes, 234441648 sectors
Disk /dev/sda: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk /dev/sdc: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk /dev/sde: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk /dev/sdb: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk /dev/mapper/sdd3: 16 GiB, 17179869184 bytes, 33554432 sectors
Disk /dev/zd0: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk /dev/sdg: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk /dev/sdh: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors根據上述結果,我看到每顆真實磁碟都有 8001563222016 位元組,因此我使用 truncate 指令建立一個假磁碟:
FAKE_DISK='/tmp/fake-drive.img'
truncate --size 8001563222016 "${FAKE_DISK}"最後,我需要為新儲存池命名。我之前使用 pool1 這個名稱時,還不知道 ZFS 社群有將儲存池命名為 tank 的文化慣例。我想趁此機會跟上潮流,將新儲存池命名為 tank:
NEWPOOL='tank'可惜,我最終沒能保留 tank 這個名稱。TrueNAS 有許多相依於儲存池名稱的設定,所以我選擇讓名稱改回 pool1,而不是將所有共享和排程工作手動更新為指向 tank(詳見下文)。
步驟 9:建立 RAIDZ2 儲存池
終於,是時候建立我的 5x8TB RAIDZ2 儲存池了:
zpool create \
-f \
${NEWPOOL} \
raidz2 \
-m "/mnt/${NEWPOOL}" \
"${DISK_5}" \
"${DISK_6}" \
"${DISK_7}" \
"${MOVED_DISK}" \
"${FAKE_DISK}"建立儲存池成功,我可以用 ZFS 工具進行檢查:
$ zpool status "${NEWPOOL}"
pool: tank
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz2-0 ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VGGGYUEG ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VRGMRVJK ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VRGNZU9K ONLINE 0 0 0
ata-TOSHIBA_HDWG480_71R0A14YFR0H ONLINE 0 0 0
/tmp/fake-drive.img ONLINE 0 0 0
errors: No known data errors
$ zpool list "${NEWPOOL}"
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 36.4T 1.27M 36.4T - - 0% 0% 1.00x ONLINE -我也能在 TrueNAS 網頁介面中看到新的 RAIDZ2 儲存池,它顯示有 21.4 TiB(23.5 TB)的可用容量:

我的新 RAIDZ2 儲存池顯示於 TrueNAS 網頁介面中。
步驟 10:移除假磁碟
這個假磁碟只是我 /tmp 目錄中的一個檔案,它實際上無法儲存其所聲稱的 8 TB 資料。為了避免當假檔案耗盡 /tmp 檔案系統的空間時出現異常行為,我立即將假磁碟從 RAIDZ2 儲存池中移除:
zpool offline "${NEWPOOL}" "${FAKE_DISK}" && \
rm "${FAKE_DISK}"如預期般,ZFS 工具回報我的 RAIDZ2 儲存池處於降級狀態,但它最多可容忍兩顆磁碟故障,因此單一磁碟離線是沒問題的:
$ zpool status "${NEWPOOL}"
pool: tank
state: DEGRADED
status: One or more devices has been taken offline by the administrator.
Sufficient replicas exist for the pool to continue functioning in a
degraded state.
action: Online the device using 'zpool online' or replace the device with
'zpool replace'.
config:
NAME STATE READ WRITE CKSUM
tank DEGRADED 0 0 0
raidz2-0 DEGRADED 0 0 0
ata-HGST_HUS728T8TALE6L1_VGGGYUEG ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VRGMRVJK ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VRGNZU9K ONLINE 0 0 0
ata-TOSHIBA_HDWG480_71R0A14YFR0H ONLINE 0 0 0
/tmp/fake-drive.img OFFLINE 0 0 0
errors: No known data errors
$ zpool list "${NEWPOOL}"
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 36.4T 1.41M 36.4T - - 0% 0% 1.00x DEGRADED -步驟 11:將資料遷移至新儲存池
我的新 RAIDZ2 儲存池已上線,擁有 23.5 TB 的容量,因此我將 18 TB 的資料從舊的 RAIDZ1 儲存池搬移過去。
為了轉移所有資料,我為整個 RAIDZ1 儲存池建立快照,然後將快照從舊儲存池發送至新儲存池:
SNAPSHOT_1="migrate1"
zfs snapshot -r "${OLDPOOL}@${SNAPSHOT_1}" && \
zfs send --verbose --raw --replicate "${OLDPOOL}@${SNAPSHOT_1}" \
| zfs receive -v -F "${NEWPOOL}"奇怪的是,指令執行完成且未回報任何錯誤,但我發現 RAIDZ2 儲存池上缺少了一些資料集。
排解遷移問題
為了完成資料遷移,我逐一發送遺漏的資料集:
DATASET='photos'zfs send --verbose --raw --replicate --skip-missing \
"${OLDPOOL}/${DATASET}@${SNAPSHOT_1}" \
| zfs receive -v -F -s "${NEWPOOL}/${DATASET}"當我將儲存池掛載於 /mnt/tank/photos 時,該目錄仍顯示為空。
我嘗試執行 zpool export 和 zpool import,但沒有任何改變。
最後,我嘗試重新啟動整台 TrueNAS 伺服器,問題竟神奇地解決了。我可以在 /mnt/tank/photos 中看到我的檔案了。
續傳中斷的傳輸
從中斷大型傳輸的經驗中,我發現 ZFS 預設在發送資料時並不支援續傳。要續傳時,我必須透過取得「續傳權杖(resume token)」並將其包含在 zfs send 指令中的複雜步驟來完成:
RESUME_TOKEN="$(zfs get -H -o value receive_resume_token "${NEWPOOL}/${DATASET}")"
zfs send -v -t "${RESUME_TOKEN}" | zfs receive -v -s "${NEWPOOL}/${DATASET}"輸出聲稱已成功解析續傳權杖,但進度總是重置為零,因此我不確定這個指令是否真的有效。
完成遷移
由於遷移花費了數小時,我建立了一個新的快照,並對自上次快照以來變更的所有資料執行增量發送:
SNAPSHOT_2="migrate2"
zfs snapshot -r "${OLDPOOL}@${SNAPSHOT_2}"zfs send -v \
-i "${OLDPOOL}/${DATASET}@${SNAPSHOT_1}" \
"${OLDPOOL}/${DATASET}@${SNAPSHOT_2}" \
| zfs receive -v "${NEWPOOL}/${DATASET}"步驟 12:交換儲存池名稱
我原本以為會將新儲存池命名為 tank,但我發現 TrueNAS 的網路共享和排程工作都與儲存池名稱綁定。如果我將主要儲存池從 pool1 重新命名為 tank,就必須手動重新建立或編輯一堆網路共享和排程工作,將名稱更新為新名稱。
更簡單的解法是讓新儲存池接管舊儲存池的名稱,因此我選擇這麼做。
我一開始嘗試匯出儲存池以使其離線,但各種 TrueNAS 系統服務阻止我這麼做。因此,我強制停止了一堆服務:
sudo systemctl stop k3s
sudo umount -l /var/lib/kubelet
sudo systemctl stop smbd
sudo systemctl stop nmbd
sudo systemctl stop winbind
sudo systemctl stop middlewared
sudo systemctl stop netdata接著,我就可以將儲存池離線以執行重新命名:
# Rename my old pool with the suffix `-old`.
zpool export -f "${OLDPOOL}" && \
zpool export -f "${NEWPOOL}" && \
zpool import "${OLDPOOL}" "${OLDPOOL}-old"
# Rename my new pool to my old pool's name.
zpool import ${NEWPOOL} ${OLDPOOL}
zfs set mountpoint="/mnt/${OLDPOOL}" "${OLDPOOL}"之後,我重新啟動所有剛才停止的服務:
sudo systemctl start middlewared
sudo systemctl start smbd
sudo systemctl start nmbd
sudo systemctl start winbind
sudo systemctl start netdata
sudo systemctl start k3s但這樣沒有用,所以我再次重新啟動。
重新啟動後,TrueNAS 已將 pool1 識別為我的新 5x8TB RAIDZ2 儲存池:
步驟 13:對新的 RAIDZ2 儲存池執行 scrub
我不確定是否有必要,但為了在刪除舊儲存池前對資料完整性多一分信心,我在新儲存池上執行了一次 scrub。scrub 完成且沒有錯誤:
步驟 14:刪除舊儲存池
現在來到最可怕的部分:刪除舊儲存池。我使用 zpool destroy 將其徹底刪除:
zpool destroy "${OLDPOOL}-old"步驟 15:用真實磁碟替換假磁碟
隨著舊的 RAIDZ1 儲存池已被刪除,我現在有三顆磁碟可以遷移至新的 RAIDZ2 儲存池。
第一次的磁碟遷移需要特殊的流程,因為我要替換 RAIDZ2 儲存池上的假磁碟(8 TB 稀疏檔案)。
我使用 zpool replace 將假磁碟替換為真實磁碟:
FAKE_DISK='/tmp/fake-drive.img'
REPLACEMENT_DISK="$(get_disk_id sde)"
NEWPOOL='pool1'
zpool replace "${NEWPOOL}" "${FAKE_DISK}" "${REPLACEMENT_DISK}"檢查 zpool status 顯示 ZFS 正在對剛換入的磁碟進行 resilvering(重新同步):
$ zpool status "${NEWPOOL}"
pool: pool1
state: DEGRADED
status: One or more devices is currently being resilvered. The pool will
continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
scan: resilver in progress since Sat May 31 19:58:51 2025
2.04T / 28.5T scanned at 56.6G/s, 0B / 28.5T issued
0B resilvered, 0.00% done, no estimated completion time
config:
NAME STATE READ WRITE CKSUM
pool1 DEGRADED 0 0 0
raidz2-0 DEGRADED 0 0 0
ata-HGST_HUS728T8TALE6L1_VGGGYUEG ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VRGMRVJK ONLINE 0 0 0
ata-HGST_HUS728T8TALE6L1_VRGNZU9K ONLINE 0 0 0
ata-TOSHIBA_HDWG480_71R0A14YFR0H ONLINE 0 0 0
replacing-4 DEGRADED 0 0 0
/tmp/fake-drive.img OFFLINE 0 0 0
ata-ST8000VN004-2M2101_WSD5B9XY ONLINE 0 0 0
errors: No known data errorsTrueNAS 介面也顯示我正在替換磁碟:
步驟 15:吸收剩餘的兩顆磁碟
最後,是時候用舊的、已刪除的 RAIDZ1 儲存池中剩餘的兩顆磁碟來擴充新的 RAIDZ2 儲存池了:
我又遇到了一個阻礙。
$ NEWDISK=$(get_disk_id sdd)
$ zpool attach "${NEWPOOL}" raidz2-0 "${NEWDISK}"
cannot attach /dev/disk/by-id/ata-ST8000VN004-3CP101_WRQ02GX5 to raidz2-0: raidz_expansion feature must be enabled in order to attach a device to raidz顯然,RAIDZ expansion 功能預設是關閉的。也許是因為我從舊版 TrueNAS 升級上來才會如此:
$ zpool get feature@raidz_expansion "${NEWPOOL}"
NAME PROPERTY VALUE SOURCE
pool1 feature@raidz_expansion disabled local我使用 zpool upgrade 啟用 RAIDZ expansion 功能:
zpool upgrade -o feature@raidz_expansion=enabled "${NEWPOOL}"接著,我確認 RAIDZ expansion 功能已啟用:
$ zpool get feature@raidz_expansion "${NEWPOOL}"
NAME PROPERTY VALUE SOURCE
pool1 feature@raidz_expansion enabled local現在,我可以再次嘗試加入磁碟:
zpool attach "${NEWPOOL}" raidz2-0 "${NEWDISK}"而 zpool status 確認它正在加入磁碟:
$ zpool status "${NEWPOOL}" | grep --after-context=1 "expand:"
+ grep --after-context=1 expand:
+ zpool status "${NEWPOOL}"
expand: expansion of raidz2-0 in progress since Sun Jun 1 08:08:03 2025
17.1G / 28.5T copied at 501M/s, 0.06% done, 16:36:03 to go在 zpool status 顯示擴充進度達到 100% 後,我對最後一顆磁碟重複此流程,並達到最終狀態。
我完整的 7x8TB RAIDZ2 儲存池
隨著所有磁碟皆已遷移完成,我現在擁有一個健康、運作良好的 7x8TB RAIDZ2 儲存池,具備 33 TB(30 TiB)的可用容量:
選用步驟:強制資料重新條帶化
讀者 @intelfx 提醒我 RAIDZ expansion 功能中有一個陷阱。在 RAIDZ expansion 之前就存在於磁碟上的資料,不會自動從 3x 資料 + 2x 同位檢查重新條帶化為 5x 資料 + 2x 同位檢查,因此無法享受到 7 顆磁碟陣列提升的儲存效率。最終的結果就是你無法使用 RAIDZ2 儲存池上的所有容量。
你可以透過強制重寫遷移前的資料集來解決這個問題(例如,將每個資料集用 zfs send 備份,再用 zfs receive 還原)。OpenZFS 的貢獻者 Rob Norris(羅布·諾里斯) 指出,在即將推出的 OpenZFS 2.4.x 中有一個 zfs rewrite 指令可以更優雅地達成同樣的效果。
選用步驟:符合 TrueNAS 的功能設定
iXsystems 團隊(維護 TrueNAS 的團隊)的一名成員 在 Reddit 上留言表示,TrueNAS 技術上不支援使用者從命令列建立 ZFS 儲存池。他建議我在 TrueNAS 中建立一個測試儲存池,匯出其功能旗標,然後將這些旗標套用至我從命令列建立的新 RAIDZ2 儲存池。
我不清楚將 TrueNAS 的旗標套用至我從 CLI 建立的儲存池究竟有何差異,但以下是我認為 iXsystems 截至 25.04 所建議的旗標:
zpool set feature@lz4_compress=enabled "${NEWPOOL}"
zpool set feature@async_destroy=enabled "${NEWPOOL}"
zpool set feature@empty_bpobj=enabled "${NEWPOOL}"
zpool set feature@multi_vdev_crash_dump=enabled "${NEWPOOL}"
zpool set feature@spacemap_histogram=enabled "${NEWPOOL}"
zpool set feature@enabled_txg=enabled "${NEWPOOL}"
zpool set feature@hole_birth=enabled "${NEWPOOL}"
zpool set feature@extensible_dataset=enabled "${NEWPOOL}"
zpool set feature@embedded_data=enabled "${NEWPOOL}"
zpool set feature@bookmarks=enabled "${NEWPOOL}"
zpool set feature@filesystem_limits=enabled "${NEWPOOL}"
zpool set feature@large_blocks=enabled "${NEWPOOL}"
zpool set feature@large_dnode=enabled "${NEWPOOL}"
zpool set feature@sha512=enabled "${NEWPOOL}"
zpool set feature@skein=enabled "${NEWPOOL}"
zpool set feature@edonr=enabled "${NEWPOOL}"
zpool set feature@userobj_accounting=enabled "${NEWPOOL}"
zpool set feature@encryption=enabled "${NEWPOOL}"
zpool set feature@project_quota=enabled "${NEWPOOL}"
zpool set feature@device_removal=enabled "${NEWPOOL}"
zpool set feature@obsolete_counts=enabled "${NEWPOOL}"
zpool set feature@zpool_checkpoint=enabled "${NEWPOOL}"
zpool set feature@spacemap_v2=enabled "${NEWPOOL}"
zpool set feature@allocation_classes=enabled "${NEWPOOL}"
zpool set feature@resilver_defer=enabled "${NEWPOOL}"
zpool set feature@bookmark_v2=enabled "${NEWPOOL}"
zpool set feature@redaction_bookmarks=enabled "${NEWPOOL}"
zpool set feature@redacted_datasets=enabled "${NEWPOOL}"
zpool set feature@bookmark_written=enabled "${NEWPOOL}"
zpool set feature@log_spacemap=enabled "${NEWPOOL}"
zpool set feature@livelist=enabled "${NEWPOOL}"
zpool set feature@device_rebuild=enabled "${NEWPOOL}"
zpool set feature@zstd_compress=enabled "${NEWPOOL}"
zpool set feature@draid=enabled "${NEWPOOL}"
zpool set feature@zilsaxattr=enabled "${NEWPOOL}"
zpool set feature@head_errlog=enabled "${NEWPOOL}"
zpool set feature@blake3=enabled "${NEWPOOL}"
zpool set feature@block_cloning=enabled "${NEWPOOL}"
zpool set feature@vdev_zaps_v2=enabled "${NEWPOOL}"
zpool set feature@redaction_list_spill=enabled "${NEWPOOL}"
zpool set feature@raidz_expansion=enabled "${NEWPOOL}"
zpool set feature@fast_dedup=enabled "${NEWPOOL}"
zpool set feature@longname=enabled "${NEWPOOL}"
zpool set feature@large_microzap=enabled "${NEWPOOL}"感謝 TrueNAS 論壇上的 @NugentS 教我這個巧妙的稀疏檔案技巧來建立更大的 RAIDZ2 儲存池。感謝來自 iXsystems 的 Chris 更正了其中一些細節。
隨機一篇部落格





