LVM 和俄羅斯娃娃有什麼共通點?

今天好。
我想與社群分享我使用md RAID + LVM建置KVM資料儲存系統的實務經驗。

該計劃將包括:

  • 從 NVMe SSD 建置 md RAID 1。
  • 從 SATA SSD 和常規磁碟機組裝 md RAID 6。
  • SSD RAID 1/6 上 TRIM/DISCARD 操作的特性。
  • 在一組公共磁碟上建立可啟動的 md RAID 1/6 陣列。
  • 當 BIOS 中沒有 NVMe 支援時,在 NVMe RAID 1 上安裝系統。
  • 使用 LVM 快取和 LVM 精簡。
  • 使用 BTRFS 快照並傳送/接收進行備份。
  • 使用LVM精簡快照和thin_delta進行BTRFS樣式備份。

有興趣的話可以看看貓。

Заявление

作者對使用或不使用本文中的材料/範例/程式碼/提示/資料所造成的後果不承擔任何責任。 透過以任何方式閱讀或使用本資料,您將對這些行為的所有後果承擔責任。 可能的後果包括:

  • 炸脆的 NVMe SSD。
  • 記錄資源完全用完,SSD 硬碟故障。
  • 所有磁碟機上的所有資料(包括備份副本)完全遺失。
  • 電腦硬體有故障。
  • 浪費了時間、精力和金錢。
  • 以上未列出的任何其他後果。

可用的是:

2013 年左右的主機板,配備 Z87 晶片組,配備 Intel Core i7 / Haswell。

  • 處理器4核心8執行緒
  • 32 GB DDR3 內存
  • 1 個 16 或 2 個 8 PCIe 3.0
  • 1×4 + 1×1 PCIe 2.0
  • 6 x 6 GBps SATA 3 連接器

SAS 適配器 LSI SAS9211-8I 快閃記憶體至 IT/HBA 模式。 支援 RAID 的韌體已特意替換為 HBA 韌體,以便:

  1. 您可以隨時扔掉這個適配器,並用您遇到的任何其他適配器替換它。
  2. TRIM/Discard 在磁碟上正常運作,因為... 在 RAID 韌體中,根本不支援這些命令,而且 HBA 通常不關心透過匯流排傳輸哪些命令。

硬碟 - 8 台 HGST Travelstar 7K1000,容量為 1 TB,外形尺寸為 2.5,適用於筆記型電腦。 這些磁碟機先前位於 RAID 6 陣列中。 它們也將在新系統中發揮作用。 用於儲存本地備份。

另外補充:

6 塊 SATA SSD 型號三星 860 QVO 2TB。 這些 SSD 需要大容量、SLC 快取、可靠性和低廉的價格。 需要支援丟棄/清零,這是透過 dmesg 中的行檢查的:

kernel: ata1.00: Enabling discard_zeroes_data

2 塊 NVMe SSD 型號三星 SSD 970 EVO 500GB。

對於這些 SSD,滿足您需求的隨機讀取/寫入速度和資源容量非常重要。 他們的散熱器。 一定。 絕對地。 否則,在第一次 RAID 同步期間將它們炸至酥脆。

StarTech PEX8M2E2 轉接器,適用於安裝在 PCIe 2 3.0x 插槽中的 8 個 NVMe SSD。 再說一次,這只是一個 HBA,但適用於 NVMe。 它與廉價適配器的不同之處在於,由於內建 PCIe 交換器的存在,它不需要主機板的 PCIe 分叉支援。 即使是 x1 PCIe 1.0 插槽,它甚至可以在最古老的 PCIe 系統中運作。 當然,以適當的速度。 那裡沒有 RAID。 主機板上沒有內建BIOS。 因此,您的系統不會神奇地學會使用 NVMe 啟動,更不用說借助此裝置進行 NVMe RAID 啟動了。

該組件完全是由於系統中僅存在一個空閒的8x PCIe 3.0,並且,如果有2 個空閒插槽,則可以輕鬆地用兩便士的PEX4M2E1 或類似物替換,這些類似物可以在任何地方以600 美元的價格購買盧布。

故意拒絕各種硬體或內建晶片組/BIOS RAID,以便能夠完全取代整個系統(SSD/HDD 本身除外),同時保留所有資料。 理想情況下,這樣您在遷移到全新/不同的硬體時甚至可以保存已安裝的作業系統。 最主要的是有SATA和PCIe連接埠。 它就像 Live CD 或可啟動閃存驅動器,只是速度非常快且體積有點大。

Юмор否則,你知道會發生什麼事——有時你迫切需要把整個陣列帶走。 但我不想丟失資料。 為此,所有提到的介質都方便地位於標準機殼 5.25 托架中的幻燈片上。

當然,也可以在 Linux 中嘗試不同的 SSD 快取方法。

硬件攻擊很無聊。 打開它。 它要么有效,要么無效。 使用 mdadm 總有多種選擇。

軟件

先前,硬體上安裝的是 Debian 8 Jessie,該版本已接近 EOL。 RAID 6 由上述 HDD 與 LVM 組合而成。 它在 kvm/libvirt 中運行虛擬機器。

因為作者在創建便攜式可啟動SATA/NVMe閃存驅動器方面有相當的經驗,並且為了不打破通常的apt模板,選擇了Ubuntu 18.04作為目標系統,該系統已經足夠穩定,但仍然有3年的時間未來的支持。

上述系統包含我們開箱即用所需的所有硬體驅動程式。 我們不需要任何第三方軟體或驅動程式。

準備安裝

要安裝系統,我們需要 Ubuntu 桌面映像。 伺服器系統有某種強大的安裝程序,表現出過度的獨立性,無法透過將 UEFI 系統分區推到其中一個磁碟來停用它,從而破壞了所有的美觀。 因此,它僅在 UEFI 模式下安裝。 不提供任何選項。

我們對此並不滿意。

為什麼呢?不幸的是,UEFI啟動與啟動軟體RAID的兼容性極差,因為... 沒有人為我們提供 UEFI ESP 分割區的預留。 網路上有一些建議將 ESP 分割區放置在 USB 連接埠的隨身碟上,但這是一個失敗點。 有一些使用元資料版本1 的軟體mdadm RAID 0.9 的方法不會阻止UEFI BIOS 看到此分區,但是這種情況會持續到BIOS 或另一個硬體作業系統向ESP 寫入某些內容並忘記將其同步到其他硬體作業系統的快樂時刻。鏡子。

另外,UEFI啟動依賴NVRAM,NVRAM不會隨著磁碟一起移動到新系統,因為是主機板的一部分。

所以,我們不會重新發明一個新輪子。 我們已經有了一輛現成的、經過時間考驗的祖父自行車,現在稱為 Legacy/BIOS boot,在 UEFI 相容系統上帶有 CSM 的自豪名稱。 我們只需將其從架子上取下來,潤滑它,給輪胎打氣並用濕布擦拭即可。

Ubuntu 的桌面版本也無法使用舊版開機載入程式正確安裝,但正如他們所說,至少有一些選項。

因此,我們收集硬體並從 Ubuntu Live 可啟動快閃磁碟機載入系統。 我們需要下載軟體包,因此我們將設定適合您的網路。 如果不行,您可以提前將必要的套件載入到隨身碟上。

我們進入桌面環境,啟動終端模擬器,然後開始:

#sudo bash

如何…?上面的行是有關 sudo 的 holiwars 的典型觸發器。 Cbо更大的機會即將到來о更大的責任。 問題是你能不能自己承擔。 很多人認為這樣使用sudo至少是不小心的。 然而:

#apt-get install mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc

為什麼不是ZFS...?當我們在電腦上安裝軟體時,我們實質上是將硬體借給該軟體的開發人員來驅動。
當我們相信這個軟體能夠確保我們資料的安全時,我們就會申請一筆相當於恢復這些資料成本的貸款,總有一天我們必須還清這筆貸款。

從這一點來看,ZFS是一輛法拉利,而mdad​​m+lvm更像是一輛自行車。

主觀上,作者更願意將自行車賒借給不知名的人,而不是法拉利。 在那裡,發行的價格並不高。 不需要權利。 比交通規則更簡單。 停車免費。 越野能力較好。 您可以隨時為自行車裝上腿,並且可以用自己的雙手修理自行車。

那為什麼 BTRFS...?為了啟動作業系統,我們需要一個在 Legacy/BIOS GRUB 中開箱即用的支援的檔案系統,同時支援即時快照。 我們將把它用於 /boot 分割區。 此外,作者更喜歡將這個 FS 用於 /(root),不要忘記注意,對於任何其他軟體,您可以在 LVM 上建立單獨的分割區並將它們掛載到必要的目錄中。

我們不會在此 FS 上儲存任何虛擬機器或資料庫的映像。
此 FS 僅用於在不關閉系統的情況下建立系統快照,然後使用傳送/接收將這些快照傳輸到備份磁碟。

此外,作者通常更喜歡直接在硬體上保留最少的軟體,並使用透過 IOMMU 將 GPU 和 PCI-USB 主機控制器轉送到 KVM 等方式在虛擬機器中執行所有其他軟體。

硬體上唯一剩下的就是資料儲存、虛擬化和備份。

如果您更信任 ZFS,那麼原則上,對於指定的應用程序,它們是可以互換的。

然而,作者刻意忽略了ZFS、BRTFS和LVM所具有的內建鏡像/RAID和冗餘特性。

作為一個附加參數,BTRFS 能夠將隨機寫入轉換為順序寫入,這對 HDD 上同步快照/備份的速度具有極為正面的影響。

讓我們重新掃描所有設備:

#udevadm control --reload-rules && udevadm trigger

讓我們環顧四周:

#lsscsi && nvme list
[0:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdn
Node SN Model Namespace Usage Format FW Rev
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1 S466NXXXXXXX15L Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7
/dev/nvme1n1 S5H7NXXXXXXX48N Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7

磁碟佈局

NVMe SSD

但我們不會以任何方式標記它們。 儘管如此,我們的 BIOS 卻看不到這些驅動器。 因此,他們將完全採用軟體 RAID。 我們甚至不會在那裡創建部分。 如果您想遵循“規範”或“原則”,請建立一個大分割區,例如 HDD。

SATA HDD

這裡沒有必要發明任何特別的東西。 我們將為所有內容創建一個部分。 我們將建立一個分割區,因為 BIOS 可以看到這些磁碟,甚至可能嘗試從它們啟動。 我們稍後甚至會在這些磁碟上安裝 GRUB,以便系統可以突然執行此操作。

#cat >hdd.part << EOF
label: dos
label-id: 0x00000000
device: /dev/sdg
unit: sectors

/dev/sdg1 : start= 2048, size= 1953523120, type=fd, bootable
EOF
#sfdisk /dev/sdg < hdd.part
#sfdisk /dev/sdh < hdd.part
#sfdisk /dev/sdi < hdd.part
#sfdisk /dev/sdj < hdd.part
#sfdisk /dev/sdk < hdd.part
#sfdisk /dev/sdl < hdd.part
#sfdisk /dev/sdm < hdd.part
#sfdisk /dev/sdn < hdd.part

SATA SSD

這就是我們感興趣的地方。

首先,我們的驅動器大小為 2 TB。 這在我們將使用的 MBR 的可接受範圍內。 如果需要,可以替換為GPT。 GPT 磁碟有一個相容層,允許 MBR 相容系統看到前 4 個分割區(如果它們位於前 2 TB 內)。 最主要的是這些磁碟上的boot分割區和bios_grub分割區應該在開頭。 這甚至允許您從 GPT Legacy/BIOS 驅動器啟動。

但這不是我們的情況。

在這裡我們將創建兩個部分。 第一個大小為 1 GB,用於 RAID 1 /boot。

第二個將用於 RAID 6,並將佔用磁碟機末端的一小塊未分配區域之外的所有剩餘可用空間。

這個未標記的區域是什麼?根據網路訊息,我們的 SATA SSD 內建可動態擴展的 SLC 緩存,大小從 6 GB 到 78 GB 不等。 由於驅動器資料表中「千兆位元組」和「吉比位元組」之間的差異,我們「免費」獲得 6 GB。 剩餘的 72 GB 是從未使用的空間中分配的。

這裡要注意的是,我們有一個SLC緩存,並且在4位元MLC模式下佔用空間。 對我們來說,這實際上意味著每 4 GB 的可用空間我們只能獲得 1 GB 的 SLC 快取。

將 72 GB 乘以 4,得到 288 GB。 這是我們不會標記的可用空間,以便驅動器能夠充分利用 SLC 快取。

因此,我們將從總共 312 個驅動器中有效地獲得高達 2 GB 的 SLC 快取。 所有磁碟機中,有 XNUMX 個將用於 RAID 中以實現冗餘。

這樣的快取量將使我們在現實生活中極少遇到寫入不進入快取的情況。 這很好地彌補了 QLC 記憶體最悲慘的缺點——繞過快取寫入資料時寫入速度極低。 如果您的負載與此不符,那麼我建議您仔細考慮您的 SSD 在這種負載下能持續多久,同時考慮資料表中的 TBW。

#cat >ssd.part << EOF
label: dos
label-id: 0x00000000
device: /dev/sda
unit: sectors

/dev/sda1 : start= 2048, size= 2097152, type=fd, bootable
/dev/sda2 : start= 2099200, size= 3300950016, type=fd
EOF
#sfdisk /dev/sda < ssd.part
#sfdisk /dev/sdb < ssd.part
#sfdisk /dev/sdc < ssd.part
#sfdisk /dev/sdd < ssd.part
#sfdisk /dev/sde < ssd.part
#sfdisk /dev/sdf < ssd.part

建立數組

首先,我們需要重命名機器。 這是必要的,因為主機名稱是 mdadm 內某處陣列名稱的一部分,並且會影響某處的某些內容。 當然,數組可以稍後重命名,但這是一個不必要的步驟。

#mcedit /etc/hostname
#mcedit /etc/hosts
#hostname
vdesk0

NVMe SSD

#mdadm --create --verbose --assume-clean /dev/md0 --level=1 --raid-devices=2 /dev/nvme[0-1]n1

為什麼-假設-乾淨...?避免初始化數組。 對於 RAID 等級 1 和 6,這都有效。 如果它是一個新數組,那麼一切都可以在沒有初始化的情況下工作。 此外,在建立SSD陣列時對其進行初始化會浪費TBW資源。 我們盡可能在組裝的 SSD 陣列上使用 TRIM/DISCARD 來「初始化」它們。

對於 SSD 陣列,開箱即支援 RAID 1 DISCARD。

對於 SSD RAID 6 DISCARD 陣列,您必須在核心模組參數中啟用它。

只有當該系統中 4/5/6 層陣列中使用的所有 SSD 都支援 Discard_zeroes_data 時,才應執行此操作。 有時你會遇到奇怪的驅動器,告訴核心支援此功能,但實際上它不存在,或者該功能並不總是起作用。 目前,幾乎所有地方都提供支持,但是,有些舊驅動器和韌體存在錯誤。 因此,預設情況下,RAID 6 會停用 DISCARD 支援。

注意,以下命令將透過以「零」「初始化」陣列來銷毀 NVMe 磁碟機上的所有資料。

#blkdiscard /dev/md0

如果出現問題,請嘗試指定步驟。

#blkdiscard --step 65536 /dev/md0

SATA SSD

#mdadm --create --verbose --assume-clean /dev/md1 --level=1 --raid-devices=6 /dev/sd[a-f]1
#blkdiscard /dev/md1
#mdadm --create --verbose --assume-clean /dev/md2 --chunk-size=512 --level=6 --raid-devices=6 /dev/sd[a-f]2

為什麼這麼大…?增加區塊大小對區塊大小(含區塊大小)內的區塊的隨機讀取速度有正面影響。 發生這種情況是因為適當大小或更小的操作可以完全在單一裝置上完成。 因此,所有設備的 IOPS 都會被匯總。 根據統計,99%的IO不超過512K。

RAID 6 每次寫入 IOPS 總是 小於或等於一個磁碟機的 IOPS。 作為隨機讀取,IOPS 可能比一個驅動器大幾倍,此時區塊大小至關重要。
作者認為嘗試優化 RAID 6 設計中不好的參數沒有意義,而是優化了 RAID 6 擅長的參數。
我們將透過 NVMe 快取和精簡配置技巧來補償 RAID 6 較差的隨機寫入效能。

我們還沒有為 RAID 6 啟用 DISCARD。所以我們暫時不會「初始化」這個陣列。 我們稍後會在安裝作業系統後執行此操作。

SATA HDD

#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1

NVMe RAID 上的 LVM

為了提高速度,我們希望將根檔案系統放置在 NVMe RAID 1 上,即 /dev/md0。
然而,我們仍然需要這個快速陣列來滿足其他需求,例如交換、元數據和 LVM 快取以及 LVM 精簡元數據,因此我們將在此陣列上建立一個 LVM VG。

#pvcreate /dev/md0
#vgcreate root /dev/md0

讓我們為根檔案系統建立一個分割區。

#lvcreate -L 128G --name root root

讓我們根據 RAM 的大小建立一個用於交換的分區。

#lvcreate -L 32G --name swap root

作業系統安裝

總的來說,我們擁有安裝系統所需的一切。

從 Ubuntu Live 環境啟動系統安裝精靈。 正常安裝。 僅在選擇安裝磁碟階段,您需要指定以下內容:

  • /dev/md1, - 掛載點 /boot, FS - BTRFS
  • /dev/root/root(又稱 /dev/mapper/root-root),- 掛載點 /(根),FS - BTRFS
  • /dev/root/swap (又稱 /dev/mapper/root-swap), - 用作交換分區
  • 在 /dev/sda 上安裝引導程式

當您選擇 BTRFS 作為根檔案系統時,安裝程式將自動建立兩個 BTRFS 卷,其中 /(根)名為“@”,/home 名為“@home”。

讓我們開始安裝...

安裝結束時將出現一個模式對話框,指示安裝開機載入程式時發生錯誤。 不幸的是,您將無法使用標準方法退出此對話方塊並繼續安裝。 我們登出系統並再次登錄,最終得到一個乾淨的 Ubuntu Live 桌面。 打開終端,然後再次:

#sudo bash

創建chroot環境以繼續安裝:

#mkdir /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@ /dev/mapper/root-root /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@home /dev/mapper/root-root /mnt/chroot/home
#mount -o defaults,space_cache,noatime,nodiratime,discard /dev/md1 /mnt/chroot/boot
#mount --bind /proc /mnt/chroot/proc
#mount --bind /sys /mnt/chroot/sys
#mount --bind /dev /mnt/chroot/dev

讓我們在 chroot 中設定網路和主機名稱:

#cat /etc/hostname >/mnt/chroot/etc/hostname
#cat /etc/hosts >/mnt/chroot/etc/hosts
#cat /etc/resolv.conf >/mnt/chroot/etc/resolv.conf

我們進入chroot環境:

#chroot /mnt/chroot

首先,我們將交付包裹:

apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm

讓我們來檢查並修復所有因係統安裝不完整而安裝錯誤的軟體包:

#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES

如果出現問題,您可能需要先編輯 /etc/apt/sources.list

讓我們調整 RAID 6 模組的參數以啟用 TRIM/DISCARD:

#cat >/etc/modprobe.d/raid456.conf << EOF
options raid456 devices_handle_discard_safely=1
EOF

讓我們稍微調整一下我們的陣列:

#cat >/etc/udev/rules.d/60-md.rules << EOF
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/stripe_cache_size", ATTR{md/stripe_cache_size}="32768"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_min", ATTR{md/sync_speed_min}="48000"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_max", ATTR{md/sync_speed_max}="300000"
EOF
#cat >/etc/udev/rules.d/62-hdparm.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/hdparm -B 254 /dev/%k"
EOF
#cat >/etc/udev/rules.d/63-blockdev.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/blockdev --setra 1024 /dev/%k"
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="md*", RUN+="/sbin/blockdev --setra 0 /dev/%k"
EOF

它以前如何..?我們建立了一組 udev 規則,將執行以下操作:

  • 將 RAID 2020 的區塊快取大小設定為足以應付 6 年。預設值似乎自 Linux 建立以來就沒有改變,並且在很長一段時間內都不夠用。
  • 在陣列檢查/同步期間保留最少的 IO。 這是為了防止您的陣列在負載下陷入永久同步狀態。
  • 限制陣列檢查/同步期間的最大 IO。 這是必要的,這樣同步/檢查 SSD RAID 就不會將您的磁碟機燒毀。 對於 NVMe 來說尤其如此。 (還記得散熱器嗎?我不是開玩笑。)
  • 透過 APM 禁止磁碟停止主軸旋轉 (HDD),並將磁碟控制器的睡眠逾時設定為 7 小時。 如果您的磁碟機可以的話,您可以完全停用 APM (-B 255)。 使用預設值時,驅動器將在五秒後停止。 然後作業系統想要重置磁碟緩存,磁碟將再次旋轉,一切都將重新開始。 光碟的最大主軸轉數是有限的。 這樣一個簡單的預設週期很容易在幾年內耗盡你的磁碟。 並非所有磁碟都會遇到此問題,但我們的磁碟是「筆記型電腦」磁碟,具有適當的預設設置,這使得 RAID 看起來像一個迷你 MAID。
  • 在磁碟上安裝預讀(旋轉)1 MB - 兩個連續的區塊/區塊 RAID 6
  • 禁用陣列本身的預讀。

讓我們編輯/etc/fstab:

#cat >/etc/fstab << EOF
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
# file-system mount-point type options dump pass
/dev/mapper/root-root / btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@ 0 1
UUID=$(blkid -o value -s UUID /dev/md1) /boot btrfs defaults,space_cache,noatime,nodiratime,discard 0 2
/dev/mapper/root-root /home btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@home 0 2
/dev/mapper/root-swap none swap sw 0 0
EOF

這是為什麼..?我們將透過 UUID 搜尋 /boot 分割區。 理論上,數組命名可以改變。

我們將透過 /dev/mapper/vg-lv 符號中的 LVM 名稱搜尋剩餘部分,因為他們非常獨特地識別分區。

我們不將 UUID 用於 LVM,因為LVM磁碟區及其快照的UUID可以相同。掛載 /dev/mapper/root-root.. 兩次?是的。 確切地。 BTRFS 的特點。 此檔案系統可以使用不同的子磁碟區多次安裝。

由於相同的功能,我建議永遠不要建立活動 BTRFS 磁碟區的 LVM 快照。 當你重新啟動時,你可能會得到一個驚喜。

讓我們重新產生 mdadm 配置:

#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf

讓我們調整 LVM 設定:

#cat >>/etc/lvm/lvmlocal.conf << EOF

activation {
thin_pool_autoextend_threshold=90
thin_pool_autoextend_percent=5
}
allocation {
cache_pool_max_chunks=2097152
}
devices {
global_filter=["r|^/dev/.*_corig$|","r|^/dev/.*_cdata$|","r|^/dev/.*_cmeta$|","r|^/dev/.*gpv$|","r|^/dev/images/.*$|","r|^/dev/mapper/images.*$|","r|^/dev/backup/.*$|","r|^/dev/mapper/backup.*$|"] issue_discards=1
}
EOF

它以前如何..?當磁碟區的 90% 達到佔用空間的 5% 時,我們啟用了 LVM 精簡池的自動擴充。

我們增加了 LVM 快取的最大快取區塊數。

我們已阻止 LVM 在以下位置搜尋 LVM 磁碟區 (PV):

  • 包含 LVM 快取 (cdata) 的設備
  • 使用 LVM 快取進行快取的設備,繞過快取( _corig)。 在這種情況下,快取設備本身仍然會透過快取進行掃描(只是)。
  • 包含 LVM 快取元資料 (cmeta) 的設備
  • VG 中名為 images 的所有裝置。 這裡我們將擁有虛擬機器的磁碟映像,我們也不希望主機上的 LVM 啟動屬於來賓作業系統的磁碟區。
  • VG 中名為 backup 的所有設備。 在這裡,我們將擁有虛擬機器映像的備份副本。
  • 所有名稱以“gpv”(來賓物理卷)結尾的設備

在釋放 LVM VG 上的可用空間時,我們啟用了 DISCARD 支援。 當心。 這將使刪除 SSD 上的 LV 變得相當耗時。 這尤其適用於SSD RAID 6。不過,根據計劃,我們將使用精簡配置,所以這不會妨礙我們。

讓我們更新 initramfs 映像:

#update-initramfs -u -k all

安裝並配置 grub:

#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc

選擇哪些磁碟?所有的人都是SD*。 系統必須能夠從任何工作的 SATA 驅動器或 SSD 啟動。

他們為什麼要加 os-prober..?適合過度獨立和俏皮的雙手。

如果其中一個 RAID 處於降級狀態,則它無法正常工作。 它嘗試在該硬體上運行的虛擬機器所使用的分區上搜尋作業系統。

如果您需要它,可以保留它,但請記住以上所有內容。 我建議在網路上尋找擺脫頑皮手的食譜。

至此我們就完成了初始安裝。 是時候重新啟動到新安裝的作業系統了。 不要忘記移除可啟動 Live CD/USB。

#exit
#reboot

選擇任意 SATA SSD 作為啟動設備。

SATA SSD 上的 LVM

此時,我們已經啟動到新作業系統,設定網路、apt,開啟終端模擬器,並啟動:

#sudo bash

我們繼續吧。

從 SATA SSD“初始化”陣列:

#blkdiscard /dev/md2

如果不起作用,請嘗試:

#blkdiscard --step 65536 /dev/md2
在SATA SSD上建立LVM VG:

#pvcreate /dev/md2
#vgcreate data /dev/md2

為什麼還要另一個VG..?事實上,我們已經有一個名為 root 的 VG。 為什麼不將所有內容都加入到一個 VG 中呢?

如果 VG 中有多個 PV,則為了正確啟動 VG,所有 PV 都必須存在(線上)。 LVM RAID 是個例外,我們故意不使用它。

我們確實希望,如果任何 RAID 6 陣列發生故障(讀取資料遺失),作業系統將正常啟動,讓我們有機會解決問題。

為此,在第一個抽象級別,我們將把每種類型的實體「媒體」隔離到一個單獨的 VG 中。

從科學上講,不同的RAID陣列屬於不同的「可靠性域」。 您不應該將它們塞進一個 VG 中,從而為它們製造額外的常見故障點。

LVM 在「硬體」層級的存在將允許我們透過以不同的方式組合不同的 RAID 陣列來任意切割它們。 例如 - 運行 同時 bcache + LVM 瘦、bcache + BTRFS、LVM 快取 + LVM 瘦、帶有緩存的複雜 ZFS 配置,或任何其他地獄般的混合物來嘗試比較它們。

在「硬體」級別,除了舊的「厚」LVM 磁碟區之外,我們不會使用任何其他東西。 此規則的例外可能是備份分割區。

我想此時此刻,很多讀者已經開始對這個嵌套娃娃產生懷疑了。

SATA HDD 上的 LVM

#pvcreate /dev/md3
#vgcreate backup /dev/md3

又是新的VG..?我們確實希望,如果用於資料備份的磁碟陣列發生故障,我們的作業系統將繼續正常運作,同時保持對非備份資料的存取照常進行。 因此,為了避免VG啟動問題,我們創建一個單獨的VG。

設定LVM緩存

讓我們在 NVMe RAID 1 上建立一個 LV 以將其用作快取設備。

#lvcreate -L 70871154688B --name cache root

怎麼這麼少啊…事實上,我們的 NVMe SSD 也有 SLC 快取。 由於 4 位元 MLC 中佔用的可用空間,因此有 18 GB 的「空閒」空間和 3 GB 的動態空間。 一旦快取耗盡,NVMe SSD 的速度不會比我們有快取的 SATA SSD 快多少。 實際上,基於這個原因,我們將LVM快取分區設定為遠大於NVMe磁碟機SLC快取大小的兩倍是沒有意義的。 對於所使用的NVMe驅動器,筆者認為製作32-64GB的快取是合理的。

組織 64 GB 的快取、快取元資料和元資料備份需要給定的分割區大小。

此外,我注意到,在髒系統關閉後,LVM 會將整個快取標記為髒並再次同步。 此外,每次在該裝置上使用 lvchange 時都會重複此操作,直到系統再次重新啟動。 因此,我建議立即使用適當的腳本重新建立快取。

讓我們在 SATA RAID 6 上建立一個 LV 以將其用作快取設備。

#lvcreate -L 3298543271936B --name cache data

為什麼只有三太位元組..?因此,如有必要,您可以使用 SATA SSD RAID 6 來滿足其他一些需求。 快取空間的大小可以動態地、即時地增加,而無需停止系統。 為此,您需要暫時停止並重新啟用緩存,但 LVM 快取相對於 bcache 等的獨特優勢在於,這可以即時完成。

讓我們建立一個新的 VG 用於快取。

#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache

讓我們在快取設備上建立一個 LV。

#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache

在這裡,我們立即佔用了 /dev/data/cache 上的所有可用空間,以便立即在 /dev/root/cache 上建立所有其他必要的分割區。 如果您在錯誤的位置創建了某些內容,您可以使用 pvmove 移動它。

讓我們創建並啟用快取:

#lvcreate -y -L 64G -n cache cache /dev/root/cache
#lvcreate -y -L 1G -n cachemeta cache /dev/root/cache
#lvconvert -y --type cache-pool --cachemode writeback --chunksize 64k --poolmetadata cache/cachemeta cache/cache
#lvconvert -y --type cache --cachepool cache/cache cache/cachedata

為什麼這麼大的塊大小..?透過實際實驗,作者發現,LVM快取區塊的大小與LVM精簡區塊的大小一致時,可以獲得最佳效果。 此外,尺寸越小,該配置在隨機記錄中的表現越好。

64k 是 LVM Thin 允許的最小區塊大小。

小心寫回..!是的。 這種類型的快取推遲了對快取設備的寫入同步。 這意味著如果快取遺失,您可能會丟失快取裝置上的資料。 稍後筆者會告訴大家,除了NVMe RAID 1之外,還可以採取哪些措施來彌補這個風險。

特意選擇這種快取類型是為了彌補 RAID 6 較差的隨機寫入效能。

讓我們檢查一下我們得到了什麼:

#lvs -a -o lv_name,lv_size,devices --units B cache
LV LSize Devices
[cache] 68719476736B cache_cdata(0)
[cache_cdata] 68719476736B /dev/root/cache(0)
[cache_cmeta] 1073741824B /dev/root/cache(16384)
cachedata 3298539077632B cachedata_corig(0)
[cachedata_corig] 3298539077632B /dev/data/cache(0)
[lvol0_pmspare] 1073741824B /dev/root/cache(16640)

只有 [cachedata_corig] 應該位於 /dev/data/cache 上。 如果出現問題,請使用 pvmove。

如有必要,您可以使用一個命令停用快取:

#lvconvert -y --uncache cache/cachedata

這是在線完成的。 LVM 將簡單地將快取同步到磁碟、將其刪除,並將cachedata_corig 重新命名回cachedata。

設定 LVM 瘦

讓我們粗略估計一下 LVM 精簡元資料需要多少空間:

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 bytes estimated metadata area size for "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"

四捨五入為 4 GB:4294967296B

乘以 4194304 並加入 8594128896B,LVM PV 元資料:XNUMXB
讓我們在 NVMe RAID 1 上建立一個單獨的分割區來放置 LVM 精簡元資料及其備份副本:

#lvcreate -L 8594128896B --name images root

為了什麼..?這裡可能會出現一個問題:如果 LVM 精簡元資料仍然緩存在 NVMe 上並且可以快速工作,為什麼還要單獨放置它?

雖然速度在這裡很重要,但這遠遠不是主要原因。 問題是快取是一個故障點。 它可能會發生一些事情,如果 LVM 精簡元資料被緩存,就會導致所有內容完全遺失。 如果沒有完整的元數據,幾乎不可能組裝精簡卷。

透過將元資料移至單獨的非快取但快速的捲,我們可以在快取遺失或損壞的情況下保證元資料的安全。 在這種情況下,由快取遺失造成的所有損壞都將集中在精簡卷內,這將大大簡化恢復過程。 這些損壞很可能會使用 FS 日誌來復原。

而且,如果先前對精簡磁碟區進行了快照,並且之後快取至少完全同步一次,那麼,由於LVM Thin的內部設計,在快取遺失的情況下,快照的完整性將得到保證。

讓我們建立一個新的 VG 來負責精簡配置:

#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata

讓我們建立一個池:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
為什麼-Zy除了該模式的實際用途(防止重新分配空間時一個虛擬機器的資料洩漏到另一台虛擬機器)之外,清零還用於提高小於 64k 的區塊中隨機寫入的速度。 對精簡磁碟區之前未分配區域的任何小於 64K 的寫入都會在快取中變為 64K 邊緣對齊。 這將允許操作完全透過快取執行,繞過快取設備。

讓我們將 LV 移到對應的 PV:

#pvmove -n images/thin-pool_tdata /dev/root/images /dev/cache/cachedata
#pvmove -n images/lvol0_pmspare /dev/cache/cachedata /dev/root/images
#pvmove -n images/thin-pool_tmeta /dev/cache/cachedata /dev/root/images

讓我們檢查:

#lvs -a -o lv_name,lv_size,devices --units B images
LV LSize Devices
[lvol0_pmspare] 4294967296B /dev/root/images(0)
thin-pool 274877906944B thin-pool_tdata(0)
[thin-pool_tdata] 274877906944B /dev/cache/cachedata(0)
[thin-pool_tmeta] 4294967296B /dev/root/images(1024)

讓我們建立一個用於測試的精簡卷:

#lvcreate -V 64G --thin-pool thin-pool --name test images

我們將安裝用於測試和監控的軟體包:

#apt-get install sysstat fio

您可以透過以下方式即時觀察我們的儲存配置的行為:

#watch 'lvs --rows --reportformat basic --quiet -ocache_dirty_blocks,cache_settings cache/cachedata && (lvdisplay cache/cachedata | grep Cache) && (sar -p -d 2 1 | grep -E "sd|nvme|DEV|md1|md2|md3|md0" | grep -v Average | sort)'

這是我們測試配置的方法:

#fio --loops=1 --size=64G --runtime=4 --filename=/dev/images/test --stonewall --ioengine=libaio --direct=1
--name=4kQD32read --bs=4k --iodepth=32 --rw=randread
--name=8kQD32read --bs=8k --iodepth=32 --rw=randread
--name=16kQD32read --bs=16k --iodepth=32 --rw=randread
--name=32KQD32read --bs=32k --iodepth=32 --rw=randread
--name=64KQD32read --bs=64k --iodepth=32 --rw=randread
--name=128KQD32read --bs=128k --iodepth=32 --rw=randread
--name=256KQD32read --bs=256k --iodepth=32 --rw=randread
--name=512KQD32read --bs=512k --iodepth=32 --rw=randread
--name=4Kread --bs=4k --rw=read
--name=8Kread --bs=8k --rw=read
--name=16Kread --bs=16k --rw=read
--name=32Kread --bs=32k --rw=read
--name=64Kread --bs=64k --rw=read
--name=128Kread --bs=128k --rw=read
--name=256Kread --bs=256k --rw=read
--name=512Kread --bs=512k --rw=read
--name=Seqread --bs=1m --rw=read
--name=Longread --bs=8m --rw=read
--name=Longwrite --bs=8m --rw=write
--name=Seqwrite --bs=1m --rw=write
--name=512Kwrite --bs=512k --rw=write
--name=256write --bs=256k --rw=write
--name=128write --bs=128k --rw=write
--name=64write --bs=64k --rw=write
--name=32write --bs=32k --rw=write
--name=16write --bs=16k --rw=write
--name=8write --bs=8k --rw=write
--name=4write --bs=4k --rw=write
--name=512KQD32write --bs=512k --iodepth=32 --rw=randwrite
--name=256KQD32write --bs=256k --iodepth=32 --rw=randwrite
--name=128KQD32write --bs=128k --iodepth=32 --rw=randwrite
--name=64KQD32write --bs=64k --iodepth=32 --rw=randwrite
--name=32KQD32write --bs=32k --iodepth=32 --rw=randwrite
--name=16KQD32write --bs=16k --iodepth=32 --rw=randwrite
--name=8KQD32write --bs=8k --iodepth=32 --rw=randwrite
--name=4kQD32write --bs=4k --iodepth=32 --rw=randwrite
| grep -E 'read|write|test' | grep -v ioengine

小心! 資源!程式碼將執行 36 個不同的測試,每個測試運行 4 秒。 一半的測試是為了記錄。 您可以在 4 秒內在 NVMe 上記錄大量內容。 每秒高達 3 GB。 因此,每次執行寫入測試最多可消耗 216 GB 的 SSD 資源。

閱讀和寫作混在一起?是的。 單獨運行讀取和寫入測試是有意義的。 此外,確保所有快取同步是有意義的,以便先前進行的寫入不會影響讀取。

當快取和精簡磁碟區填滿時,第一次啟動期間和後續啟動期間的結果會有很大差異,並且還取決於系統是否設法同步上次啟動期間填充的快取。

除此之外,我建議測量剛拍攝快照的已滿精簡捲的速度。 作者有機會觀察到隨機寫入在創建第一個快照後如何立即急劇加速,尤其是在快取尚未完全滿的情況下。 發生這種情況的原因是寫入時複製寫入語義、快取和精簡卷區塊的對齊,以及對 RAID 6 的隨機寫入變成從 RAID 6 的隨機讀取,然後寫入快取的事實。 在我們的配置中,從 RAID 6 隨機讀取的速度比寫入快 6 倍(陣列中 SATA SSD 的數量)。 因為CoW 的區塊是從精簡池中順序分配的,然後記錄大部分也變成順序的。

這兩個功能都可以為您帶來優勢。

快取“一致”快照

為了降低快取損壞/遺失時資料遺失的風險,作者建議引入輪換快照的做法,以確保這種情況下的完整性。

首先,由於精簡卷元資料駐留在未快取的裝置上,因此元資料將是一致的,並且可能的遺失將被隔離在資料區塊內。

以下快照輪換週期可在快取遺失的情況下保證快照內資料的完整性:

  1. 對於每個名為 <name> 的精簡卷,建立一個名為 <name>.cached 的快照
  2. 讓我們將遷移閾值設定為一個合理的高值: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. 在循環中我們檢查快取中髒塊的數量: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' 直到我們得到零。 如果零遺失時間過長,可以透過暫時將快取切換到直寫模式來建立零。 然而,考慮到我們的 SATA 和 NVMe SSD 陣列的速度特性以及它們的 TBW 資源,您要么能夠在不更改快取模式的情況下快速抓住時機,要么您的硬體將完全耗盡其全部資源幾天。 由於資源限制,系統原則上不可能一直處於100%的寫入負載。 我們的 NVMe SSD 在 100% 寫入負載下將完全耗盡資源 3-4天。 SATA SSD 的使用壽命只有兩倍。 因此,我們假設大部分負載用於讀取,並且我們有相對短期的極高活動突發和平均較低的寫入負載。
  4. 一旦我們捕獲(或製造)了零,我們就將 <name>.cached 重新命名為 <name>.comfilled。 舊的 <name>.commissed 將會被刪除。
  5. 或者,如果快取已 100% 滿,則可以透過腳本重新建立它,從而清除它。 在半空快取的情況下,系統在寫入時工作得更快。
  6. 將遷移閾值設為零: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata 這將暫時阻止快取與主媒體同步。
  7. 我們等到快取中累積了相當多的更改 #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' 否則計時器將會關閉。
  8. 我們再重複一遍。

為什麼移民門檻有困難......?問題是,在實際實踐中,「隨機」錄音實際上並不是完全隨機的。 如果我們向 4 KB 大小的扇區寫入內容,則很可能在接下來的幾分鐘內向同一扇區或相鄰 (+- 32K) 扇區之一寫入記錄。

透過將遷移閾值設為零,我們推遲了 SATA SSD 上的寫入同步,並將多項變更聚合到快取中的一個 64K 區塊。 這大大節省了SATA SSD的資源。

代碼在哪裡..?不幸的是,作者認為自己在bash腳本的開發方面能力不足,因為他是100%自學並實踐「google」驅動的開發,因此他認為出自他之手的糟糕程式碼不應該被任何人使用別的。

我認為這個領域的專業人士將能夠在必要時獨立描述上述所有邏輯,甚至可能將其精美地設計為一個 systemd 服務,正如作者試圖做的那樣。

這種簡單的快照輪換方案不僅使我們能夠不斷地在 SATA SSD 上完全同步一個快照,而且還使我們能夠使用 Thin_Delta 實用程式找出哪些區塊在創建後發生了更改,從而定位損壞主卷,大大簡化了恢復。

libvirt/KVM 中的 TRIM/DISCARD

因為資料儲存將用於運行 libvirt 的 KVM,那麼教會我們的 VM 不僅要佔用可用空間,還要釋放不再需要的空間,這將是一個好主意。

這是透過在虛擬磁碟上模擬 TRIM/DISCARD 支援來完成的。 為此,您需要將控制器類型變更為 virtio-scsi 並編輯 xml。

#virsh edit vmname
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='writethrough' io='threads' discard='unmap'/>
<source dev='/dev/images/vmname'/>
<backingStore/>
<target dev='sda' bus='scsi'/>
<alias name='scsi0-0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>

<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</controller>

LVM 可以正確處理來自來賓作業系統的此類 DISCARD,並且在快取和精簡池中都可以正確釋放區塊。 在我們的例子中,這主要是在刪除下一個快照時以延遲的方式發生的。

BTRFS備份

使用現成的腳本 科拉伊內伊 謹慎和 風險自擔。 該程式碼是作者自己編寫的,並且是專門為自己編寫的。 我相信很多有經驗的 Linux 使用者都有類似的工具,沒有必要複製別人的。

讓我們在備份設備上建立一個磁碟區:

#lvcreate -L 256G --name backup backup

讓我們將其格式化為 BTRFS:

#mkfs.btrfs /dev/backup/backup

讓我們建立掛載點並掛載檔案系統的根部分:

#mkdir /backup
#mkdir /backup/btrfs
#mkdir /backup/btrfs/root
#mkdir /backup/btrfs/back
#ln -s /boot /backup/btrfs
# cat >>/etc/fstab << EOF

/dev/mapper/root-root /backup/btrfs/root btrfs defaults,space_cache,noatime,nodiratime 0 2
/dev/mapper/backup-backup /backup/btrfs/back btrfs defaults,space_cache,noatime,nodiratime 0 2
EOF
#mount -a
#update-initramfs -u
#update-grub

讓我們建立備份目錄:

#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot

讓我們為備份腳本建立一個目錄:

#mkdir /root/btrfs-backup

讓我們來複製一下腳本:

很多可怕的 bash 程式碼。 使用風險自負。 不要寫憤怒的信給作者...#cat >/root/btrfs-backup/btrfs-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".@base"
PEND_SUFFIX=".@pend"
SNAP_SUFFIX=".@snap"
MOUNTS="/backup/btrfs/"
BACKUPS="/backup/btrfs/back/remote/"

function terminate ()
{
echo "$1" >&2
exit 1
}

function wait_lock()
{
flock 98
}

function wait_lock_or_terminate()
{
echo "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function backup()
{
SOURCE_PATH="$MOUNTS$1"
TARGET_PATH="$BACKUPS$1"
SOURCE_BASE_PATH="$MOUNTS$1$BASE_SUFFIX"
TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
TARGET_BASE_DIR="$(dirname $TARGET_BASE_PATH)"
SOURCE_PEND_PATH="$MOUNTS$1$PEND_SUFFIX"
TARGET_PEND_PATH="$BACKUPS$1$PEND_SUFFIX"
if [ -d "$SOURCE_BASE_PATH" ] then
echo "$SOURCE_BASE_PATH found"
else
echo "$SOURCE_BASE_PATH File not found creating snapshot of $SOURCE_PATH to $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found out of sync with source... removing..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found"
else
echo "$TARGET_BASE_PATH not found. Synching to $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
if [ -d "$SOURCE_PEND_PATH" ] then
echo "$SOURCE_PEND_PATH found removing..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
if [ -d "$TARGET_PEND_PATH" ] then
echo "$TARGET_PEND_PATH found removing..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Sending $SOURCE_PEND_PATH to $TARGET_PEND_PATH"
btrfs send -p $SOURCE_BASE_PATH $SOURCE_PEND_PATH | btrfs receive $TARGET_BASE_DIR
sync
TARGET_DATE_SUFFIX=$(suffix)
btrfs subvolume snapshot -r $TARGET_PEND_PATH "$TARGET_PATH$TARGET_DATE_SUFFIX"
sync
btrfs subvolume delete -c $SOURCE_BASE_PATH
sync
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
mv $SOURCE_PEND_PATH $SOURCE_BASE_PATH
mv $TARGET_PEND_PATH $TARGET_BASE_PATH
sync
}

function list()
{
LIST_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
LIST_TARGET_BASE_DIR="$(dirname $LIST_TARGET_BASE_PATH)"
LIST_TARGET_BASE_NAME="$(basename -s .$BASE_SUFFIX $LIST_TARGET_BASE_PATH)"
find "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
REMOVE_TARGET_BASE_DIR="$(dirname $REMOVE_TARGET_BASE_PATH)"
btrfs subvolume delete -c $REMOVE_TARGET_BASE_DIR/$2
sync
}

function removeall()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$1" "$SNAPSHOT"
done < <(list "$1" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Help"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1"
;;
"list")
list "$1"
;;
"remove")
wait_lock_or_terminate
remove "$1" "$2"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2"
;;
*)
echo "None.."
;;
esac
) 98>$LOCK_FILE

EOF

它到底有什麼作用..?包含一組簡單命令,用於建立 BTRFS 快照並使用 BTRFS 發送/接收將其複製到另一個 FS。

第一次啟動可能會相對較長,因為... 一開始,所有資料都會被複製。 進一步的發射將會非常快,因為...... 僅複製變更。

我們將放入 cron 的另一個腳本:

更多 bash 程式碼#cat >/root/btrfs-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/btrfs-backup.sh"
RETENTION="-60 day"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENTION"
EOF

它有什麼作用..?建立並同步備份 FS 上列出的 BTRFS 磁碟區的增量快照。 此後,它會刪除 60 天前創建的所有圖片。 啟動後,所列磁碟區的日期快照將顯示在 /backup/btrfs/back/remote/ 子目錄中。

讓我們賦予程式碼執行權限:

#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh

讓我們檢查一下並將其放入 cron 中:

#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup
#cat /var/log/syslog | grep btrfs-backup
#crontab -e
0 2 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup

LVM精簡備份

讓我們在備份設備上建立一個精簡池:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool

讓我們安裝 ddrescue,因為... 腳本將使用此工具:

#apt-get install gddrescue

讓我們為腳本建立一個目錄:

#mkdir /root/lvm-thin-backup

讓我們複製腳本:

裡面有很多bash...#cat >/root/lvm-thin-backup/lvm-thin-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".base"
PEND_SUFFIX=".pend"
SNAP_SUFFIX=".snap"
BACKUPS="backup"
BACKUPS_POOL="thin-pool"

export LVM_SUPPRESS_FD_WARNINGS=1

function terminate ()
{
echo "$1" >&2
exit 1
}

function wait_lock()
{
flock 98
}

function wait_lock_or_terminate()
{
echo "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function read_thin_id {
lvs --rows --reportformat basic --quiet -othin_id "$1/$2" | awk '{print $2}'
}

function read_pool_lv {
lvs --rows --reportformat basic --quiet -opool_lv "$1/$2" | awk '{print $2}'
}

function read_lv_dm_path {
lvs --rows --reportformat basic --quiet -olv_dm_path "$1/$2" | awk '{print $2}'
}

function read_lv_active {
lvs --rows --reportformat basic --quiet -olv_active "$1/$2" | awk '{print $2}'
}

function read_lv_chunk_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -ochunk_size "$1/$2" | awk '{print $2}'
}

function read_lv_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -olv_size "$1/$2" | awk '{print $2}'
}

function activate_volume {
lvchange -ay -Ky "$1/$2"
}

function deactivate_volume {
lvchange -an "$1/$2"
}

function read_thin_metadata_snap {
dmsetup status "$1" | awk '{print $7}'
}

function thindiff()
{
DIFF_VG="$1"
DIFF_SOURCE="$2"
DIFF_TARGET="$3"
DIFF_SOURCE_POOL=$(read_pool_lv $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_POOL=$(read_pool_lv $DIFF_VG $DIFF_TARGET)

if [ "$DIFF_SOURCE_POOL" == "" ] then
(>&2 echo "Source LV is not thin.")
exit 1
fi

if [ "$DIFF_TARGET_POOL" == "" ] then
(>&2 echo "Target LV is not thin.")
exit 1
fi

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ] then
(>&2 echo "Source and target LVs belong to different thin pools.")
exit 1
fi

DIFF_POOL_PATH=$(read_lv_dm_path $DIFF_VG $DIFF_SOURCE_POOL)
DIFF_SOURCE_ID=$(read_thin_id $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_ID=$(read_thin_id $DIFF_VG $DIFF_TARGET)
DIFF_POOL_PATH_TPOOL="$DIFF_POOL_PATH-tpool"
DIFF_POOL_PATH_TMETA="$DIFF_POOL_PATH"_tmeta
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" != "-" ] then
(>&2 echo "Thin pool metadata snapshot already exist. Assuming stale one. Will release metadata snapshot in 5 seconds.")
sleep 5
dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap
fi

dmsetup message $DIFF_POOL_PATH_TPOOL 0 reserve_metadata_snap
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" == "-" ] then
(>&2 echo "Failed to create thin pool metadata snapshot.")
exit 1
fi

#We keep output in variable because metadata snapshot need to be released early.
DIFF_DATA=$(thin_delta -m$DIFF_POOL_METADATA_SNAP --snap1 $DIFF_SOURCE_ID --snap2 $DIFF_TARGET_ID $DIFF_POOL_PATH_TMETA)

dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap

echo $"$DIFF_DATA" | grep -E 'different|left_only|right_only' | sed 's/</"/g' | sed 's/ /"/g' | awk -F'"' '{print $6 "t" $8 "t" $11}' | sed 's/different/copy/g' | sed 's/left_only/copy/g' | sed 's/right_only/discard/g'

}

function thinsync()
{
SYNC_VG="$1"
SYNC_PEND="$2"
SYNC_BASE="$3"
SYNC_TARGET="$4"
SYNC_PEND_POOL=$(read_pool_lv $SYNC_VG $SYNC_PEND)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SYNC_VG $SYNC_PEND_POOL)
SYNC_PEND_PATH=$(read_lv_dm_path $SYNC_VG $SYNC_PEND)

activate_volume $SYNC_VG $SYNC_PEND

while read -r SYNC_ACTION SYNC_OFFSET SYNC_LENGTH ; do
SYNC_OFFSET_BYTES=$((SYNC_OFFSET * SYNC_BLOCK_SIZE))
SYNC_LENGTH_BYTES=$((SYNC_LENGTH * SYNC_BLOCK_SIZE))
if [ "$SYNC_ACTION" == "copy" ] then
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SYNC_PEND_PATH" "$SYNC_TARGET"
fi

if [ "$SYNC_ACTION" == "discard" ] then
blkdiscard -o $SYNC_OFFSET_BYTES -l $SYNC_LENGTH_BYTES "$SYNC_TARGET"
fi
done < <(thindiff "$SYNC_VG" "$SYNC_PEND" "$SYNC_BASE")
}

function discard_volume()
{
DISCARD_VG="$1"
DISCARD_LV="$2"
DISCARD_LV_PATH=$(read_lv_dm_path "$DISCARD_VG" "$DISCARD_LV")
if [ "$DISCARD_LV_PATH" != "" ] then
echo "$DISCARD_LV_PATH found"
else
echo "$DISCARD_LV not found in $DISCARD_VG"
exit 1
fi
DISCARD_LV_POOL=$(read_pool_lv $DISCARD_VG $DISCARD_LV)
DISCARD_LV_SIZE=$(read_lv_size "$DISCARD_VG" "$DISCARD_LV")
lvremove -y --quiet "$DISCARD_LV_PATH" || exit 1
lvcreate --thin-pool "$DISCARD_LV_POOL" -V "$DISCARD_LV_SIZE"B --name "$DISCARD_LV" "$DISCARD_VG" || exit 1
}

function backup()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
SOURCE_PEND_LV="$SOURCE_LV$PEND_SUFFIX"
TARGET_PEND_LV="$TARGET_LV$PEND_SUFFIX"
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "Source base not found creating snapshot of $SOURCE_VG/$SOURCE_LV to $SOURCE_VG/$SOURCE_BASE_LV"
lvcreate --quiet --snapshot --name "$SOURCE_BASE_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo "Discarding $SOURCE_BASE_LV_PATH as we need to bootstrap."
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
sync
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found out of sync with source... removing..."
lvremove -y --quiet $TARGET_BASE_LV_PATH || exit 1
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
sync
fi
fi
SOURCE_BASE_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_VG/$TARGET_LV not found. Creating empty volume."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Have to rebootstrap. Discarding source at $SOURCE_BASE_LV_PATH"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
TARGET_BASE_POOL=$(read_pool_lv $TARGET_VG $TARGET_BASE_LV)
TARGET_BASE_CHUNK_SIZE=$(read_lv_chunk_size $TARGET_VG $TARGET_BASE_POOL)
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
echo "Discarding target at $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ] then
echo "$SOURCE_PEND_LV_PATH found removing..."
lvremove -y --quiet "$SOURCE_PEND_LV_PATH" || exit 1
sync
fi
lvcreate --quiet --snapshot --name "$SOURCE_PEND_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
sync
if [ "$TARGET_PEND_LV_PATH" != "" ] then
echo "$TARGET_PEND_LV_PATH found removing..."
lvremove -y --quiet $TARGET_PEND_LV_PATH
sync
fi
lvcreate --quiet --snapshot --name "$TARGET_PEND_LV" "$TARGET_VG/$TARGET_BASE_LV" || exit 1
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")
SOURCE_PEND_LV_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_PEND_LV")
lvresize -L "$SOURCE_PEND_LV_SIZE"B "$TARGET_PEND_LV_PATH"
activate_volume "$TARGET_VG" "$TARGET_PEND_LV"
echo "Synching $SOURCE_PEND_LV_PATH to $TARGET_PEND_LV_PATH"
thinsync "$SOURCE_VG" "$SOURCE_PEND_LV" "$SOURCE_BASE_LV" "$TARGET_PEND_LV_PATH" || exit 1
sync

TARGET_DATE_SUFFIX=$(suffix)
lvcreate --quiet --snapshot --name "$TARGET_LV$TARGET_DATE_SUFFIX" "$TARGET_VG/$TARGET_PEND_LV" || exit 1
sync
lvremove --quiet -y "$SOURCE_BASE_LV_PATH" || exit 1
sync
lvremove --quiet -y "$TARGET_BASE_LV_PATH" || exit 1
sync
lvrename -y "$SOURCE_VG/$SOURCE_PEND_LV" "$SOURCE_BASE_LV" || exit 1
lvrename -y "$TARGET_VG/$TARGET_PEND_LV" "$TARGET_BASE_LV" || exit 1
sync
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function verify()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Comparing "$SOURCE_BASE_LV_PATH" with "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function resync()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)

echo Syncronizing "$SOURCE_BASE_LV_PATH" to "$TARGET_BASE_LV_PATH"

CMP_OFFSET=0
while [[ "$CMP_OFFSET" != "" ]] ; do
CMP_MISMATCH=$(cmp -i "$CMP_OFFSET" "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH" | grep differ | awk '{print $5}' | sed 's/,//g' )
if [[ "$CMP_MISMATCH" != "" ]] ; then
CMP_OFFSET=$(( CMP_MISMATCH + CMP_OFFSET ))
SYNC_OFFSET_BYTES=$(( ( CMP_OFFSET / SYNC_BLOCK_SIZE ) * SYNC_BLOCK_SIZE ))
SYNC_LENGTH_BYTES=$(( SYNC_BLOCK_SIZE ))
echo "Synching $SYNC_LENGTH_BYTES bytes at $SYNC_OFFSET_BYTES from $SOURCE_BASE_LV_PATH to $TARGET_BASE_LV_PATH"
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
else
CMP_OFFSET=""
fi
done
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function list()
{
LIST_SOURCE_VG="$1"
LIST_SOURCE_LV="$2"
LIST_TARGET_VG="$BACKUPS"
LIST_TARGET_LV="$LIST_SOURCE_VG-$LIST_SOURCE_LV"
LIST_TARGET_BASE_LV="$LIST_TARGET_LV$SNAP_SUFFIX"
lvs -olv_name | grep "$LIST_TARGET_BASE_LV.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}

function removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$SNAPSHOT"
done < <(list "$1" "$2" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Help"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1" "$2"
;;
"list")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verify")
wait_lock_or_terminate
verify "$1" "$2"
;;
"resync")
wait_lock_or_terminate
resync "$1" "$2"
;;
"remove")
wait_lock_or_terminate
remove "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "None.."
;;
esac
) 98>$LOCK_FILE

EOF

它有什麼作用...?包含一組命令,用於操作精簡快照並使用 ddrescue 和 blkdiscard 將透過 Thin_Delta 接收的兩個精簡快照之間的差異同步到另一個區塊裝置。

我們將在 cron 中放入另一個腳本:

再猛烈一點#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 days"

$BACKUP_SCRIPT backup images linux-dev
$BACKUP_SCRIPT backup images win8
$BACKUP_SCRIPT backup images win8-data
#etc

$BACKUP_SCRIPT removeall images linux-dev "$RETENTION"
$BACKUP_SCRIPT removeall images win8 "$RETENTION"
$BACKUP_SCRIPT removeall images win8-data "$RETENTION"
#etc

EOF

它有什麼作用...?使用前面的腳本建立和同步列出的精簡卷的備份。 該腳本將留下列出的捲的非活動快照,這些快照是追蹤自上次同步以來的變更所必需的。

必須編輯此腳本,指定應為其建立備份副本的精簡磁碟區清單。 給出的名稱僅用於說明目的。 如果您願意,您可以編寫一個腳本來同步所有磁碟區。

讓我們賦予權利:

#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh

讓我們檢查一下並將其放入 cron 中:

#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup
#cat /var/log/syslog | grep lvm-thin-backup
#crontab -e
0 3 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup

第一次發射將會很長,因為...... 精簡磁碟區將透過複製所有已使用空間來完全同步。 感謝 LVM 精簡元數據,我們知道哪些區塊實際在使用中,因此只有實際使用的精簡卷塊才會被複製。

由於透過 LVM 精簡元資料進行更改跟踪,後續運行將增量複製資料。

讓我們來看看發生了什麼:

#time /root/btrfs-backup/cron-daily.sh
real 0m2,967s
user 0m0,225s
sys 0m0,353s

#time /root/lvm-thin-backup/cron-daily.sh
real 1m2,710s
user 0m12,721s
sys 0m6,671s

#ls -al /backup/btrfs/back/remote/*
/backup/btrfs/back/remote/boot:
total 0
drwxr-xr-x 1 root root 1260 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 322 мар 26 02:00 .@base
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 240 мар 26 09:11 @.@base
drwxr-xr-x 1 root root 22 мар 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
...
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
...

#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV LSize
linux-dev 128,00g
linux-dev.base 128,00g
thin-pool 1,38t
win8 128,00g
win8-data 2,00t
win8-data.base 2,00t
win8.base 128,00g
LV LSize
backup 256,00g
images-linux-dev.base 128,00g
images-linux-dev.snap.2020-03-08-10-09-11 128,00g
images-linux-dev.snap.2020-03-08-10-09-25 128,00g
...
images-win8-data.base 2,00t
images-win8-data.snap.2020-03-16-14-11-55 2,00t
images-win8-data.snap.2020-03-16-14-19-50 2,00t
...
images-win8.base 128,00g
images-win8.snap.2020-03-17-04-51-46 128,00g
images-win8.snap.2020-03-18-03-02-49 128,00g
...
thin-pool <2,09t

這和嵌套娃娃有什麼關係?

最有可能的是,考慮到 LVM LV 邏輯磁碟區可以是其他 VG 的 LVM PV 實體磁碟區。 LVM 可以是遞歸的,就像嵌套娃娃一樣。 這為 LVM 提供了極大的靈活性。

聚苯乙烯

在下一篇文章中,我們將嘗試使用幾個類似的行動儲存系統/KVM作為基礎,使用家庭桌面、家庭網路和P2P網路在幾大洲創建具有冗餘的地理分散式儲存/虛擬機器叢集。

來源: www.habr.com

添加評論