Using ZFS as root file system of Debian GNU/Linux

最近資安事件頻傳,資安專家高聲疾呼重要資料備份的觀念。身為 GNU/Linux 使用者,除了手動拖曳檔案,我們有更多備份資料的選擇。恰巧上個月聽了社群友人對 ZFS 的介紹,決定花點時間玩看看。

對於資料備份與保存, ZFS 讓我感到最棒的一點就是近乎即時的快照功能。其他如內建 raid 0/1/5 (只是功能上近似,並非真的 raid) 和 checksum 都有助於避免資料流失。

ZFS 觀念簡單介紹

POOL

POOL 就是底層的儲存媒介,你可以把多個儲存設備或 partition 用類似 raid-0/1/5 的方式組成一個 POOL

POOL 決定了資料是怎麼存放在硬碟裡的:可以是 default (分散在各個硬碟裡,類似 raid0)、mirror (同時在多顆硬碟建立複本,類似 raid 1) 和 raidz (raid5 的變形)

做為簡單但不太精確的類比,你可以想像 POOL 就是把數個硬碟或 partition 組合成一個虛擬的硬碟

DATASET

套用上面的例子, DATASET 就是這個虛擬硬碟裡的 partition;但這個 partiton 預設會動態調整大小的,而且有紀錄自己的掛載點

實戰安裝 Debian GNU/Linux (With UEFI)

事前準備

要準備的東西其實不多: 一片 Debian live CD 或是 live USB stick。製作方式非本篇主題,請自行估狗。

實戰安裝

首先當然是先用剛剛準備好的 Live CD/USB 開機、登入並連上網路。開機時請選擇 「Live」

安裝必要的工具

Debian Live CD 預設並不會提供手動安裝系統和 ZFS 的工具,所以要先手動安裝

sudo apt-get update
sudo apt-get install -y zfsutils-linux debootstrap dosfstools
sudo modprobe zfs

建立 partition

以下假設系統有兩顆硬碟 /dev/sda 和 /dev/sdb,並使用 /dev/sda 開機

考慮到 UEFI 需要一個專用的 EFI system partition,加上把 initrd image 放在 ZFS 裡的話開機會慢很多,所以要把硬碟切出三份 partition

  • EFI system partition: UEFI boot 用,幾十 MB 就夠
  • ext4 partition: /boot 用,放置 kernel 和 initrd,也是幾十 MB 就夠用了
  • zfs 用
$ sudo fdisk /dev/sda
Welcome to fdisk (util-linux 2.29.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x4edad5ed.
# 建立 GPT 格式的 partition table
Command (m for help): g
Created a new GPT disklabel (GUID: 82CDE848-A34B-4C9C-B2E6-8195B74F014D).
# 建立 EFI system partition
Command (m for help): n
Partition number (1-128, default 1): 
First sector (2048-2097118, default 2048): 
Last sector, +sectors or +size{K,M,G,T,P} (2048-2097118, default 2097118): +50M
Created a new partition 1 of type 'Linux filesystem' and of size 50 MiB.
# 建立 /boot 用的 partition
Command (m for help): n
Partition number (2-128, default 2): 
First sector (104448-2097118, default 104448):  
Last sector, +sectors or +size{K,M,G,T,P} (104448-2097118, default 2097118): +100M
Created a new partition 2 of type 'Linux filesystem' and of size 100 MiB.
# 剩下的空間都給 zfs
Command (m for help): n
Partition number (3-128, default 3): 
First sector (309248-2097118, default 309248): 
Last sector, +sectors or +size{K,M,G,T,P} (309248-2097118, default 2097118): 
Created a new partition 3 of type 'Linux filesystem' and of size 873 MiB.
# EFI system partition 需要特殊的 partition type
Command (m for help): t
Partition number (1-3, default 3): 1
# 建議用 L 確認一下,寫此文的時候看到的是 1
Hex code (type L to list all codes): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.
Command (m for help): w
The partition table has been altered.
Syncing disks.

處理 EFI system partition

EFI 必須使用 fat32

sudo mkfs.fat -F 32 /dev/sda1

處理 /boot

使用一個安全穩定且 grub 內建支援的 file system 就好,這邊使用 ext4

sudo mkfs.ext4 /dev/sda2

建立 POOL

這邊建立的是 default 格式的,如果想使用其他方式,請自行 man zpool

sudo zpool create -o ashift=12 -o altroot=/mnt root /dev/sda3 /dev/sdb
  • 如果想把整顆硬碟都交給 ZFS 管理,就不需要建立 partition 了
  • root 是 POOL 的名稱,預設是 rpool,但為了後文展示一些設定的需要,所以刻意挑了自訂的名稱
  • ashift=12 是硬碟的 block size,應該要照硬碟的規格調整。這個值的調整需要對硬體有一定的知識;但考慮到一般 GNU/Linux 使用者都使用 4K block,所以這邊也以 4K 為準 (2^12 = 4096 = 4K,同理 ashift=13 就是 8K block)
  • altroot 屬性可以讓你錯開各個不同的 POOL 的掛載點,是 ZFS 維護與救援必備好物。這個屬性的值不會被紀錄下來

建立 DATASET

由於在建立快照的時候,有一些目錄我想排除在外,所以會先建立兩個 container DATASET 做區別

# 需要快照的 DATASET 會放在這裡面
sudo zfs create -o mountpoint=none -o canmount=off -o atime=on -o relatime=on root/inc
# 不需要的放這
sudo zfs create -o mountpoint=none -o canmount=off -o atime=on -o relatime=on root/exc
  • mountpointcanmount 告訴 ZFS 不要自動 mount 這兩個 DATASET
  • atimerelatime 讓 ZFS 為裡面的 DATASET 啟用 relatime,可以加快寫入速度

我是打算把 /var/log / /var/cache / /tmp / /var/tmp / /var/lib/apt/lists 這些東西排除,所以總共要建立六個 DATASET (加上一個 rootfs)

sudo zfs create -o mountpoint=/ root/inc/rootfs
# 為了安全,關閉 home 目錄的 dev 和 suid 權限
sudo zfs create -o mountpoint=/home -o devices=no -o setuid=no root/inc/home

root/inc 裡建立 root/inc/rootfs,並設定它將要掛載在 / (根目錄)。由於 POOL 的 altroot 是設定成 /mnt,所以這次會掛在 /mnt/

sudo zfs create -o mountpoint=/tmp root/exc/tmp
sudo zfs create -o mountpoint=/var/tmp root/exc/vartmp
sudo zfs create -o mountpoint=/var/cache root/exc/varcache
# log 完全不需要 atime,關閉可以提高更多效能
sudo zfs create -o mountpoint=/var/log -o atime=off root/exc/varlog
sudo zfs create -o mountpoint=/var/lib/apt/lists -o atime=off root/exc/aptlists

安裝基本的 Debian 系統

debootstrap 是手動安裝 Debian GNU/Linux 的最佳工具

sudo debootstrap stretch /mnt http://ftp.tw.debian.org/debian

你可以自行把版本 stretch 和 mirror site http://ftp.tw.debian.org/debian 換成你想要的版本/鏡像站

基本系統必要的設定

第一個要設定的就是 hostname

sudo cp /etc/host* /mnt/etc/
# 把預設的 hostname 換成你想要的 hostname
sudo vi /mnt/etc/hosts
sudo vi /mnt/etc/hostname

再來是 /boot 和 EFI 的掛載

# 把 /boot 的 UUID 放進 fstab
sudo blkid /dev/sda2|cut -d ' ' -f 2|sed 's/"//g' | sudo tee /mnt/etc/fstab
# 把 EFI system partition 的 UUID 放進 fstab
sudo blkid /dev/sda1|cut -d ' ' -f 2|sed 's/"//g' | sudo tee -a /mnt/etc/fstab
# 調整 fstab 的內容,把 EFI system partition 掛在 /boot/efi,並補上必要的參數
sudo vi /mnt/fstab

完成後的 fstab 大約會像這樣

UUID=00000000-0000-0000-0000-000000000000 /boot ext4 defaults,noatime 0 0
UUID=0000-0000 /boot/efi vfat defaults,noatime 0 0

chroot 進去安裝必要的系統

掛載必要的檔案系統

剛剛雖然寫了 fstab 但還沒真的掛起來,現在補掛

sudo mount /dev/sda2 /mnt/boot
sudo mkdir /mnt/boot/efi
sudo mount /dev/sda1 /mnt/boot/efi

再把 proc / sys / dev 這幾個裝東西必備的特殊目錄也掛上

sudo mount --rbind /proc /mnt/proc
sudo mount --rbind /sys /mnt/sys
sudo mount --rbind /dev /mnt/dev

因為會直接裝完整個系統,所以 /run 也要掛上

sudo mount -t tmpfs none /mnt/run

chroot 進去

chroot /mnt bash

安裝 grub / kernel 和其他必要的基本套件

dpkg-reconfigure tzdata
apt-get install -y grub-efi linux-image-amd64 locales build-essential zfs-initramfs zfsutils-linux zfsnap sudo
# 安裝 grub
grub-install --efi-directory=/boot/efi --no-urfi-secure-boot
# 設定開機載入 zfs module
echo 'zfs' >> /etc/initramfs-tools/modules
update-initramfs -u 
update-grub

修改預設的 zfs 工具設定

如果我們是用預設配置的話,這邊可以完全跳過。但正如開頭說的,我們為了好好介紹,所以幾乎完全沒有用預設配置。

vi /etc/defult/zfs
  • 首先要注意到的是被註解起來的 ZFS_IMPORT_PATH 這個設定,它影響了開機的時候,zfs 會依照什麼規則去找 POOL。我們用的是 /dev/sdX 這種路徑,顯然不符合它的規則,所以可以考慮修改。但因為我沒試過,所以本文不採用這種改法
  • 再來看下方不遠的 ZFS_POOL_IMPORT,如果我們剛剛把 POOL 取名成 rpool 的話,它自己就會 import 進來了;但我們剛剛用的名稱是 root,所以這邊要去修改它
  • 其他設定可以不去修改

安裝桌面環境及其他必要的套件

# 看你愛用哪個,其他的桌面環境安裝請自行用慣用的套件管理工具查詢
apt-get install -y task-gnome-desktop  # GNOME
apt-get install -y task-kde-desktop # KDE
apt-get install -y xxx-firmware ooo # 視需要安裝其他套件

新增自己的帳號

adduser your_username
# 不要忘了 sudo 權限
adduser your_username sudo

關機並移除 Live CD/USB,然後重開機進入新系統

關機前要先離開 chroot 環境

exit
poweroff

設定 zfsnap 自動快照

社群有人寫了 zfs-auto-snapshot 這個工具,其實比較好用,但並沒有收錄在 Debian official repository 裡。當然你可以自行安裝它,但本文會介紹另一個比較陽春但堪用的,有在 official repository 裡的工具。

安裝

sudo apt-get install zfsnap

設定

zfsnap 要配合 cron 使用

sudo vi /etc/cron.d/zfsnap

內容如下

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
SHELL=/bin/bash
# 每 15 分鐘拍一次快照,快照保存一週
*/15 * * * * root which zfSnap > /dev/null 2>&1 && zfSnap -a 1w -r root/inc
# 每小時刪除過期的快照
0 */1 * * * root which zfSnap > /dev/null 2>&1 && zfSnap -d

時間建議你可以自己調整,我只是隨便打個時間而已,不要傻呼呼的照抄啊…..

資料備份

資料備份不能再簡單了,直接提供 bash script 給各位自行修改

使用方式

  1. 適當修改以下的 script
  2. 插上你的備份碟,掛載到某個目錄下 (以下假設是 /tmp/zfsbak)
  3. 執行 script: sudo /path/to/script /tmp/zfsbak
  4. unmount 然後移除備份碟

SCRIPT 內容

#!/bin/bash
# 把你要備份的 POOL 寫在這裡 (用空白分隔)
POOLS="root"
if [[ `id -u` != "0" ]]
then
echo "Need root priviledge to backup"
exit 1
fi
if [[ "$1" == "" ]]
then
echo "Usage: $0 dir"
exit
fi
if [[ ! -d "$1" ]]
then
echo "Usage: $0 dir"
exit
fi
# create snapshot first
for i in $POOLS
do
snap="${i}@zfs-backup"
# remove old snapshot if exist
zfs destroy -r "$snap" > /dev/null 2>&1
zfs snapshot -r "$snap"
if [[ $? != 0 ]]
then
echo "Error creating snapshot, aborted"
exit 1
fi
done
echo ''
echo "Snapshot created"
echo ''
echo ''
# save snapshot and delete it
for i in $POOLS
do
snap="${i}@zfs-backup"
fn="$1/${i}.gz"
zfs send -R -v "$snap" | gzip > "$fn"
zfs destroy -r "$snap" > /dev/null 2>&1
done

這個 SCRIPT 做了什麼

  1. 拍下整個系統的快照
  2. zfs send 功能把快照輸出成一個 gzip 壓縮檔
  3. 刪除剛剛拍的快照

就只有這樣

如果只想備份 home 目錄

把 script 裡的 POOLS 的值改成 root/inc/home

如果只想備份 home 目錄和 /var/log

幹你媽的什麼爛需求 把 script 裡的 POOLS 的值改成 root/inc/home root/exc/varlog

怎麼還原

從備份碟還原

  1. 用 Live CD/USB 開機
  2. 照本文上面的步驟安裝 zfs 工具
  3. 插上並掛載備份碟
  4. 用 zfs 工具載入你的 POOL 的設定 sudo zpool import -N root
  5. 用 zfs 工具還原 gzip -cd /path/to/備份硬掛載目錄/root.gz | sudo zfs recv root (記得那個 root 要跟當初你寫在備份 script 裡的一樣)

從快照還原

我還沒試過從快照還原,但理論上應該還是離線還原比較好,所以建議先照上面 從備份碟還原 的步驟用 Live CD/USB 開機進入離線狀態

  1. 用 zfs 工具載入你的 POOL 的設定 sudo zpool import -N root
  2. 找到你要的 snapshot 名稱 sudo zfs list -t snapshot
  3. 還原它 sudo zfs rollback -R snapshot_name

Written by 

熱愛思辨,喜歡透過自我問答來找到可能的答案

Related posts

發表迴響