19.4. zfs 管理

zfs 工具負責建立、摧毀與管理在一個儲存池中所有的 ZFS 資料集。儲存池使用 zpool 來管理。

19.4.1. 建立與摧毀資料集

不同於傳統的磁碟與磁碟區管理程式 (Volume manager) ,在 ZFS 中的空間並會預先分配。傳統的檔案系統在分割與分配空間完後,若沒有增加新的磁碟便無法再增加額外的檔案系統。在 ZFS,可以隨時建立新的檔案系統,每個資料集 (Dataset) 都有自己的屬性,包含壓縮 (Compression)、去重複 (Deduplication)、快取 (Caching) 與配額 (Quota) 功能以及其他有用的屬性如唯讀 (Readonly)、區分大小寫 (Case sensitivity)、網路檔案分享 (Network file sharing) 以及掛載點 (Mount point)。資料集可以存在於其他資料集中,且子資料集會繼承其父資料集的屬性。每個資料集都可以作為一個單位來管理、委託 (Delegate)、備份 (Replicate)、快照 (Snapshot)、監禁 (Jail) 與摧毀 (Destroy),替每種不同類型或集合的檔案建立各別的資料集還有許多的好處。唯一的缺點是在當有非常大數量的資料集時,部份指令例如 zfs list 會變的較緩慢,且掛載上百個或其至上千個資料集可能會使 FreeBSD 的開機程序變慢。

建立一個新資料集並開啟 LZ4 壓縮

# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
mypool                781M  93.2G   144K  none
mypool/ROOT           777M  93.2G   144K  none
mypool/ROOT/default   777M  93.2G   777M  /
mypool/tmp            176K  93.2G   176K  /tmp
mypool/usr            616K  93.2G   144K  /usr
mypool/usr/home       184K  93.2G   184K  /usr/home
mypool/usr/ports      144K  93.2G   144K  /usr/ports
mypool/usr/src        144K  93.2G   144K  /usr/src
mypool/var           1.20M  93.2G   608K  /var
mypool/var/crash      148K  93.2G   148K  /var/crash
mypool/var/log        178K  93.2G   178K  /var/log
mypool/var/mail       144K  93.2G   144K  /var/mail
mypool/var/tmp        152K  93.2G   152K  /var/tmp
# zfs create -o compress=lz4 mypool/usr/mydataset
# zfs list
NAME                   USED  AVAIL  REFER  MOUNTPOINT
mypool                 781M  93.2G   144K  none
mypool/ROOT            777M  93.2G   144K  none
mypool/ROOT/default    777M  93.2G   777M  /
mypool/tmp             176K  93.2G   176K  /tmp
mypool/usr             704K  93.2G   144K  /usr
mypool/usr/home        184K  93.2G   184K  /usr/home
mypool/usr/mydataset  87.5K  93.2G  87.5K  /usr/mydataset
mypool/usr/ports       144K  93.2G   144K  /usr/ports
mypool/usr/src         144K  93.2G   144K  /usr/src
mypool/var            1.20M  93.2G   610K  /var
mypool/var/crash       148K  93.2G   148K  /var/crash
mypool/var/log         178K  93.2G   178K  /var/log
mypool/var/mail        144K  93.2G   144K  /var/mail
mypool/var/tmp         152K  93.2G   152K  /var/tmp

摧毀資料集會比刪除所有在資料集上所殘留的檔案來的快,由於摧毀資料集並不會掃描所有檔案並更新所有相關的 Metadata。

摧毀先前建立的資料集:

# zfs list
NAME                   USED  AVAIL  REFER  MOUNTPOINT
mypool                 880M  93.1G   144K  none
mypool/ROOT            777M  93.1G   144K  none
mypool/ROOT/default    777M  93.1G   777M  /
mypool/tmp             176K  93.1G   176K  /tmp
mypool/usr             101M  93.1G   144K  /usr
mypool/usr/home        184K  93.1G   184K  /usr/home
mypool/usr/mydataset   100M  93.1G   100M  /usr/mydataset
mypool/usr/ports       144K  93.1G   144K  /usr/ports
mypool/usr/src         144K  93.1G   144K  /usr/src
mypool/var            1.20M  93.1G   610K  /var
mypool/var/crash       148K  93.1G   148K  /var/crash
mypool/var/log         178K  93.1G   178K  /var/log
mypool/var/mail        144K  93.1G   144K  /var/mail
mypool/var/tmp         152K  93.1G   152K  /var/tmp
# zfs destroy mypool/usr/mydataset
# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
mypool                781M  93.2G   144K  none
mypool/ROOT           777M  93.2G   144K  none
mypool/ROOT/default   777M  93.2G   777M  /
mypool/tmp            176K  93.2G   176K  /tmp
mypool/usr            616K  93.2G   144K  /usr
mypool/usr/home       184K  93.2G   184K  /usr/home
mypool/usr/ports      144K  93.2G   144K  /usr/ports
mypool/usr/src        144K  93.2G   144K  /usr/src
mypool/var           1.21M  93.2G   612K  /var
mypool/var/crash      148K  93.2G   148K  /var/crash
mypool/var/log        178K  93.2G   178K  /var/log
mypool/var/mail       144K  93.2G   144K  /var/mail
mypool/var/tmp        152K  93.2G   152K  /var/tmp

在最近版本的 ZFSzfs destroy 是非同步的,且釋放出的空間會許要花費數分鐘才會出現在儲存池上,可使用 zpool get freeing poolname 來查看 freeing 屬性,這個屬性會指出資料集在背景已經釋放多少資料區塊了。若有子資料集,如快照 (Snapshot) 或其他資料集存在的話,則會無法摧毀父資料集。要摧毀一個資料集及其所有子資料集,可使用 -r 來做遞迴摧毀資料集及其所有子資料集,可用 -n -v 來列出會被這個操作所摧毀的資料集及快照,而不會真的摧毀,因摧毀快照所釋放出的空間也會同時顯示。

19.4.2. 建立與摧毀磁碟區

磁碟區 (Volume) 是特殊類型的資料集,不會被掛載成一個檔案系統,而是會被當做儲存區塊裝置出現在 /dev/zvol/poolname/dataset 下。這讓磁碟區可以用於其他檔案系統,備份虛擬機器的磁碟或是使用 iSCSIHAST 通訊協定匯出。

磁碟區可以被格式化成任何檔案系統,或不使用檔案系統來儲存原始資料。對一般使用者,磁碟區就像是一般的磁碟,可以放置一般的檔案系統在這些 zvols上,並提供一般磁碟或檔案系統一般所沒有的功能。例如,使用壓縮屬性在一個 250 MB 的磁碟區可建立一個壓縮的 FAT 檔案系統。

# zfs create -V 250m -o compression=on tank/fat32
# zfs list tank
NAME USED AVAIL REFER MOUNTPOINT
tank 258M  670M   31K /tank
# newfs_msdos -F32 /dev/zvol/tank/fat32
# mount -t msdosfs /dev/zvol/tank/fat32 /mnt
# df -h /mnt | grep fat32
Filesystem           Size Used Avail Capacity Mounted on
/dev/zvol/tank/fat32 249M  24k  249M     0%   /mnt
# mount | grep fat32
/dev/zvol/tank/fat32 on /mnt (msdosfs, local)

摧毀一個磁碟區與摧毀一個一般的檔案系統資料集差不多。操作上幾乎是即時的,但在背景會需要花費數分鐘來讓釋放空間再次可用。

19.4.3. 重新命名資料集

資料集的名稱可以使用 zfs rename 更改。父資料集也同樣可以使用這個指令來更改名稱。重新命名一個資料集到另一個父資料集也會更改自父資料集繼承的屬性值。重新命名資料集後,會被卸載然後重新掛載到新的位置 (依繼承的新父資料集而定),可使用 -u 來避免重新掛載。

重新命名一個資料集並移動該資料集到另一個父資料集:

# zfs list
NAME                   USED  AVAIL  REFER  MOUNTPOINT
mypool                 780M  93.2G   144K  none
mypool/ROOT            777M  93.2G   144K  none
mypool/ROOT/default    777M  93.2G   777M  /
mypool/tmp             176K  93.2G   176K  /tmp
mypool/usr             704K  93.2G   144K  /usr
mypool/usr/home        184K  93.2G   184K  /usr/home
mypool/usr/mydataset  87.5K  93.2G  87.5K  /usr/mydataset
mypool/usr/ports       144K  93.2G   144K  /usr/ports
mypool/usr/src         144K  93.2G   144K  /usr/src
mypool/var            1.21M  93.2G   614K  /var
mypool/var/crash       148K  93.2G   148K  /var/crash
mypool/var/log         178K  93.2G   178K  /var/log
mypool/var/mail        144K  93.2G   144K  /var/mail
mypool/var/tmp         152K  93.2G   152K  /var/tmp
# zfs rename mypool/usr/mydataset mypool/var/newname
# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
mypool                780M  93.2G   144K  none
mypool/ROOT           777M  93.2G   144K  none
mypool/ROOT/default   777M  93.2G   777M  /
mypool/tmp            176K  93.2G   176K  /tmp
mypool/usr            616K  93.2G   144K  /usr
mypool/usr/home       184K  93.2G   184K  /usr/home
mypool/usr/ports      144K  93.2G   144K  /usr/ports
mypool/usr/src        144K  93.2G   144K  /usr/src
mypool/var           1.29M  93.2G   614K  /var
mypool/var/crash      148K  93.2G   148K  /var/crash
mypool/var/log        178K  93.2G   178K  /var/log
mypool/var/mail       144K  93.2G   144K  /var/mail
mypool/var/newname   87.5K  93.2G  87.5K  /var/newname
mypool/var/tmp        152K  93.2G   152K  /var/tmp

快照也可以像這樣重新命名,由於快照的天性,使其無法被重新命名到另一個父資料集。要遞迴重新命名快照可指定 -r,然後在子資料集中所有同名的快照也會一併被重新命名。

# zfs list -t snapshot
NAME                                USED  AVAIL  REFER  MOUNTPOINT
mypool/var/newname@first_snapshot      0      -  87.5K  -
# zfs rename mypool/var/newname@first_snapshot new_snapshot_name
# zfs list -t snapshot
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/newname@new_snapshot_name      0      -  87.5K  -

19.4.4. 設定資料集屬性

每個 ZFS 資料集有數個屬性可以用來控制其行為。大部份的屬性會自動繼承自其父資料集,但可以被自己覆蓋。設定資料集上的屬性可使用 zfs set property=value dataset。大部份屬性有限制可用的值,zfs get 會顯示每個可以使用的屬性及其可用的值。大部份可以使用 zfs inherit 還原成其繼承的值。

也可設定使用者自訂的屬性。這些屬性也會成為資料集設定的一部份,且可以被用來提供資料集或其內容的額外資訊。要別分自訂屬性與 ZFS 提供的屬性,會使用冒號 (:) 建立一個自訂命名空間供自訂屬性使用。

# zfs set custom:costcenter=1234 tank
# zfs get custom:costcenter tank
NAME PROPERTY           VALUE SOURCE
tank custom:costcenter  1234  local

要移除自訂屬性,可用 zfs inherit 加上 -r。若父資料集未定義任何自訂屬性,將會將該屬性完全移除 (更改動作仍會記錄於儲存池的歷史記錄)。

# zfs inherit -r custom:costcenter tank
# zfs get custom:costcenter tank
NAME    PROPERTY           VALUE              SOURCE
tank    custom:costcenter  -                  -
# zfs get all tank | grep custom:costcenter
#

19.4.5. 管理快照 (Snapshot)

快照 (Snapshot) 是 ZFS 最強大的功能之一。快照提供了資料集唯讀、單一時間點 (Point-in-Time) 的複製功能,使用了寫入時複製 (Copy-On-Write, COW) 的技術,可以透過保存在磁碟上的舊版資料快速的建立快照。若沒有快照存在,在資料被覆蓋或刪除時,便回收空間供未來使用。由於只記錄前一個版本與目前資料集的差異,因此快照可節省磁碟空間。快照只允許在整個資料集上使用,無法在各別檔案或目錄。當建立了一個資料集的快照時,便備份了所有內含的資料,這包含了檔案系統屬性、檔案、目錄、權限等等。第一次建立快照時只會使用到更改參照到資料區塊的空間,不會用到額外的空間。使用 -r 可以對使用同名的資料集及其所有子資料集的建立一個遞迴快照,提供一致且即時 (Moment-in-time) 的完整檔案系統快照功能,這對於那些彼此有相關或相依檔案存放在不同資料集的應用程式非常重要。若不使用快照,備份所複製的資料其實是不同時間點的,可能會有不一致的問題。

ZFS 中的快照提供了多種功能,即使是在其他缺乏快照功能的檔案系統上。一個使用快照的典型例子是在安裝軟體或執行系統升級這種有風險的動作時,能有一個快速的方式可以備份檔案系統目前的狀態,若動作失敗,可以使用快照還原 (Roll back) 到與快照建立時相同的系統狀態,若升級成功,便可刪除快照來釋放空間。若沒有快照功能,升級失敗通常會需要使用備份來恢復 (Restore) 系統,而這個動作非常繁瑣、耗時且可能會需要停機一段時間系統無法使用。使用快照可以快速的還原,即使系統正在執行一般的運作,只而要短暫或甚至不需停機。能夠節省大量在有數 TB 的儲存系統上從備份複製所需資料的時間。快照並非要用來取代儲存池的完整備份,但可以用在快速且簡單的保存某個特定時間點的資料集。

19.4.5.1. 建立快照

快照可以使用 zfs snapshot dataset@snapshotname 來建立。加入 -r 可以遞迴對所有同名的子資料集建立快照。

建立一個整個儲存池的遞迴快照:

# zfs list -t all
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool                                 780M  93.2G   144K  none
mypool/ROOT                            777M  93.2G   144K  none
mypool/ROOT/default                    777M  93.2G   777M  /
mypool/tmp                             176K  93.2G   176K  /tmp
mypool/usr                             616K  93.2G   144K  /usr
mypool/usr/home                        184K  93.2G   184K  /usr/home
mypool/usr/ports                       144K  93.2G   144K  /usr/ports
mypool/usr/src                         144K  93.2G   144K  /usr/src
mypool/var                            1.29M  93.2G   616K  /var
mypool/var/crash                       148K  93.2G   148K  /var/crash
mypool/var/log                         178K  93.2G   178K  /var/log
mypool/var/mail                        144K  93.2G   144K  /var/mail
mypool/var/newname                    87.5K  93.2G  87.5K  /var/newname
mypool/var/newname@new_snapshot_name      0      -  87.5K  -
mypool/var/tmp                         152K  93.2G   152K  /var/tmp
# zfs snapshot -r mypool@my_recursive_snapshot
# zfs list -t snapshot
NAME                                        USED  AVAIL  REFER  MOUNTPOINT
mypool@my_recursive_snapshot                   0      -   144K  -
mypool/ROOT@my_recursive_snapshot              0      -   144K  -
mypool/ROOT/default@my_recursive_snapshot      0      -   777M  -
mypool/tmp@my_recursive_snapshot               0      -   176K  -
mypool/usr@my_recursive_snapshot               0      -   144K  -
mypool/usr/home@my_recursive_snapshot          0      -   184K  -
mypool/usr/ports@my_recursive_snapshot         0      -   144K  -
mypool/usr/src@my_recursive_snapshot           0      -   144K  -
mypool/var@my_recursive_snapshot               0      -   616K  -
mypool/var/crash@my_recursive_snapshot         0      -   148K  -
mypool/var/log@my_recursive_snapshot           0      -   178K  -
mypool/var/mail@my_recursive_snapshot          0      -   144K  -
mypool/var/newname@new_snapshot_name           0      -  87.5K  -
mypool/var/newname@my_recursive_snapshot       0      -  87.5K  -
mypool/var/tmp@my_recursive_snapshot           0      -   152K  -

建立的快照不會顯示在一般的 zfs list 操作結果,要列出快照需在 zfs list 後加上 -t snapshot,使用 -t all 可以同時列出檔案系統的內容及快照。

快照並不會直接掛載,因此 MOUNTPOINT 欄位的路徑如此顯示。在 AVAIL 欄位不會有可用的磁碟空間,因為快照建立之後便無法再寫入。比較快照與其原來建立時的資料集:

# zfs list -rt all mypool/usr/home
NAME                                    USED  AVAIL  REFER  MOUNTPOINT
mypool/usr/home                         184K  93.2G   184K  /usr/home
mypool/usr/home@my_recursive_snapshot      0      -   184K  -

同時顯示資料集與快照可以了解快照如何使用 COW 技術來運作。快照只會保存有更動 (差異) 的資料,並非整個檔案系統的內容,這個意思是說,快照只會在有做更動時使用一小部份的空間,複製一個檔案到該資料集,可以讓空間使用量變的更明顯,然後再做第二個快照:

# cp /etc/passwd /var/tmp
# zfs snapshot mypool/var/tmp@after_cp
# zfs list -rt all mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp                         206K  93.2G   118K  /var/tmp
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp                   0      -   118K  -

第二快照只會包含了資料集做了複製動作後的更動,這樣的機制可以節省大量的空間。注意在複製之後快照 mypool/var/tmp@my_recursive_snapshotUSED 欄位中的大小也更改了,這說明了這個更動在前次快照與之後快照間的關係。

19.4.5.2. 比對快照

ZFS 提供了內建指令可以用來比對兩個快照 (Snapshot) 之間的差異,在使用者想要查看一段時間之間檔案系統所的變更時非常有用。例如 zfs diff 可以讓使用者在最後一次快照中找到意外刪除的檔案。對前面一節所做的兩個快照使用這個指令會產生以下結果:

# zfs list -rt all mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp                         206K  93.2G   118K  /var/tmp
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp                   0      -   118K  -
# zfs diff mypool/var/tmp@my_recursive_snapshot
M       /var/tmp/
+       /var/tmp/passwd

指令會列出指定快照 (在這個例子中為 mypool/var/tmp@my_recursive_snapshot) 與目前檔案系統間的更改。第一個欄位是更改的類型:

+加入了該路徑或檔案。
-刪除了該路徑或檔案。
M修改了該路徑或檔案。
R重新命名了該路徑或檔案。

對照這個表格來看輸出的結果,可以明顯的看到 passwd 是在快照 mypool/var/tmp@my_recursive_snapshot 建立之後才加入的,結果也同樣看的到掛載到 /var/tmp 的父目錄已經做過修改。

在使用 ZFS 備份功能來傳輸一個資料集到另一個主機備份時比對兩個快照也同樣很有用。

比對兩個快照需要提供兩個資料集的完整資料集名稱與快照名稱:

# cp /var/tmp/passwd /var/tmp/passwd.copy
# zfs snapshot mypool/var/tmp@diff_snapshot
# zfs diff mypool/var/tmp@my_recursive_snapshot mypool/var/tmp@diff_snapshot
M       /var/tmp/
+       /var/tmp/passwd
+       /var/tmp/passwd.copy
# zfs diff mypool/var/tmp@my_recursive_snapshot mypool/var/tmp@after_cp
M       /var/tmp/
+       /var/tmp/passwd

備份管理者可以比對兩個自傳送主機所接收到的兩個快照並查看實際在資料集中的變更。請參考 備份 一節來取得更多資訊。

19.4.5.3. 使用快照還原

只要至少有一個可用的快照便可以隨時還原。大多數在已不需要目前資料集,想要改用較舊版的資料的情況,例如,本地開發的測試發生錯誤、不良的系統更新破壞了系統的整體功能或需要還原意外刪除檔案或目錄 ... 等,都是非常常見的情形。幸運的,要還原到某個快照只需要簡單輸入 zfs rollback snapshotname。會依快照所做的變更數量來決定處理的時間,還原的操作會在一段時間後完成。在這段時間中,資料集會一直保持一致的狀態,類似一個符合 ACID 原則的資料庫在做還原。還原可在資料集處於上線及可存取的情況下完成,不需要停機。還原到快照之後,資料集便回到當初執行快照時相同的狀態,所有沒有在快照中的其他資料便會被丟棄,因此往後若還有可能需要部份資料時,建議在還原到前一個快照之前先對目前的資料集做快照,這樣一來,使用者便可以在快照之間來回快換,而不會遺失重要的資料。

在第一個範例中,因為 rm 操作不小心移除了預期外的資料,要還原到快照。

# zfs list -rt all mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp                         262K  93.2G   120K  /var/tmp
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp               53.5K      -   118K  -
mypool/var/tmp@diff_snapshot              0      -   120K  -
% ls /var/tmp
passwd          passwd.copy
% rm /var/tmp/passwd*
% ls /var/tmp
vi.recover
%

在此時,使用者發現到刪除了太多檔案並希望能夠還原。ZFS 提供了簡單的方可以取回檔案,便是使用還原 (Rollback),但這只在有定期對重要的資料使用快照時可用。要拿回檔案並從最後一次快照重新開始,可執行以下指令:

# zfs rollback mypool/var/tmp@diff_snapshot
% ls /var/tmp
passwd          passwd.copy     vi.recover

還原操作會將資料集還原為最後一次快照的狀態。這也可以還原到更早之前,有其他在其之後建立的快照。要這麼做時,ZFS 會發出這個警告:

# zfs list -rt snapshot mypool/var/tmp
AME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp@my_recursive_snapshot    88K      -   152K  -
mypool/var/tmp@after_cp               53.5K      -   118K  -
mypool/var/tmp@diff_snapshot              0      -   120K  -
# zfs rollback mypool/var/tmp@my_recursive_snapshot
cannot rollback to 'mypool/var/tmp@my_recursive_snapshot': more recent snapshots exist
use '-r' to force deletion of the following snapshots:
mypool/var/tmp@after_cp
mypool/var/tmp@diff_snapshot

這個警告是因在該快照與資料集的目前狀態之間有其他快照存在,然而使用者想要還原到該快照。要完成這樣的還原動作,必須刪除在這之間的快照,因為 ZFS 無法追蹤不同資料集狀態間的變更。在使用者未指定 -r 來確認這個動作前,ZFS 不會刪除受影響的快照。若確定要這麼做,那麼必須要知道會遺失所有在這之間的快照,然後可執行以下指令:

# zfs rollback -r mypool/var/tmp@my_recursive_snapshot
# zfs list -rt snapshot mypool/var/tmp
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
mypool/var/tmp@my_recursive_snapshot     8K      -   152K  -
% ls /var/tmp
vi.recover

可從 zfs list -t snapshot 的結果來確認 zfs rollback -r 會移除的快照。

19.4.5.4. 從快照還原個別檔案

快照會掛載在父資料集下的隱藏目錄:.zfs/snapshots/snapshotname。預設不會顯示這些目錄,即使是用 ls -a 指令。雖然該目錄不會顯示,但該目錄實際存在,而且可以像一般的目錄一樣存取。一個名稱為 snapdir 的屬性可以控制是否在目錄清單中顯示這些隱藏目錄,設定該屬性為可見 (visible) 可以讓這些目錄出現在 ls 以及其他處理目錄內容的指令中。

# zfs get snapdir mypool/var/tmp
NAME            PROPERTY  VALUE    SOURCE
mypool/var/tmp  snapdir   hidden   default
% ls -a /var/tmp
.               ..              passwd          vi.recover
# zfs set snapdir=visible mypool/var/tmp
% ls -a /var/tmp
.               ..              .zfs            passwd          vi.recover

要還原個別檔案到先前的狀態非常簡單,只要從快照中複製檔案到父資料集。在 .zfs/snapshot 目錄結構下有一個與先前所做的快照名稱相同的目錄,可以很容易的找到。在下個範例中,我們會示範從隱藏的 .zfs 目錄還原一個檔案,透過從含有該檔案的最新版快照複製:

# rm /var/tmp/passwd
% ls -a /var/tmp
.               ..              .zfs            vi.recover
# ls /var/tmp/.zfs/snapshot
after_cp                my_recursive_snapshot
# ls /var/tmp/.zfs/snapshot/after_cp
passwd          vi.recover
# cp /var/tmp/.zfs/snapshot/after_cp/passwd /var/tmp

執行 ls .zfs/snapshot 時,雖然 snapdir 可能已經設為隱藏,但仍可能可以顯示該目錄中的內容,這取決於管理者是否要顯示這些目錄,可以只顯示特定的資料集,而其他的則不顯示。從這個隱藏的 .zfs/snapshot 複製檔案或目錄非常簡單,除此之外,嘗試其他的動作則會出現以下錯誤:

# cp /etc/rc.conf /var/tmp/.zfs/snapshot/after_cp/
cp: /var/tmp/.zfs/snapshot/after_cp/rc.conf: Read-only file system

這個錯誤用來提醒使用者快照是唯讀的,在建立之後不能更改。無法複製檔案進去或從該快照目錄中移除,因為這會變更該資料集所代表的狀態。

快照所消耗的空間是依據自快照之後父檔案系統做了多少變更來決定,快照的 written 屬性可以用來追蹤有多少空間被快照所使用。

使用 zfs destroy dataset@snapshot 可以摧毀快照並回收空間。加上 -r 可以遞迴移除所有在父資料集下使用同名的快照。加入 -n -v 來顯示將要移除的快照清單以及估計回收的空間,而不會實際執行摧毀的操作。

19.4.6. 管理複本 (Clone)

複本 (Clone) 是快照的複製,但更像是一般的資料集,與快照不同的是,複本是非唯讀的 (可寫),且可掛載,可以有自己的屬性。使用 zfs clone 建立複本之後,便無法再摧毀用來建立複本的快照。複本與快照的父/子關係可以使用 zfs promote 來對換。提升複本之後 ,快照便會成為複本的子資料集,而不是原來的父資料集,這個動作會改變空間計算的方式,但並不會實際改變空間的使用量。複本可以被掛載到 ZFS 檔案系統階層中的任何一點,並非只能位於原來快照的位置底下。

要示範複本功能會用到這個範例資料集:

# zfs list -rt all camino/home/joe
NAME                    USED  AVAIL  REFER  MOUNTPOINT
camino/home/joe         108K   1.3G    87K  /usr/home/joe
camino/home/joe@plans    21K      -  85.5K  -
camino/home/joe@backup    0K      -    87K  -

會使用到複本一般是要在可以保留快照以便出錯時可還原的情況下使用指定的資料集做實驗,由於快照並無法做更改,所以會建立一個可以讀/寫的快照複本。當在複本中做完想要執行的動作後,便可以提升複本成資料集,然後移除舊的檔案系統。嚴格來說這並非必要,因為複本與資料集可同時存在,不會有任何問題。

# zfs clone camino/home/joe@backup camino/home/joenew
# ls /usr/home/joe*
/usr/home/joe:
backup.txz     plans.txt

/usr/home/joenew:
backup.txz     plans.txt
# df -h /usr/home
Filesystem          Size    Used   Avail Capacity  Mounted on
usr/home/joe        1.3G     31k    1.3G     0%    /usr/home/joe
usr/home/joenew     1.3G     31k    1.3G     0%    /usr/home/joenew

建立完的複本便有與建立快照時狀態相同的資料集,現在複本可以獨立於原來的資料集來做更改。剩下唯一與資料集之間的關係便是快照,ZFS 會在屬性 origin 記錄這個關係,一旦在快照與複本之間的相依關係因為使用 zfs promote 提升而移除時,複本的 origin 也會因為成為一個完全獨立的資料集而移除。以下範例會示範這個動作:

# zfs get origin camino/home/joenew
NAME                  PROPERTY  VALUE                     SOURCE
camino/home/joenew    origin    camino/home/joe@backup    -
# zfs promote camino/home/joenew
# zfs get origin camino/home/joenew
NAME                  PROPERTY  VALUE   SOURCE
camino/home/joenew    origin    -       -

做為部份更改之後,例如複製 loader.conf 到提升後的複本,這個例子中的舊目錄便無須保留,取而代之的是提升後的複本,這個動作可以用兩個連續的指令來完成:在舊資料集上執行 zfs destroy 並在與舊資料相似名稱 (也可能用完全不同的名稱) 的複本上執行 zfs rename

# cp /boot/defaults/loader.conf /usr/home/joenew
# zfs destroy -f camino/home/joe
# zfs rename camino/home/joenew camino/home/joe
# ls /usr/home/joe
backup.txz     loader.conf     plans.txt
# df -h /usr/home
Filesystem          Size    Used   Avail Capacity  Mounted on
usr/home/joe        1.3G    128k    1.3G     0%    /usr/home/joe

快照的複本現在可以如同一般資料集一樣使用,它的內容包含了所有來自原始快照的資料以及後來加入的檔案,例如 loader.conf。複本可以在許多不同的情境下使用提供 ZFS 的使用者有用的功能,例如,Jail 可以透過含有已安裝了各種應用程式集的快照來提供,使用者可以複製這些快照然後加入自己想要嘗試的應用程式,一但更改可以滿足需求,便可提升複本為完整的資料集然後提供給終端使用者,讓終端使用者可以如同實際擁有資料集一般的使用,這個以節省提供這些 Jail 的時間與管理成本。

19.4.7. 備份 (Replication)

將資料保存在單一地點的單一儲存池上會讓資料暴露在盜竊、自然或人為的風險之下,定期備份整個儲存池非常重要,ZFS 提供了內建的序列化 (Serialization) 功能可以將資料以串流傳送到標準輸出。使用這項技術,不僅可以將資料儲存到另一個已連結到本地系統的儲存池,也可以透過網路將資料傳送到另一個系統,這種備份方式以快照為基礎 (請參考章節 ZFS 快照(Snapshot))。用來備份資料的指令為 zfs sendzfs receive

以下例子將示範使用兩個儲存池來做 ZFS 備份:

# zpool list
NAME    SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
backup  960M    77K   896M     0%  1.00x  ONLINE  -
mypool  984M  43.7M   940M     4%  1.00x  ONLINE  -

名為 mypool 的儲存池為主要的儲存池,資料會定期寫入與讀取的位置。第二個儲存池 backup 用來待命 (Standby),萬一主要儲存池無法使用時可替換。注意,ZFS 並不會自動做容錯移轉 (Fail-over),必須要由系統管理者在需要的時候手動完成。快照會用來提供一個與檔系統一致的版本來做備份,mypool 的快照建立之後,便可以複製到 backup 儲存池,只有快照可以做備份,最近一次快照之後所做的變更不會含在內容裡面。

# zfs snapshot mypool@backup1
# zfs list -t snapshot
NAME                    USED  AVAIL  REFER  MOUNTPOINT
mypool@backup1             0      -  43.6M  -

快照存在以後,便可以使用 zfs send 來建立一個代表快照內容的串流,這個串流可以儲存成檔案或由其他儲存池接收。串流會寫入到標準輸出,但是必須要重新導向到一個檔案或轉接到其他地方,否則會錯誤:

# zfs send mypool@backup1
Error: Stream can not be written to a terminal.
You must redirect standard output.

要使用 zfs send 備份一個資料集,可重新導向到一個位於在已掛載到備份儲存池上的檔案。確定該儲存池有足夠的空間容納要傳送的快照,這裡指的是該快照中內含的所有資料,並非只有上次快照到該快照間的變更。

# zfs send mypool@backup1 > /backup/backup1
# zpool list
NAME    SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
backup  960M  63.7M   896M     6%  1.00x  ONLINE  -
mypool  984M  43.7M   940M     4%  1.00x  ONLINE  -

zfs send 會傳輸在快照 backup1 中所有的資料到儲存池 backup。可以使用 cron(8) 排程來自動完成建立與傳送快照的動作。

若不想將備份以封存檔案儲存,ZFS 可用實際的檔案系統來接收資料,讓備份的資料可以直接被存取。要取得實際包含在串流中的資料可以用 zfs receive 將串流轉換回檔案與目錄。以下例子會以管線符號連接 zfs sendzfs receive,將資料從一個儲存池複製到另一個,傳輸完成後可以直接使用接收儲存池上的資料。一個資料集只可以被複製到另一個空的資料集。

# zfs snapshot mypool@replica1
# zfs send -v mypool@replica1 | zfs receive backup/mypool
send from @ to mypool@replica1 estimated size is 50.1M
total estimated size is 50.1M
TIME        SENT   SNAPSHOT

# zpool list
NAME    SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
backup  960M  63.7M   896M     6%  1.00x  ONLINE  -
mypool  984M  43.7M   940M     4%  1.00x  ONLINE  -

19.4.7.1. 漸進式備份

zfs send 也可以比較兩個快照之間的差異,並且只傳送兩者之間的差異,這麼做可以節省磁碟空間及傳輸時間。例如:

# zfs snapshot mypool@replica2
# zfs list -t snapshot
NAME                    USED  AVAIL  REFER  MOUNTPOINT
mypool@replica1         5.72M      -  43.6M  -
mypool@replica2             0      -  44.1M  -
# zpool list
NAME    SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
backup  960M  61.7M   898M     6%  1.00x  ONLINE  -
mypool  960M  50.2M   910M     5%  1.00x  ONLINE  -

會建立一個名為 replica2 的第二個快照,這個快照只中只會含有目前與前次快照 replica1 之間檔案系統所做的變更。使用 zfs send -i 並指定要用來產生漸進備份串流的快照,串流中只會含有做過更改的資料。這個動作只在接收端已經有初始快照時才可用。

# zfs send -v -i mypool@replica1 mypool@replica2 | zfs receive /backup/mypool
send from @replica1 to mypool@replica2 estimated size is 5.02M
total estimated size is 5.02M
TIME        SENT   SNAPSHOT

# zpool list
NAME    SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
backup  960M  80.8M   879M     8%  1.00x  ONLINE  -
mypool  960M  50.2M   910M     5%  1.00x  ONLINE  -

# zfs list
NAME                         USED  AVAIL  REFER  MOUNTPOINT
backup                      55.4M   240G   152K  /backup
backup/mypool               55.3M   240G  55.2M  /backup/mypool
mypool                      55.6M  11.6G  55.0M  /mypool

# zfs list -t snapshot
NAME                                         USED  AVAIL  REFER  MOUNTPOINT
backup/mypool@replica1                       104K      -  50.2M  -
backup/mypool@replica2                          0      -  55.2M  -
mypool@replica1                             29.9K      -  50.0M  -
mypool@replica2                                 0      -  55.0M  -

如此一來,便成功傳輸漸進式的串流,只有做過更改的資料會被備份,不會傳送完整的 replica1。由於不會備份完整的儲存池,只傳送差異的部份,所以可以減少傳輸的時間並節省磁碟空間,特別是在網路緩慢或需要考量每位元傳輸成本時非常有用。

從儲存池 mypool 複製所有檔案與資料的新檔案系統 backup/mypool 便可以使用。若指定 -P,會一併複製資料集的屬性,這包含壓縮 (Compression) 設定,配額 (Quota) 及掛載點 (Mount point)。若指定 -R,會複製所有指定資料集的子資料集,及這些子資料集的所有屬性。可將傳送與接收自動化來定期使用第二個儲存池做備份。

19.4.7.2. 透過 SSH 傳送加密的備份

透過網路來傳送串流對要遠端備份是相當不錯的方式,但是也有一些缺點,透過網路連結傳送的資料沒有加密,這會讓任何人都可以在未告知傳送使用者的情況下攔截並轉換串流回資料,這是我們所不想見到的情況,特別是在使用網際網路傳送串流到遠端的主機時。SSH 可用來安全的加密要透過網路連線傳送的資料,在 ZFS 只需要從標準輸出重新導向便可簡單的轉接到 SSH。要在傳送或在遠端系統中維持檔案系統內容在加密的狀態也可考慮使用 PEFS

有一些設定以及安全性注意事項必須先完成,只有對 zfs send 操作必要的步驟才會在此說明,要取得更多有關 SSH 的資訊請參考 節 13.8, “OpenSSH”

必要的環境設定:

  • 使用 SSH 金鑰設定傳送端與接收端間無密碼的 SSH 存取

  • 正常會需要 root 的權限來傳送與接收串流,這需要可以 root 登入到接收端系統。但是,預設因安全性考慮會關閉以 root 登入。ZFS 委託 (ZFS Delegation) 系統可以用來允許一個非 root 使用者在每個系統上執行各自的發送與接收操作。

  • 在傳送端系統上:

    # zfs allow -u someuser send,snapshot mypool
  • 要掛載儲存池,無權限的使用者必須擁有該目錄且必須允許一般的使用者掛載檔案系統。在接收端系統上:

    # sysctl vfs.usermount=1
    vfs.usermount: 0 -> 1
    # echo vfs.usermount=1 >> /etc/sysctl.conf
    # zfs create recvpool/backup
    # zfs allow -u someuser create,mount,receive recvpool/backup
    # chown someuser /recvpool/backup

無權限的使用者現在有能力可以接收並掛載資料集,且 home 資料集可以被複製到遠端系統:

% zfs snapshot -r mypool/home@monday
% zfs send -R mypool/home@monday | ssh someuser@backuphost zfs recv -dvu recvpool/backup

替儲存在儲存池 mypool 上的檔案系統資料集 home 製作一個遞迴快照 monday,然後使用 zfs send -R 來傳送包含該資料集及其所有子資料集、快照、複製與設定的串流。輸出會被導向到 SSH 連線的遠端主機 backuphost 上等候輸入的 zfs receive,在此建議使用完整網域名稱或 IP 位置。接收端的機器會寫入資料到 recvpool 儲存池上的 backup 資料集,在 zfs recv 加上 -d 可覆寫在接收端使用相同名稱的快照,加上 -u 可讓檔案系統在接收端不會被掛載,當使用 -v,會顯示更多有關傳輸的詳細資訊,包含已花費的時間及已傳輸的資料量。

19.4.8. 資料集、使用者以及群組配額

資料集配額 (Dataset quota) 可用來限制特定資料集可以使用的的空間量。參考配額 (Reference Quota) 的功能也非常相似,差在參考配額只會計算資料集自己使用的空間,不含快照與子資料集。類似的,使用者 (User) 與群組 (Group) 配額可以用來避免使用者或群組用掉儲存池或資料集的所有空間。

要設定 storage/home/bob 的資料集配額為 10 GB:

# zfs set quota=10G storage/home/bob

要設定 storage/home/bob 的參考配額為 10 GB:

# zfs set refquota=10G storage/home/bob

要移除 storage/home/bob 的 10 GB 配額:

# zfs set quota=none storage/home/bob

設定使用者配額的一般格式為 userquota@user=size 使用者的名稱必須使用以下格式:

  • POSIX 相容的名稱,如 joe

  • POSIX 數字 ID,如 789

  • SID 名稱,如 joe.bloggs@example.com

  • SID 數字 ID,如 S-1-123-456-789

例如,要設定使用者名為 joe 的使用者配額為 50 GB:

# zfs set userquota@joe=50G

要移除所有配額:

# zfs set userquota@joe=none

注意:

使用者配額的屬性不會顯示在 zfs get all。非 root 的使用者只可以看到自己的配額,除非它們有被授予 userquota 權限,擁有這個權限的使用者可以檢視與設定任何人的配額。

要設定群組配額的一般格式為:groupquota@group=size

要設定群組 firstgroup 的配額為 50 GB 可使用:

# zfs set groupquota@firstgroup=50G

要移除群組 firstgroup 的配額,或確保該群組未設定配額可使用:

# zfs set groupquota@firstgroup=none

如同使用者配額屬性,非 root 使用者只可以查看自己所屬群組的配額。而 root 或擁有 groupquota 權限的使用者,可以檢視並設定所有群組的任何配額。

要顯示在檔案系統或快照上每位使用者所使用的空間量及配額可使用 zfs userspace,要取得群組的資訊則可使用 zfs groupspace,要取得有關支援的選項資訊或如何只顯示特定選項的資訊請參考 zfs(1)

有足夠權限的使用者及 root 可以使用以下指令列出 storage/home/bob 的配額:

# zfs get quota storage/home/bob

19.4.9. 保留空間

保留空間 (Reservation) 可以確保資料集最少可用的空間量,其他任何資料集無法使用保留的空間,這個功能在要確保有足夠的可用空間來存放重要的資料集或日誌檔時特別有用。

reservation 屬性的一般格式為 reservation=size,所以要在 storage/home/bob 設定保留 10 GB 的空間可以用:

# zfs set reservation=10G storage/home/bob

要清除任何保留空間:

# zfs set reservation=none storage/home/bob

同樣的原則可以應用在 refreservation 屬性來設定參考保留空間 (Reference Reservation),參考保留空間的一般格式為 refreservation=size

這個指令會顯示任何已設定於 storage/home/bob 的 reservation 或 refreservation:

# zfs get reservation storage/home/bob
# zfs get refreservation storage/home/bob

19.4.10. 壓縮 (Compression)

ZFS 提供直接的壓縮功能,在資料區塊層級壓縮資料不僅可以節省空間,也可以增加磁碟的效能。若資料壓縮了 25%,但壓縮的資料會使用了與未壓縮版本相同的速率寫入到磁碟,所以實際的寫入速度會是原來的 125%。壓縮功能也可來替代去重複 (Deduplication) 功能,因為壓縮並不需要使用額外的記憶體。

ZFS 提了多種不同的壓縮演算法,每一種都有不同的優缺點,隨著 ZFS v5000 引進了 LZ4 壓縮技術,可對整個儲存池開啟壓縮,而不像其他演算法需要消耗大量的效能來達成,最大的優點是 LZ4 擁有 提早放棄 的功能,若 LZ4 無法在資料一開始的部份達成至少 12.5% 的壓縮率,便會以不壓縮的方式來寫入資料區塊來避免 CPU 在那些已經壓縮過或無法壓縮的資料上浪費運算能力。要取得更多有關 ZFS 中可用的壓縮演算法詳細資訊,可參考術語章節中的壓縮 (Compression) 項目。

管理者可以使用資料集的屬性來監視壓縮的效果。

# zfs get used,compressratio,compression,logicalused mypool/compressed_dataset
NAME        PROPERTY          VALUE     SOURCE
mypool/compressed_dataset  used              449G      -
mypool/compressed_dataset  compressratio     1.11x     -
mypool/compressed_dataset  compression       lz4       local
mypool/compressed_dataset  logicalused       496G      -

資料集目前使用了 449 GB 的空間 (在 used 屬性)。在尚未壓縮前,該資料集應該會使用 496 GB 的空間 (於 logicallyused 屬性),這個結果顯示目前的壓縮比為 1.11:1。

壓縮功能在與使用者配額 (User Quota) 一併使用時可能會產生無法預期的副作用。使用者配額會限制一個使用者在一個資料集上可以使用多少空間,但衡量的依據是以 壓縮後 所使用的空間,因此,若一個使用者有 10 GB 的配額,寫入了 10 GB 可壓縮的資料,使用者將還會有空間儲存額外的資料。若使用者在之後更新了一個檔案,例如一個資料庫,可能有更多或較少的可壓縮資料,那麼剩餘可用的空間量也會因此而改變,這可能會造成奇怪的現象便是,一個使用者雖然沒有增加實際的資料量 (於 logicalused 屬性),但因為更改影響了壓縮率,導致使用者達到配額的上限。

壓縮功能在與備份功能一起使用時也可能會有類似的問題,通常會使用配額功能來限制能夠儲存的資料量來確保有足夠的備份空間可用。但是由於配額功能並不會考量壓縮狀況,可能會有比未壓縮版本備份更多的資料量會被寫入到資料集。

19.4.11. 去重複 (Deduplication)

當開啟,去重複 (Deduplication) 功能會使用每個資料區塊的校驗碼 (Checksum) 來偵測重複的資料區塊,當新的資料區塊與現有的資料區塊重複,ZFS 便會寫入連接到現有資料的參考來替代寫入重複的資料區塊,這在資料中有大量重複的檔案或資訊時可以節省大量的空間,要注意的是:去重複功能需要使用大量的記憶體且大部份可節省的空間可改開啟壓縮功能來達成,而壓縮功能不需要使用額外的記憶體。

要開啟去重複功能,需在目標儲存池設定 dedup 屬性:

# zfs set dedup=on pool

只有要被寫入到儲存池的新資料才會做去重複的動作,先前已被寫入到儲存池的資料不會因此啟動了這個選項而做去重複。查看已開啟去重複屬性的儲存池會如下:

# zpool list
NAME  SIZE ALLOC  FREE CAP DEDUP HEALTH ALTROOT
pool 2.84G 2.19M 2.83G  0% 1.00x ONLINE -

DEDUP 欄位會顯示儲存池的實際去重複率,數值為 1.00x 代表資料尚未被去重複。在下一個例子會在前面所建立的去重複儲存池中複製三份 Port 樹到不同的目錄。中。

# zpool list
for d in dir1 dir2 dir3; do
for> mkdir $d && cp -R /usr/ports $d &
for> done

已經偵測到重複的資料並做去重複:

# zpool list
NAME SIZE  ALLOC FREE CAP DEDUP HEALTH ALTROOT
pool 2.84G 20.9M 2.82G 0% 3.00x ONLINE -

DEDUP 欄位顯示有 3.00x 的去重複率,這代表已偵測到多份複製的 Port 樹資料並做了去重複的動作,且只會使用第三份資料所佔的空間。去重複能節省空間的潛力可以非常巨大,但會需要消耗大量的記憶體來持續追蹤去重複的資料區塊。

去重複並非總是有效益的,特別是當儲存池中的資料本身並沒有重複時。ZFS 可以透過在現有儲存池上模擬開啟去重複功能來顯示可能節省的空間:

# zdb -S pool
Simulated DDT histogram:

bucket              allocated                       referenced
______   ______________________________   ______________________________
refcnt   blocks   LSIZE   PSIZE   DSIZE   blocks   LSIZE   PSIZE   DSIZE
------   ------   -----   -----   -----   ------   -----   -----   -----
     1    2.58M    289G    264G    264G    2.58M    289G    264G    264G
     2     206K   12.6G   10.4G   10.4G     430K   26.4G   21.6G   21.6G
     4    37.6K    692M    276M    276M     170K   3.04G   1.26G   1.26G
     8    2.18K   45.2M   19.4M   19.4M    20.0K    425M    176M    176M
    16      174   2.83M   1.20M   1.20M    3.33K   48.4M   20.4M   20.4M
    32       40   2.17M    222K    222K    1.70K   97.2M   9.91M   9.91M
    64        9     56K   10.5K   10.5K      865   4.96M    948K    948K
   128        2   9.50K      2K      2K      419   2.11M    438K    438K
   256        5   61.5K     12K     12K    1.90K   23.0M   4.47M   4.47M
    1K        2      1K      1K      1K    2.98K   1.49M   1.49M   1.49M
 Total    2.82M    303G    275G    275G    3.20M    319G    287G    287G

dedup = 1.05, compress = 1.11, copies = 1.00, dedup * compress / copies = 1.16

zdb -S 分析完儲存池後會顯示在啟動去重複後可達到的空間減少比例。在本例中,1.16 是非常差的空間節省比例,因為這個比例使用壓縮功能便能達成。若在此儲存池上啟動去重複並不能明顯的節省空間使用量,那麼就不值得耗費大量的記憶體來開啟去重複功能。透過公式 ratio = dedup * compress / copies,系統管理者可以規劃儲存空間的配置,來判斷要處理的資料是否有足夠的重複資料區塊來平衡所需的記憶體。若資料是可壓縮的,那麼空間節少的效果可能會非常好,建議先開啟壓縮功能,且壓縮功能也可以大大提高效能。去重複功能只有在可以節省可觀的空間且有足夠的記憶體做 DDT 時才開啟。

19.4.12. ZFS 與 Jail

zfs jail 以及相關的 jailed 屬性可以用來將一個 ZFS 資料集委託給一個 Jail 管理。zfs jail jailid 可以將一個資料集連結到一個指定的 Jail,而 zfs unjail 則可解除連結。資料集要可以在 Jail 中控制需設定 jailed 屬性,一旦資料集被隔離便無法再掛載到主機,因為有掛載點可能會破壞主機的安全性。

本文及其他文件,可由此下載: ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/

若有 FreeBSD 方面疑問,請先閱讀 FreeBSD 相關文件,如不能解決的話,再洽詢 <questions@FreeBSD.org>。

關於本文件的問題,請洽詢 <doc@FreeBSD.org>。