¿Qué tienen en común LVM y Matryoshka?

Buen día.
Me gustaría compartir con la comunidad mi experiencia práctica en la construcción de un sistema de almacenamiento de datos para KVM utilizando md RAID + LVM.

El programa incluirá:

  • Construyendo md RAID 1 desde NVMe SSD.
  • Montaje de md RAID 6 a partir de SSD SATA y unidades normales.
  • Características de la operación TRIM/DISCARD en SSD RAID 1/6.
  • Creación de una matriz md RAID 1/6 de arranque en un conjunto común de discos.
  • Instalar el sistema en NVMe RAID 1 cuando no hay soporte NVMe en el BIOS.
  • Usando caché LVM y LVM delgado.
  • Usar instantáneas BTRFS y enviar/recibir para respaldo.
  • Uso de instantáneas delgadas de LVM y Thin_delta para copias de seguridad de estilo BTRFS.

Si está interesado, consulte cat.

declaración

El autor no asume ninguna responsabilidad por las consecuencias del uso o no uso de materiales/ejemplos/códigos/consejos/datos de este artículo. Al leer o utilizar este material de cualquier forma, usted asume la responsabilidad de todas las consecuencias de estas acciones. Las posibles consecuencias incluyen:

  • SSD NVMe fritos crujientes.
  • Recursos de grabación completamente agotados y fallas de las unidades SSD.
  • Pérdida completa de todos los datos en todas las unidades, incluidas las copias de seguridad.
  • Hardware informático defectuoso.
  • Tiempo, nervios y dinero perdidos.
  • Cualesquiera otras consecuencias que no estén enumeradas anteriormente.

hierro

Disponibles fueron:

Placa base de aproximadamente 2013 con chipset Z87, completa con Intel Core i7 / Haswell.

  • Procesador 4 núcleos, 8 hilos
  • 32 GB de RAM DDR3
  • 1 x 16 o 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 conectores SATA 6 de 3 GB/s

El adaptador SAS LSI SAS9211-8I pasó al modo IT/HBA. El firmware habilitado para RAID se reemplazó intencionalmente con firmware HBA para:

  1. Puedes tirar este adaptador en cualquier momento y reemplazarlo por cualquier otro que encuentres.
  2. TRIM/Discard funcionó normalmente en los discos, porque... en el firmware RAID estos comandos no son compatibles en absoluto y al HBA, en general, no le importa qué comandos se transmiten a través del bus.

Discos duros: 8 unidades HGST Travelstar 7K1000 con una capacidad de 1 TB en un formato de 2.5, como para portátiles. Estas unidades estaban anteriormente en una matriz RAID 6. También tendrán un uso en el nuevo sistema. Para almacenar copias de seguridad locales.

Agregado adicionalmente:

6 piezas SSD SATA modelo Samsung 860 QVO 2TB. Estos SSD requerían un gran volumen, se deseaba la presencia de un caché SLC, confiabilidad y un precio bajo. Se requirió soporte para descarte/cero, lo cual se verifica en la línea en dmesg:

kernel: ata1.00: Enabling discard_zeroes_data

2 unidades de SSD NVMe modelo Samsung SSD 970 EVO 500GB.

Para estos SSD, la velocidad de lectura/escritura aleatoria y la capacidad de recursos para sus necesidades son importantes. Radiador para ellos. Necesariamente. Absolutamente. De lo contrario, fríelos hasta que estén crujientes durante la primera sincronización RAID.

Adaptador StarTech PEX8M2E2 para 2 x NVMe SSD instalado en ranura PCIe 3.0 8x. Esto, nuevamente, es solo un HBA, pero para NVMe. Se diferencia de los adaptadores económicos en que no requiere soporte de bifurcación PCIe de la placa base debido a la presencia de un conmutador PCIe incorporado. Funcionará incluso en el sistema más antiguo con PCIe, incluso si se trata de una ranura PCIe 1 x1.0. Naturalmente, a la velocidad adecuada. Allí no hay redadas. No hay BIOS integrado a bordo. Por lo tanto, su sistema no aprenderá mágicamente a arrancar con NVMe y mucho menos a hacer NVMe RAID gracias a este dispositivo.

Este componente se debió únicamente a la presencia de un solo PCIe 8 3.0x libre en el sistema y, si hay 2 ranuras libres, se puede reemplazar fácilmente con dos centavos PEX4M2E1 o análogos, que se pueden comprar en cualquier lugar a un precio de 600 rublos.

El rechazo de todo tipo de hardware o RAID de chipset/BIOS incorporado se hizo deliberadamente, para poder reemplazar completamente todo el sistema, con la excepción de los propios SSD/HDD, conservando todos los datos. Idealmente, para que pueda guardar incluso el sistema operativo instalado al pasar a un hardware completamente nuevo/diferente. Lo principal es que hay puertos SATA y PCIe. Es como un CD en vivo o una unidad flash de arranque, sólo que muy rápido y un poco voluminoso.

humorDe lo contrario, ya sabe lo que sucede: a veces es necesario llevarse urgentemente toda la matriz para llevársela. Pero no quiero perder datos. Para hacer esto, todos los medios mencionados están convenientemente ubicados en las diapositivas de las bahías 5.25 del estuche estándar.

Bueno, y, por supuesto, para experimentar con diferentes métodos de almacenamiento en caché SSD en Linux.

Las incursiones de hardware son aburridas. Encenderlo. O funciona o no. Y con mdadm siempre hay opciones.

Suave

Anteriormente, se instaló Debian 8 Jessie en el hardware, que está cerca del EOL. RAID 6 se ensambló a partir de los discos duros mencionados anteriormente combinados con LVM. Ejecutó máquinas virtuales en kvm/libvirt.

Porque El autor tiene experiencia adecuada en la creación de unidades flash SATA/NVMe de arranque portátiles y, además, para no romper la plantilla apt habitual, se eligió como sistema de destino Ubuntu 18.04, que ya está suficientemente estabilizado, pero aún tiene 3 años de funcionamiento. apoyo en el futuro.

El sistema mencionado contiene todos los controladores de hardware que necesitamos listos para usar. No necesitamos ningún software ni controladores de terceros.

Preparación para la instalación

Para instalar el sistema necesitamos Ubuntu Desktop Image. El sistema del servidor tiene una especie de instalador potente, lo que demuestra una independencia excesiva que no se puede desactivar colocando la partición del sistema UEFI en uno de los discos, estropeando toda la belleza. En consecuencia, se instala solo en modo UEFI. No ofrece ninguna opción.

No estamos contentos con esto.

¿Por qué?Desafortunadamente, el arranque UEFI es extremadamente poco compatible con el software de arranque RAID, porque... Nadie nos ofrece reservas para la partición UEFI ESP. Hay recetas en línea que sugieren colocar la partición ESP en una unidad flash en un puerto USB, pero este es un punto de falla. Hay recetas que utilizan el software mdadm RAID 1 con metadatos versión 0.9 que no impiden que UEFI BIOS vea esta partición, pero esto dura hasta el feliz momento en que el BIOS u otro sistema operativo de hardware escribe algo en el ESP y se olvida de sincronizarlo con otros. espejos.

Además, el arranque UEFI depende de la NVRAM, que no se moverá junto con los discos al nuevo sistema, porque es parte de la placa base.

Por tanto, no reinventaremos una nueva rueda. Ya tenemos una bicicleta de abuelo lista para usar y probada en el tiempo, ahora llamada bota Legacy/BIOS, que lleva el orgulloso nombre de CSM en sistemas compatibles con UEFI. Simplemente lo sacaremos del estante, lo lubricaremos, inflaremos los neumáticos y lo limpiaremos con un paño húmedo.

La versión de escritorio de Ubuntu tampoco se puede instalar correctamente con el gestor de arranque Legacy, pero aquí, como dicen, al menos hay opciones.

Y así, recopilamos el hardware y cargamos el sistema desde la unidad flash de arranque de Ubuntu Live. Necesitaremos descargar paquetes, por lo que configuraremos la red que funcione para usted. Si no funciona, puedes cargar los paquetes necesarios en una unidad flash con antelación.

Entramos en el entorno de Escritorio, lanzamos el emulador de terminal y listo:

#sudo bash

Cómo…?La línea de arriba es el desencadenante canónico de holiwars sobre sudo. C bоmayores oportunidades vienen yоmayor responsabilidad. La pregunta es si puedes asumirlo tú mismo. Mucha gente piensa que usar sudo de esta manera al menos no es cuidadoso. Sin embargo:

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

¿Por qué no ZFS...?Cuando instalamos software en nuestra computadora, esencialmente prestamos nuestro hardware a los desarrolladores de este software para que lo manejen.
Cuando confiamos a este software la seguridad de nuestros datos, solicitamos un préstamo equivalente al costo de restaurar estos datos, que tendremos que pagar algún día.

Desde este punto de vista, ZFS es un Ferrari y mdadm+lvm se parece más a una bicicleta.

Subjetivamente, el autor prefiere prestar una bicicleta a crédito a desconocidos en lugar de un Ferrari. Allí el precio de la emisión no es elevado. No hay necesidad de derechos. Más sencillo que las normas de tráfico. El aparcamiento es gratuito. La capacidad para cruzar el país es mejor. Siempre puedes colocar patas en una bicicleta y puedes repararla con tus propias manos.

¿Por qué entonces BTRFS...?Para iniciar el sistema operativo, necesitamos un sistema de archivos que sea compatible con Legacy/BIOS GRUB listo para usar y que al mismo tiempo admita instantáneas en vivo. Lo usaremos para la partición /boot. Además, el autor prefiere usar este FS para / (root), sin olvidar señalar que para cualquier otro software puede crear particiones separadas en LVM y montarlas en los directorios necesarios.

No almacenaremos ninguna imagen de máquinas virtuales o bases de datos en este FS.
Este FS solo se usará para crear instantáneas del sistema sin apagarlo y luego transferir estas instantáneas a un disco de respaldo mediante enviar/recibir.

Además, el autor generalmente prefiere mantener un mínimo de software directamente en el hardware y ejecutar todo el resto del software en máquinas virtuales usando cosas como reenviar GPU y controladores de host PCI-USB a KVM a través de IOMMU.

Lo único que queda en el hardware es el almacenamiento de datos, la virtualización y la copia de seguridad.

Si confía más en ZFS, entonces, en principio, para la aplicación especificada son intercambiables.

Sin embargo, el autor ignora deliberadamente las funciones integradas de duplicación/RAID y redundancia que tienen ZFS, BRTFS y LVM.

Como argumento adicional, BTRFS tiene la capacidad de convertir escrituras aleatorias en secuenciales, lo que tiene un efecto extremadamente positivo en la velocidad de sincronización de instantáneas/copias de seguridad en el disco duro.

Volvamos a escanear todos los dispositivos:

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

Echemos un vistazo a nuestro alrededor:

#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

Diseño del disco

NVMe SSD

Pero no los marcaremos de ninguna manera. De todos modos, nuestro BIOS no ve estas unidades. Entonces, pasarán completamente al RAID por software. Ni siquiera crearemos secciones allí. Si desea seguir el "canon" o "principalmente", cree una partición grande, como un disco duro.

HDD SATA

No es necesario inventar nada especial aquí. Crearemos una sección para todo. Crearemos una partición porque el BIOS ve estos discos e incluso puede intentar arrancar desde ellos. Incluso instalaremos GRUB en estos discos más adelante para que el sistema pueda hacer esto de repente.

#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

Aquí es donde las cosas se ponen interesantes para nosotros.

En primer lugar, nuestras unidades tienen un tamaño de 2 TB. Esto está dentro del rango aceptable para MBR, que es lo que usaremos. Si es necesario, se puede reemplazar con GPT. Los discos GPT tienen una capa de compatibilidad que permite a los sistemas compatibles con MBR ver las primeras 4 particiones si están ubicadas dentro de los primeros 2 terabytes. Lo principal es que la partición de arranque y la partición bios_grub de estos discos deben estar al principio. Esto incluso le permite arrancar desde unidades GPT Legacy/BIOS.

Pero este no es nuestro caso.

Aquí crearemos dos secciones. El primero tendrá un tamaño de 1 GB y se utilizará para RAID 1/arranque.

El segundo se usará para RAID 6 y ocupará todo el espacio libre restante excepto una pequeña área no asignada al final de la unidad.

¿Qué es esta zona no marcada?Según fuentes de la red, nuestros SSD SATA tienen a bordo un caché SLC dinámicamente ampliable con un tamaño de 6 a 78 gigabytes. Obtenemos 6 gigabytes “gratis” debido a la diferencia entre “gigabytes” y “gibibytes” en la hoja de datos de la unidad. Los 72 gigabytes restantes se asignan a partir del espacio no utilizado.

Aquí cabe señalar que tenemos un caché SLC y el espacio está ocupado en modo MLC de 4 bits. Lo que para nosotros significa efectivamente que por cada 4 gigabytes de espacio libre solo obtendremos 1 gigabyte de caché SLC.

Multiplica 72 gigabytes por 4 y obtienes 288 gigabytes. Este es el espacio libre que no marcaremos para permitir que las unidades hagan uso completo del caché SLC.

Así, conseguiremos efectivamente hasta 312 gigas de caché SLC de un total de seis unidades. De todas las unidades, 2 se utilizarán en RAID para lograr redundancia.

Esta cantidad de caché nos permitirá, en muy raras ocasiones, encontrarnos en la vida real con una situación en la que una escritura no vaya al caché. Esto compensa muy bien el inconveniente más triste de la memoria QLC: la velocidad de escritura extremadamente baja cuando los datos se escriben sin pasar por el caché. Si sus cargas no corresponden a esto, le recomiendo que piense detenidamente cuánto durará su SSD bajo dicha carga, teniendo en cuenta el TBW de la hoja de datos.

#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

Creando matrices

Primero, necesitamos cambiar el nombre de la máquina. Esto es necesario porque el nombre del host es parte del nombre de la matriz en algún lugar dentro de mdadm y afecta algo en alguna parte. Por supuesto, se puede cambiar el nombre de las matrices más adelante, pero este es un paso innecesario.

#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

¿Por qué -suponer-limpio...?Para evitar la inicialización de matrices. Esto es válido para los niveles RAID 1 y 6. Todo puede funcionar sin inicialización si se trata de una matriz nueva. Además, inicializar la matriz SSD tras su creación es un desperdicio de recursos TBW. Usamos TRIM/DISCARD siempre que sea posible en matrices SSD ensambladas para "inicializarlas".

Para matrices SSD, RAID 1 DISCARD se admite de fábrica.

Para matrices SSD RAID 6 DISCARD, debe habilitarlo en los parámetros del módulo del kernel.

Esto solo debe hacerse si todos los SSD utilizados en matrices de nivel 4/5/6 en este sistema tienen soporte funcional para descartar_zeroes_data. A veces te encuentras con unidades extrañas que le dicen al kernel que esta función es compatible, pero en realidad no está ahí o la función no siempre funciona. Por el momento, el soporte está disponible en casi todas partes, sin embargo, hay unidades y firmware antiguos con errores. Por este motivo, la compatibilidad con DISCARD está deshabilitada de forma predeterminada para RAID 6.

Atención, el siguiente comando destruirá todos los datos de las unidades NVMe "inicializando" la matriz con "ceros".

#blkdiscard /dev/md0

Si algo sale mal, intente especificar un paso.

#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

¿Por qué tan grande...?Aumentar el tamaño del fragmento tiene un efecto positivo en la velocidad de lectura aleatoria en bloques hasta el tamaño del fragmento inclusive. Esto sucede porque una operación del tamaño apropiado o menor se puede completar completamente en un solo dispositivo. Por lo tanto, se resumen los IOPS de todos los dispositivos. Según las estadísticas, el 99% de las IO no superan los 512K.

RAID 6 IOPS por escritura siempre menor o igual que los IOPS de una unidad. Cuando, como lectura aleatoria, IOPS puede ser varias veces mayor que el de una unidad, y aquí el tamaño del bloque es de importancia clave.
El autor no ve el sentido de intentar optimizar un parámetro que es malo en el diseño de RAID 6 y en su lugar optimiza en qué es bueno RAID 6.
Compensaremos la mala escritura aleatoria de RAID 6 con una caché NVMe y trucos de aprovisionamiento ligero.

Todavía no hemos habilitado DISCARD para RAID 6. Por lo tanto, no “inicializaremos” esta matriz por ahora. Haremos esto más tarde, después de instalar el sistema operativo.

HDD SATA

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

LVM en RAID NVMe

Para mayor velocidad, queremos colocar el sistema de archivos raíz en NVMe RAID 1, que es /dev/md0.
Sin embargo, seguiremos necesitando esta matriz rápida para otras necesidades, como intercambio, metadatos y metadatos LVM-cache y LVM-thin, por lo que crearemos un LVM VG en esta matriz.

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

Creemos una partición para el sistema de archivos raíz.

#lvcreate -L 128G --name root root

Creemos una partición para intercambiar según el tamaño de la RAM.

#lvcreate -L 32G --name swap root

instalación del sistema operativo

En total tenemos todo lo necesario para instalar el sistema.

Inicie el asistente de instalación del sistema desde el entorno Ubuntu Live. Instalación normal. Solo en la etapa de selección de discos para la instalación, debe especificar lo siguiente:

  • /dev/md1, - punto de montaje /boot, FS - BTRFS
  • /dev/root/root (también conocido como /dev/mapper/root-root), - punto de montaje / (raíz), FS - BTRFS
  • /dev/root/swap (también conocido como /dev/mapper/root-swap), - utilizar como partición de intercambio
  • Instale el gestor de arranque en /dev/sda

Cuando selecciona BTRFS como sistema de archivos raíz, el instalador creará automáticamente dos volúmenes BTRFS llamados "@" para / (raíz) y "@home" para /home.

Comencemos la instalación...

La instalación finalizará con un cuadro de diálogo modal que indica un error al instalar el gestor de arranque. Desafortunadamente, no podrá salir de este cuadro de diálogo utilizando los medios estándar y continuar con la instalación. Cerramos sesión en el sistema y volvemos a iniciar sesión, terminando en un escritorio limpio de Ubuntu Live. Abra la terminal y nuevamente:

#sudo bash

Cree un entorno chroot para continuar con la instalación:

#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

Configuremos la red y el nombre de host en chroot:

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

Entremos en el entorno chroot:

#chroot /mnt/chroot

En primer lugar, entregaremos los paquetes:

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

Revisemos y arreglemos todos los paquetes que se instalaron mal debido a una instalación incompleta del sistema:

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

Si algo no funciona, es posible que deba editar /etc/apt/sources.list primero

Ajustemos los parámetros del módulo RAID 6 para habilitar TRIM/DISCARD:

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

Modifiquemos un poco nuestras matrices:

#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

Qué era..?Hemos creado un conjunto de reglas udev que harán lo siguiente:

  • Configure el tamaño de caché de bloque para RAID 2020 para que sea adecuado para 6. El valor predeterminado, al parecer, no ha cambiado desde la creación de Linux y no ha sido adecuado durante mucho tiempo.
  • Reserve un mínimo de IO mientras duren las comprobaciones/sincronizaciones de la matriz. Esto es para evitar que sus matrices se atasquen en un estado de sincronización eterna bajo carga.
  • Limite el IO máximo durante las comprobaciones/sincronización de matrices. Esto es necesario para que la sincronización/comprobación de los RAID SSD no queme sus unidades. Esto es especialmente cierto para NVMe. (¿Recuerdas lo del radiador? No estaba bromeando).
  • Prohíba que los discos detengan la rotación del eje (HDD) a través de APM y establezca el tiempo de espera para los controladores de disco en 7 horas. Puede desactivar completamente APM si sus unidades pueden hacerlo (-B 255). Con el valor predeterminado, las unidades se detendrán después de cinco segundos. Luego, el sistema operativo quiere restablecer el caché del disco, los discos girarán nuevamente y todo comenzará de nuevo. Los discos tienen un número máximo limitado de rotaciones del husillo. Un ciclo predeterminado tan simple puede destruir fácilmente sus discos en un par de años. No todos los discos sufren esto, pero los nuestros son "portátiles", con la configuración predeterminada adecuada, que hace que el RAID parezca un mini-MAID.
  • Instale readahead en discos (rotativos) de 1 megabyte: dos bloques consecutivos/fragmento RAID 6
  • Deshabilite la lectura anticipada en las propias matrices.

Editemos /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

Porqué es eso..?Buscaremos la partición /boot por UUID. En teoría, los nombres de las matrices podrían cambiar.

Buscaremos las secciones restantes por nombres LVM en la notación /dev/mapper/vg-lv, porque identifican particiones de forma bastante única.

No utilizamos UUID para LVM porque El UUID de los volúmenes LVM y sus instantáneas pueden ser los mismos.¿Montar /dev/mapper/root-root... dos veces?Sí. Exactamente. Característica de BTRFS. Este sistema de archivos se puede montar varias veces con diferentes subvols.

Debido a esta misma característica, recomiendo nunca crear instantáneas LVM de volúmenes BTRFS activos. Es posible que recibas una sorpresa cuando reinicies.

Regeneremos la configuración de mdadm:

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

Ajustemos la configuración de 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

Qué era..?Hemos habilitado la expansión automática de los pools delgados LVM al alcanzar el 90% del espacio ocupado por un 5% del volumen.

Hemos aumentado la cantidad máxima de bloques de caché para el caché LVM.

Hemos impedido que LVM busque volúmenes LVM (PV) en:

  • dispositivos que contienen caché LVM (cdata)
  • dispositivos almacenados en caché usando caché LVM, sin pasar por el caché ( _corig). En este caso, el dispositivo almacenado en caché se seguirá escaneando a través del caché (sólo ).
  • dispositivos que contienen metadatos de caché LVM (cmeta)
  • todos los dispositivos en VG con el nombre imágenes. Aquí tendremos imágenes de disco de máquinas virtuales y no queremos que LVM en el host active volúmenes que pertenecen al sistema operativo invitado.
  • todos los dispositivos en VG con el nombre de copia de seguridad. Aquí tendremos copias de seguridad de las imágenes de las máquinas virtuales.
  • todos los dispositivos cuyo nombre termina en “gpv” (volumen físico invitado)

Hemos habilitado la compatibilidad con DISCARD al liberar espacio libre en LVM VG. Ten cuidado. Esto hará que eliminar LV en el SSD lleve bastante tiempo. Esto se aplica especialmente al SSD RAID 6. Sin embargo, según el plan, usaremos aprovisionamiento ligero, por lo que esto no nos obstaculizará en absoluto.

Actualicemos la imagen de initramfs:

#update-initramfs -u -k all

Instalar y configurar grub:

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

¿Qué discos elegir?Todos los que son SD*. El sistema debe poder arrancar desde cualquier unidad SATA o SSD que funcione.

¿Por qué agregaron os-prober...?Para una independencia excesiva y manos juguetonas.

No funciona correctamente si uno de los RAID está en estado degradado. Intenta buscar el sistema operativo en las particiones que se utilizan en las máquinas virtuales que se ejecutan en este hardware.

Si lo necesitas puedes dejarlo, pero ten en cuenta todo lo anterior. Recomiendo buscar recetas para deshacerse de las manos traviesas en línea.

Con esto hemos completado la instalación inicial. Es hora de reiniciar en el sistema operativo recién instalado. No olvides quitar el Live CD/USB de arranque.

#exit
#reboot

Seleccione cualquiera de los SSD SATA como dispositivo de arranque.

LVM en SSD SATA

En este punto, ya iniciamos el nuevo sistema operativo, configuramos la red, apt, abrimos el emulador de terminal y ejecutamos:

#sudo bash

Seguimos

“Inicialice” la matriz desde SATA SSD:

#blkdiscard /dev/md2

Si no funciona, intente:

#blkdiscard --step 65536 /dev/md2
Cree LVM VG en SATA SSD:

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

¿Por qué otro VG...?De hecho, ya tenemos un VG llamado root. ¿Por qué no agregar todo en un solo VG?

Si hay varios PV en un VG, para que el VG se active correctamente, todos los PV deben estar presentes (en línea). La excepción es LVM RAID, que no utilizamos deliberadamente.

Realmente queremos que si hay una falla (pérdida de datos de lectura) en cualquiera de las matrices RAID 6, el sistema operativo arranque normalmente y nos dé la oportunidad de solucionar el problema.

Para hacer esto, en el primer nivel de abstracción aislaremos cada tipo de "medio" físico en un VG separado.

Científicamente hablando, diferentes matrices RAID pertenecen a diferentes "dominios de confiabilidad". No deberías crear un punto común adicional de falla para ellos metiéndolos en un VG.

La presencia de LVM a nivel de “hardware” nos permitirá cortar arbitrariamente piezas de diferentes matrices RAID combinándolas de diferentes maneras. Por ejemplo - ejecutar simultáneamente bcache + LVM Thin, bcache + BTRFS, LVM cache + LVM Thin, una configuración ZFS compleja con cachés, o cualquier otra mezcla infernal para probar y compararlo todo.

A nivel de "hardware", no utilizaremos nada más que los viejos volúmenes LVM "gruesos". La excepción a esta regla puede ser la partición de respaldo.

Creo que en ese momento muchos lectores ya habían comenzado a sospechar algo sobre la muñeca nido.

LVM en disco duro SATA

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

¿Nuevo VG otra vez...?Realmente queremos que si falla la matriz de discos que usaremos para la copia de seguridad de los datos, nuestro sistema operativo continúe funcionando normalmente, manteniendo el acceso a los datos que no son de la copia de seguridad como de costumbre. Por lo tanto, para evitar problemas de activación de VG, creamos un VG separado.

Configurar la caché LVM

Creemos un LV en NVMe RAID 1 para usarlo como dispositivo de almacenamiento en caché.

#lvcreate -L 70871154688B --name cache root

¿Por qué hay tan poco...?El caso es que nuestros SSD NVMe también cuentan con caché SLC. 4 gigabytes “libres” y 18 gigabytes dinámicos debido al espacio libre ocupado en el MLC de 3 bits. Una vez agotada esta caché, los SSD NVMe no serán mucho más rápidos que nuestros SSD SATA con caché. En realidad, por esta razón, no tiene sentido para nosotros hacer que la partición de caché LVM sea mucho más grande que el doble del tamaño de la caché SLC de la unidad NVMe. Para las unidades NVMe utilizadas, el autor considera razonable crear entre 32 y 64 gigabytes de caché.

El tamaño de partición indicado es necesario para organizar 64 gigabytes de caché, metadatos de caché y copia de seguridad de metadatos.

Además, observo que después de un apagado sucio del sistema, LVM marcará todo el caché como sucio y se sincronizará nuevamente. Además, esto se repetirá cada vez que se utilice lvchange en este dispositivo hasta que el sistema se reinicie nuevamente. Por lo tanto, recomiendo recrear inmediatamente el caché utilizando el script adecuado.

Creemos un LV en SATA RAID 6 para usarlo como dispositivo en caché.

#lvcreate -L 3298543271936B --name cache data

¿Por qué sólo tres terabytes...?De modo que, si es necesario, puedas utilizar SATA SSD RAID 6 para algunas otras necesidades. El tamaño del espacio en caché se puede aumentar dinámicamente, sobre la marcha, sin detener el sistema. Para hacer esto, debe detener temporalmente y volver a habilitar el caché, pero la ventaja distintiva de LVM-cache sobre, por ejemplo, bcache es que esto se puede hacer sobre la marcha.

Creemos un nuevo VG para el almacenamiento en caché.

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

Creemos un LV en el dispositivo almacenado en caché.

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

Aquí ocupamos inmediatamente todo el espacio libre en /dev/data/cache para que todas las demás particiones necesarias se crearan inmediatamente en /dev/root/cache. Si creaste algo en el lugar equivocado, puedes moverlo usando pvmove.

Creemos y habilitemos el caché:

#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

¿Por qué ese tamaño de trozo...?A través de experimentos prácticos, el autor pudo descubrir que el mejor resultado se logra si el tamaño del bloque de caché LVM coincide con el tamaño del bloque delgado LVM. Además, cuanto menor sea el tamaño, mejor funcionará la configuración en una grabación aleatoria.

64k es el tamaño de bloque mínimo permitido para LVM delgado.

¡Tenga cuidado al escribir!Sí. Este tipo de caché difiere la sincronización de escritura en el dispositivo almacenado en caché. Esto significa que si se pierde el caché, es posible que pierda datos en el dispositivo almacenado en caché. Más adelante, el autor os contará qué medidas, además de NVMe RAID 1, se pueden tomar para compensar este riesgo.

Este tipo de caché se eligió intencionalmente para compensar el bajo rendimiento de escritura aleatoria de RAID 6.

Veamos lo que tenemos:

#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)

Solo [cachedata_corig] debe ubicarse en /dev/data/cache. Si algo anda mal, utilice pvmove.

Puede desactivar el caché si es necesario con un comando:

#lvconvert -y --uncache cache/cachedata

Esto se hace en línea. LVM simplemente sincronizará el caché con el disco, lo eliminará y cambiará el nombre de cachedata_corig a cachedata.

Configurando LVM delgado

Calculemos aproximadamente cuánto espacio necesitamos para los metadatos delgados de 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"

Redondear hasta 4 gigabytes: 4294967296B

Multiplique por dos y agregue 4194304B para los metadatos de LVM PV: 8594128896B
Creemos una partición separada en NVMe RAID 1 para colocar metadatos delgados de LVM y su copia de seguridad en ella:

#lvcreate -L 8594128896B --name images root

Para qué..?Aquí puede surgir la pregunta: ¿por qué colocar los metadatos ligeros de LVM por separado si aún estarán almacenados en caché en NVMe y funcionarán rápidamente?

Aunque la velocidad es importante aquí, está lejos de ser la razón principal. El caso es que el caché es un punto de falla. Algo podría pasarle, y si los metadatos finos de LVM se almacenan en caché, todo se perderá por completo. Sin metadatos completos, será casi imposible ensamblar volúmenes delgados.

Al mover los metadatos a un volumen separado que no está almacenado en caché, pero que es rápido, garantizamos la seguridad de los metadatos en caso de pérdida o corrupción de la caché. En este caso, todos los daños causados ​​por la pérdida de caché se localizarán dentro de volúmenes delgados, lo que simplificará el procedimiento de recuperación en varios órdenes de magnitud. Con una alta probabilidad, estos daños se repararán utilizando registros FS.

Además, si previamente se tomó una instantánea de un volumen delgado y después de eso el caché se sincronizó completamente al menos una vez, entonces, debido al diseño interno de LVM delgado, la integridad de la instantánea estará garantizada en caso de pérdida de caché. .

Creemos un nuevo VG que será responsable del aprovisionamiento ligero:

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

Creemos un grupo:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
¿Por qué -Z y?Además de para qué está destinado realmente este modo (para evitar que los datos de una máquina virtual se filtren a otra máquina virtual al redistribuir el espacio), la puesta a cero se utiliza adicionalmente para aumentar la velocidad de escritura aleatoria en bloques de menos de 64k. Cualquier escritura de menos de 64k en un área previamente no asignada del volumen delgado se alineará con los bordes de 64K en la caché. Esto permitirá que la operación se realice completamente a través del caché, sin pasar por el dispositivo almacenado en caché.

Movamos los LV a los PV correspondientes:

#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

Cheque:

#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)

Creemos un volumen delgado para las pruebas:

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

Instalaremos paquetes para pruebas y seguimiento:

#apt-get install sysstat fio

Así podrás observar en tiempo real el comportamiento de nuestra configuración de almacenamiento:

#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)'

Así es como podemos probar nuestra configuración:

#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

¡Con cuidado! ¡Recurso!Este código ejecutará 36 pruebas diferentes, cada una de las cuales durará 4 segundos. La mitad de las pruebas son para grabación. Puedes grabar mucho en NVMe en 4 segundos. Hasta 3 gigabytes por segundo. Por lo tanto, cada ejecución de pruebas de escritura puede consumir hasta 216 gigabytes de recursos SSD.

¿Lectura y escritura mezcladas?Sí. Tiene sentido ejecutar las pruebas de lectura y escritura por separado. Además, tiene sentido asegurarse de que todos los cachés estén sincronizados para que una escritura realizada anteriormente no afecte la lectura.

Los resultados variarán mucho durante el primer lanzamiento y los posteriores a medida que el caché y el volumen reducido se llenen, y también dependiendo de si el sistema logró sincronizar los cachés llenados durante el último lanzamiento.

Entre otras cosas, recomiendo medir la velocidad en un volumen delgado ya lleno del que se acaba de tomar una instantánea. El autor tuvo la oportunidad de observar cómo las escrituras aleatorias se aceleran bruscamente inmediatamente después de crear la primera instantánea, especialmente cuando el caché aún no está completamente lleno. Esto sucede debido a la semántica de copia en escritura, la alineación de la caché y los bloques de volumen delgados, y al hecho de que una escritura aleatoria en RAID 6 se convierte en una lectura aleatoria de RAID 6 seguida de una escritura en la caché. En nuestra configuración, la lectura aleatoria de RAID 6 es hasta 6 veces (la cantidad de SSD SATA en la matriz) más rápida que la escritura. Porque Los bloques para CoW se asignan secuencialmente desde un grupo reducido, luego la grabación, en su mayor parte, también se vuelve secuencial.

Ambas características se pueden utilizar a su favor.

Caché de instantáneas "coherentes"

Para reducir el riesgo de pérdida de datos en caso de daño/pérdida de la caché, el autor propone introducir la práctica de rotar instantáneas para garantizar su integridad en este caso.

En primer lugar, debido a que los metadatos de volumen reducido residen en un dispositivo sin caché, los metadatos serán consistentes y las posibles pérdidas se aislarán dentro de los bloques de datos.

El siguiente ciclo de rotación de instantáneas garantiza la integridad de los datos dentro de las instantáneas en caso de pérdida de caché:

  1. Para cada volumen delgado con el nombre <nombre>, cree una instantánea con el nombre <nombre>.cached
  2. Establezcamos el umbral de migración en un valor alto razonable: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. En el bucle verificamos la cantidad de bloques sucios en el caché: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' hasta llegar a cero. Si el cero falta durante demasiado tiempo, se puede crear cambiando temporalmente la caché al modo de escritura directa. Sin embargo, teniendo en cuenta las características de velocidad de nuestras matrices SSD SATA y NVMe, así como su recurso TBW, podrá captar rápidamente el momento sin cambiar el modo de caché o su hardware consumirá por completo todos sus recursos en unos pocos días. Debido a limitaciones de recursos, el sistema, en principio, no puede estar por debajo del 100% de carga de escritura todo el tiempo. Nuestros SSD NVMe con una carga de escritura del 100 % agotarán completamente el recurso en 3, 4 días. Los SSD SATA solo durarán el doble. Por lo tanto, asumiremos que la mayor parte de la carga se destina a la lectura y que tenemos ráfagas relativamente breves de actividad extremadamente alta combinadas con una carga baja en promedio para la escritura.
  4. Tan pronto como detectamos (o creamos) un cero, cambiamos el nombre de <nombre>.cached a <nombre>.committed. Se elimina el antiguo <nombre>.committed.
  5. Opcionalmente, si el caché está 100% lleno, se puede recrear mediante un script y así borrarlo. Con la caché medio vacía, el sistema funciona mucho más rápido a la hora de escribir.
  6. Establezca el umbral de migración en cero: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Esto evitará temporalmente que el caché se sincronice con el medio principal.
  7. Esperamos hasta que se acumulen muchos cambios en el caché. #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' o el cronómetro sonará.
  8. Repetimos de nuevo.

¿Por qué las dificultades con el umbral migratorio...?El caso es que, en la práctica real, una grabación “aleatoria” no es del todo aleatoria. Si escribimos algo en un sector de 4 kilobytes de tamaño, existe una alta probabilidad de que en los próximos minutos se realice un registro en el mismo sector o en uno de los vecinos (+- 32K).

Al establecer el umbral de migración en cero, posponemos la sincronización de escritura en el SSD SATA y agregamos varios cambios a un bloque de 64K en el caché. Esto ahorra significativamente el recurso de SATA SSD.

¿Dónde está el código...?Desafortunadamente, el autor se considera insuficientemente competente en el desarrollo de scripts bash porque es 100% autodidacta y practica el desarrollo impulsado por “google”, por lo que cree que el terrible código que sale de sus manos no debería ser utilizado por nadie. demás.

Creo que los profesionales en este campo podrán representar de forma independiente toda la lógica descrita anteriormente, si es necesario, y tal vez incluso diseñarla bellamente como un servicio systemd, como intentó hacer el autor.

Un esquema de rotación de instantáneas tan simple nos permitirá no solo tener constantemente una instantánea completamente sincronizada en el SSD SATA, sino que también nos permitirá, utilizando la utilidad Thin_Delta, descubrir qué bloques se cambiaron después de su creación y, por lo tanto, localizar daños en los volúmenes principales, simplificando enormemente la recuperación.

RECORTAR/DESCARTAR en libvirt/KVM

Porque el almacenamiento de datos se utilizará para KVM que ejecuta libvirt, entonces sería una buena idea enseñar a nuestras VM no solo a ocupar espacio libre, sino también a liberar lo que ya no es necesario.

Esto se hace emulando el soporte TRIM/DISCARD en discos virtuales. Para hacer esto, necesita cambiar el tipo de controlador a virtio-scsi y editar el 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 procesa correctamente dichos DISCARDs de los sistemas operativos invitados y los bloques se liberan correctamente tanto en la caché como en el grupo ligero. En nuestro caso, esto ocurre principalmente de forma retrasada, al eliminar la siguiente instantánea.

Copia de seguridad BTRFS

Utilice scripts ya preparados con крайней precaución y bajo su propio riesgo. El autor escribió este código él mismo y exclusivamente para él mismo. Estoy seguro de que muchos usuarios experimentados de Linux tienen herramientas similares y no es necesario copiar las de otra persona.

Creemos un volumen en el dispositivo de respaldo:

#lvcreate -L 256G --name backup backup

Formateémoslo en BTRFS:

#mkfs.btrfs /dev/backup/backup

Creemos puntos de montaje y montemos las subsecciones raíz del sistema de archivos:

#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

Creemos directorios para copias de seguridad:

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

Creemos un directorio para los scripts de respaldo:

#mkdir /root/btrfs-backup

Copiemos el guión:

Mucho código bash aterrador. Úselo bajo su propio riesgo. No escribas cartas enojadas al autor...#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

¿Qué hace siquiera...?Contiene un conjunto de comandos simples para crear instantáneas BTRFS y copiarlas a otro FS usando el envío/recepción BTRFS.

El primer lanzamiento puede ser relativamente largo, porque... Al principio, se copiarán todos los datos. Los futuros lanzamientos serán muy rápidos, porque... Sólo se copiarán los cambios.

Otro script que pondremos en cron:

Algo más de código 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

Qué hace..?Crea y sincroniza instantáneas incrementales de los volúmenes BTRFS enumerados en el FS de respaldo. Después de esto, elimina todas las imágenes creadas hace 60 días. Después del lanzamiento, aparecerán instantáneas fechadas de los volúmenes enumerados en los subdirectorios /backup/btrfs/back/remote/.

Démosle derechos de ejecución al código:

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

Comprobémoslo y pongámoslo en el 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

Copia de seguridad delgada LVM

Creemos un grupo reducido en el dispositivo de respaldo:

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

Instalemos ddrescue, porque... Los scripts utilizarán esta herramienta:

#apt-get install gddrescue

Creemos un directorio para scripts:

#mkdir /root/lvm-thin-backup

Copiemos los scripts:

Mucha fiesta por dentro...#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

Qué hace...?Contiene un conjunto de comandos para manipular instantáneas delgadas y sincronizar la diferencia entre dos instantáneas delgadas recibidas a través de Thin_delta con otro dispositivo de bloque usando ddrescue y blkdiscard.

Otro script que pondremos en cron:

Un poco más de fiesta#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

Qué hace...?Utiliza el script anterior para crear y sincronizar copias de seguridad de los volúmenes ligeros enumerados. El script dejará instantáneas inactivas de los volúmenes enumerados, que son necesarias para realizar un seguimiento de los cambios desde la última sincronización.

Este script debe editarse, especificando la lista de volúmenes ligeros para los cuales se deben realizar copias de seguridad. Los nombres dados son sólo para fines ilustrativos. Si lo desea, puede escribir un script que sincronice todos los volúmenes.

Demos los derechos:

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

Comprobémoslo y pongámoslo en el 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

El primer lanzamiento será largo, porque... Los volúmenes delgados se sincronizarán completamente copiando todo el espacio utilizado. Gracias a los metadatos delgados de LVM, sabemos qué bloques están realmente en uso, por lo que solo se copiarán los bloques de volumen delgados realmente utilizados.

Las ejecuciones posteriores copiarán los datos de forma incremental gracias al seguimiento de cambios a través de metadatos finos LVM.

Vamos a ver que pasó:

#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

¿Qué tiene esto que ver con las muñecas nido?

Lo más probable es que, dado que los volúmenes lógicos LVM LV pueden ser volúmenes físicos LVM PV para otros VG. LVM puede ser recursivo, como muñecos nido. Esto le da a LVM una flexibilidad extrema.

PS

En el próximo artículo, intentaremos utilizar varios sistemas de almacenamiento móvil/KVM similares como base para crear un clúster de almacenamiento/vm geodistribuido con redundancia en varios continentes utilizando escritorios domésticos, Internet doméstico y redes P2P.

Fuente: habr.com

Añadir un comentario