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硬盘

这里没有必要发明任何特别的东西。 我们将为所有内容创建一个部分。 我们将创建一个分区,因为 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硬盘

#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 提供了极大的灵活性。

PS

在下一篇文章中,我们将尝试使用几个类似的移动存储系统/KVM作为基础,使用家庭桌面、家庭互联网和P2P网络创建一个在几大洲具有冗余的地理分布式存储/虚拟机集群。

来源: habr.com

添加评论