¿Qué tienen en común LVM y una matryoshka?

Buenas,
Quiero compartir con la comunidad mi experiencia práctica en la construcción de un sistema de almacenamiento para KVM utilizando md RAID + LVM.

El programa incluirá:

  • Ensamblaje de md RAID 1 con NVMe SSD.
  • Ensamblaje de md RAID 6 con SATA SSD y discos convencionales.
  • Características del funcionamiento de TRIM/DISCARD en SSD RAID 1/6.
  • Creación de un array de arranque md RAID 1/6 con un conjunto común de discos.
  • Instalación del sistema en NVMe RAID 1 en ausencia de soporte NVMe en la BIOS.
  • Uso de LVM cache y LVM thin.
  • Uso de instantáneas BTRFS y send/receive para copias de seguridad.
  • Uso de instantáneas LVM thin y thin_delta para copias de seguridad al estilo BTRFS.

Si está interesado, por favor, continúe leyendo.

Declaración

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

  • SSD NVMe fritos hasta quedar crujientes.
  • Recursos de escritura completamente agotados y falla de unidades SSD.
  • Pérdida total de todos los datos en todos los dispositivos, incluidas las copias de seguridad.
  • Hardware informático defectuoso.
  • Tiempo, nervios y dinero desperdiciados.
  • Cualquier otra consecuencia que no esté listada arriba.

Hardware

Se disponía de:

Una placa base de aproximadamente 2013 con chipset Z87, acompañada de un Intel Core i7 / Haswell.

  • Un procesador de 4 núcleos y 8 hilos
  • 32 Gigabytes de memoria RAM DDR3
  • 1 x 16 o 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 x 6 GBps SATA 3 puertos

Adaptador SAS LSI SAS9211-8I flasheado en modo IT / HBA. El firmware con soporte RAID fue reemplazado intencionalmente por el firmware HBA para que:

  1. Se pudiera desechar este adaptador en cualquier momento y reemplazarlo por cualquier otro que estuviera a mano.
  2. TRIM/Discard funcionaba correctamente en los discos, ya que en el firmware RAID estos comandos no están soportados en absoluto, mientras que el HBA, en general, no se preocupa por qué comandos pasar por el bus.

Discos duros, — 8 unidades HGST Travelstar 7K1000 de 1 TB en formato de 2.5, como los de portátiles. Estas unidades previamente estaban en un array RAID 6. En el nuevo sistema también tendrán su uso. Para almacenar copias de seguridad locales.

Se añadieron adicionalmente:

6 unidades SATA SSD modelo Samsung 860 QVO de 2TB. De estos SSD se requería un gran volumen, la disponibilidad de caché SLC, deseable confiabilidad y un precio bajo. Era obligatoria la compatibilidad con discard/zero, la cual se verifica con una línea en dmesg:

kernel: ata1.00: Activando discard_zeroes_data

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

Para estos SSD, la velocidad de lectura/escritura aleatoria y los recursos según tus necesidades son cruciales. Un radiador es imprescindible. Absolutamente imprescindible. De lo contrario, los freirás hasta quedar crujientes en la primera sincronización del RAID.

Adaptador StarTech PEX8M2E2 para 2 x SSD NVMe, instalado en un slot PCIe 3.0 de 8x. Este es, nuevamente, solo un HBA, pero para NVMe. Se diferencia de los adaptadores baratos en que no requiere soporte de bifurcación PCIe por parte de la placa madre, gracias a su conmutador PCIe integrado. Funcionará incluso en el sistema más antiguo que tenga PCIe, incluso si es un slot x1 PCIe 1.0. Naturalmente, a la velocidad correspondiente. No hay RAID allí. No cuenta con BIOS integrado. Así que tu sistema no aprenderá mágicamente a arrancar desde NVMe, y mucho menos a hacer RAID NVMe gracias a este dispositivo.

Este componente se eligió exclusivamente debido a la disponibilidad de solo un puerto libre de 8x PCIe 3.0 en el sistema, y, con dos slots libres, puede ser fácilmente reemplazado por dos económicos PEX4M2E1 o alternativas que se pueden comprar en cualquier lugar a partir de 600 rublos.

La decisión de abandonar cualquier RAID físico o integrado en el chipset/BIOS se tomó conscientemente, con el objetivo de poder reemplazar todo el sistema, excepto los propios SSD/HDD, manteniendo todos los datos. Idealmente, debería ser posible conservar incluso el sistema operativo instalado al mudarse a un hardware completamente nuevo/diferente. Lo principal es que haya puertos SATA y PCIe. Es como un live CD o un USB de arranque, solo que muy rápido y un poco más voluminoso.

HumorPorque, ya saben cómo es a veces: a veces necesitas llevar todo el array contigo. Y no quieres perder datos. Para esto, todos los medios mencionados se colocan cómodamente en los soportes de un chasis estándar de 5.25.

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

Los RAID de hardware son aburridos. Los enciendes. O funcionan o no. Pero con mdadm siempre hay opciones.

Software

Anteriormente, se había instalado Debian 8 Jessie en el hardware, que está cerca de EOL. Se configuró un RAID 6 con los HDD mencionados anteriormente en conjunto con LVM. En él estaban funcionando máquinas virtuales en kvm/libvirt.

Dado que el autor tiene la experiencia adecuada en la creación de unidades flash de arranque portátiles SATA/NVMe, y para no romper el patrón habitual de apt, se eligió como sistema objetivo Ubuntu 18.04, que ya se ha estabilizado bastante, pero aún tiene 3 años de soporte en perspectiva.

En el sistema mencionado vienen todos los controladores de hardware necesarios de serie. No necesitaremos ningún software o controlador adicional.

Preparación para la instalación

Para instalar el sistema, necesitaremos la imagen de escritorio de Ubuntu. El instalador del sistema server tiene una interfaz de instalación intrusiva que, sin poderse desactivar, inserta obligatoriamente una partición UEFI en uno de los discos, arruinando toda la configuración. Por lo tanto, solo se puede instalar en modo UEFI. No ofrece otras opciones.

Esto no nos satisface.

¿Por qué?Desafortunadamente, el arranque UEFI es extremadamente incompatible con RAID por software, ya que nadie nos ofrece una reserva para la partición ESP de UEFI. En internet hay recetas que sugieren colocar la partición ESP en una unidad flash en el puerto USB, pero esto es un punto de fallo. Existen recetas que utilizan RAID 1 por software con metadatos de la versión 0.9, que no impiden que el BIOS UEFI vea esta partición, pero eso dura felices momentos hasta que el BIOS o cualquier otro sistema operativo en el hardware escriba algo en ESP sin sincronizar en otras copias.

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

Así que, no vamos a inventar la rueda de nuevo. Ya tenemos una rueda lista y probada a lo largo de los años, llamada arranque Legacy/BIOS, que lleva el orgulloso nombre de CSM en sistemas compatibles con UEFI. Simplemente la sacaremos de la estantería, la aceitaremos, inflaremos las ruedas y la limpiaremos con un paño húmedo.

La versión de escritorio de Ubuntu tampoco se instala correctamente con el cargador de arranque Legacy, pero aquí, al menos, hay algunas opciones.

Así que, ensamblamos el hardware y cargamos el sistema desde la unidad flash de Ubuntu Live. Necesitaremos descargar paquetes, así que configuramos la red, según la que esté funcionando. Si no funciona, los paquetes necesarios se pueden cargar en la unidad flash con antelación.

Entramos en el entorno de escritorio, lanzamos un emulador de terminal y, ¡vamos!

#sudo bash

¿Cómo...?La línea anterior es un desencadenante canónico de debates sobre sudo. Con bmayor audiencia.Más capacidades conllevan una mayor responsabilidad. La pregunta es si podrá asumirla. Muchos consideran que usar sudo de esta manera es, al menos, imprudente. Sin embargo:mayor audiencia.¿Por qué no ZFS…?

Reproducir video

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

Cuando instalamos software en nuestra computadora, en esencia, le estamos prestando nuestro hardware a los desarrolladores de ese software.Cuando confiamos en este software para la seguridad de nuestros datos, estamos asumiendo una deuda equivalente al costo de recuperar esos datos, que algún día tendremos que saldar.
Desde este punto de vista, ZFS es un Ferrari, mientras que mdadm+lvm se asemeja más a una bicicleta.

Subjetivamente, el autor prefiere prestar a desconocidos una bicicleta adquirida a crédito en lugar de un Ferrari. El costo no es alto. No necesita requisitos. Las reglas de tránsito son más simples. El estacionamiento es gratuito. La maniobrabilidad es mejor. Siempre se le pueden añadir pedales a la bicicleta, y se puede reparar por su cuenta.

¿Entonces, por qué BTRFS…?

Para poder cargar el sistema operativo, necesitaremos un sistema de archivos compatible con GRUB en Legacy/BIOS desde el principio, y que, además, soporte instantáneas en vivo. La usaremos para la partición /boot. Además, el autor prefiere utilizar este FS para / (raíz), sin olvidar mencionar que para cualquier otro software se pueden crear particiones separadas en LVM y montarlas en los directorios necesarios.No almacenaremos ni imágenes,

ni bases de datos en este FS. máquinas virtualesEste FS solo se utilizará para crear instantáneas del sistema sin necesidad de apagarlo, y luego transferir estas instantáneas a un disco de respaldo utilizando send/receive.
Además, el autor prefiere mantener al mínimo el software en el hardware y ejecutar todo el resto en máquinas virtuales utilizando técnicas como el paso de GPU y controladores de host PCI-USB en KVM a través de IOMMU.

En el hardware solo quedan: almacenamiento de datos, virtualización y respaldo.

Si confía más en ZFS, en principio, para el uso indicado son intercambiables.

No obstante, el autor ignora deliberadamente las funciones integradas de espejado/RAID y redundancia que existen en ZFS, BTRFS y LVM.

No obstante, el autor conscientemente ignora las funciones integradas de espejado/RAID y redundancia que existen en ZFS, BRTFS y LVM.

Como argumento adicional, BTRFS tiene la propiedad de convertir escrituras aleatorias en secuenciales, lo cual tiene un efecto muy positivo en la velocidad de sincronización de instantáneas / copias de seguridad en HDD.

Volveremos a escanear todos los dispositivos:

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

Echemos un vistazo:

#lsscsi && nvme list
[0:0:0:0] disco ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disco ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disco ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disco ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disco ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disco ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disco ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disco ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disco ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disco ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disco ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disco ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disco ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disco ATA HGST HTS721010A9 A3J0 /dev/sdn
Nodo SN Modelo Espacio Usos Formato Versión FW
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/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

Particionado de «discos»

NVMe SSD

No vamos a particionarlos de ninguna manera. De todos modos, nuestra BIOS no ve estos dispositivos. Así que irán completamente a un RAID por software. Ni siquiera crearemos particiones allí. Si se desea seguir el «canon» o por «principio» — cree una gran partición, como en un HDD.

SATA HDD

Aquí no hay necesidad de inventar nada. Crearemos una partición para todo. Crearemos la partición porque estos discos son visibles para la BIOS y pueden incluso intentar arrancar desde ellos. Incluso instalaremos más tarde GRUB en estos discos para que el sistema pueda arrancar desde ellos de manera repentina.

#cat >hdd.part << EOF
etiqueta: dos
id-etiqueta: 0x00000000
dispositivo: /dev/sdg
unidad: sectores

/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 se pone más interesante.

Primero, los dispositivos tienen un tamaño de 2 TB. Esto está dentro del límite permitido para MBR, que es lo que utilizaremos. Si es necesario, se puede cambiar a GPT. Los discos GPT tienen una capa de compatibilidad que permite a 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 en estos discos estén al principio. Esto también permite que el arranque Legacy/BIOS se realice desde discos GPT.

Pero este no es nuestro caso.

Aquí crearemos dos particiones. La primera tendrá un tamaño de 1 GB y se utilizará para RAID 1 /boot.

La segunda se utilizará para RAID 6 y ocupará todo el espacio libre restante, excepto por una pequeña área sin particionar al final del dispositivo.

¿Qué es esta área sin particionar?Según fuentes en línea, nuestros SATA SSD tienen a bordo un caché SLC dinámicamente expandible que varía de 6 a 78 gigabytes. Los 6 gigabytes los obtenemos «gratis» debido a la diferencia entre «gigabytes» y «gibibytes» en la ficha técnica del dispositivo. Los otros 72 gigabytes se asignan a partir del espacio no utilizado.

Es importante notar que nuestro caché es SLC y el espacio se utiliza en modo 4 bit MLC. Lo que esto significa de manera efectiva para nosotros es que por cada 4 gigabytes de espacio libre, solo obtendremos 1 gigabyte de caché SLC.

Multiplicamos 72 gigabytes por 4 y obtenemos 288 gigabytes. Este es el espacio libre que no vamos a particionar, para permitir que los dispositivos utilicen completamente el caché SLC.

De este modo, obtendremos hasta 312 gigabytes de caché SLC en total de seis unidades. De todas las unidades, 2 se utilizarán en RAID para redundancia.

Esta cantidad de caché permitirá que rara vez nos enfrentemos en la práctica a situaciones donde la escritura no se realiza en caché. Esto compensa de manera excepcional la mayor desventaja de la memoria QLC: la extremadamente baja velocidad de escritura cuando los datos se escriben sin pasar por la caché. Si tus cargas de trabajo no se ajustan a esto, te recomiendo que pienses seriamente en cuánto tiempo sobrevivirán tus SSD bajo tal carga considerando el TBW de la hoja de especificaciones.

#cat >ssd.part << EOF
etiqueta: dos
id-etiqueta: 0x00000000
dispositivo: /dev/sda
unidad: sectores

/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

Creación de arreglos

Para comenzar, necesitamos renombrar la máquina. Es necesario porque el nombre del host es parte del nombre del arreglo dentro de mdadm y afecta a algo. Los arreglos, por supuesto, se pueden renombrar más tarde, pero eso implica pasos adicionales.

#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é -assume-clean…?Para no inicializar los arreglos. Para ambos niveles de RAID 1 y 6, esto es aceptable. Todo puede funcionar sin inicialización si es un nuevo arreglo. Además, la inicialización de un arreglo SSD al crearlo es una pérdida innecesaria del recurso TBW. Usamos TRIM/DISCARD donde sea posible en los arreglos SSD creados para "inicializarlos".

El RAID 1 de los arreglos SSD admite DISCARD de forma nativa.

En los arreglos SSD RAID 6, DISCARD debe habilitarse en los parámetros del módulo del núcleo.

Esto solo debe hacerse si todos los SSD utilizados en los arreglos de niveles 4/5/6 en este sistema tienen soporte operativo para discard_zeroes_data. A veces aparecen unidades extrañas que informan al núcleo que soportan esta función, pero en realidad no lo hacen, o la función no siempre funciona. En este momento, el soporte existe prácticamente en todas partes; sin embargo, se encuentran unidades antiguas y firmware defectuoso. Por esta razón, la compatibilidad con DISCARD está desactivada por defecto para RAID 6.

Atención, el siguiente comando destruirá todos los datos en las unidades NVMe "inicializando" el arreglo "a ceros".

#blkdiscard /dev/md0

Si algo salió mal, intenta especificar el 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 chunk-size impacta positivamente en la velocidad de lectura aleatoria mediante bloques hasta incluir el chunk-size. Esto sucede porque una operación del tamaño correspondiente o menor puede completarse completamente en un solo dispositivo. Por lo tanto, los IOPS de todos los dispositivos se suman. Estadísticamente, el 99% del IO no supera los 512K.

En RAID 6, los IOPS de escritura siempre son menores o iguales a los IOPS de un solo dispositivo. En cambio, en la lectura aleatoria, los IOPS pueden ser mucho mayores que los de un solo dispositivo, y aquí el tamaño del bloque es fundamental.
El autor no ve sentido en intentar optimizar un parámetro que es intrínsecamente malo en RAID 6 por diseño y, en su lugar, optimiza aquello en lo que RAID 6 se desempeña bien.
Compensaremos la baja escritura aleatoria de RAID 6 con caché en NVMe y trucos con thin-provisioning.

Por el momento, no hemos activado DISCARD para RAID 6. Así que no inicializaremos este conjunto aún. Lo haremos más tarde, después de instalar el sistema operativo.

SATA HDD

#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 alojar el sistema de archivos raíz en RAID 1 NVMe, que es /dev/md0.
Sin embargo, este conjunto rápido aún lo necesitaremos para otras necesidades, como swap, metadatos y caché LVM-cache y metadatos LVM-thin, por lo que crearemos LVM VG en este conjunto.

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

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

#lvcreate -L 128G --name root root

Crearemos una partición para swap del tamaño de la memoria RAM.

#lvcreate -L 32G --name swap root

Instalación del sistema operativo

En total, tenemos todo lo necesario para instalar el sistema.

Iniciamos el asistente de instalación del sistema desde el entorno Ubuntu Live. Instalación estándar. Solo en la etapa de selección de discos para la instalación se debe indicar lo siguiente:

  • /dev/md1, — точка монтирования /boot, ФС — BTRFS
  • /dev/root/root (a.k.a /dev/mapper/root-root), — точка монтирования / (корень), ФС — BTRFS
  • /dev/root/swap (a.k.a /dev/mapper/root-swap), — использовать как раздел подкачки
  • Instalar el cargador en /dev/sda

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

Iniciamos la instalación...

La instalación finalizará con un cuadro de diálogo modal informando sobre un error en la instalación del cargador. Desafortunadamente, no se podrá salir de este diálogo por medios estándar y continuar con la instalación. Hacemos logout del sistema y volvemos a iniciar sesión, aterrizando en un escritorio limpio de Ubuntu Live. Abrimos la terminal y nuevamente:

#sudo bash

Creamos 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

Configuraremos la red y el hostname 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

Entramos en el entorno chroot:

#chroot /mnt/chroot

Primero, instalaremos los paquetes:

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

Verificaremos y corregiremos todos los paquetes que se instalaron incorrectamente debido a la instalación incompleta del sistema:

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

Si algo no salió bien, es posible que necesite editar antes /etc/apt/sources.list

Ajustaremos los parámetros para el módulo RAID 6 para habilitar TRIM/DISCARD:

#cat >/etc/modprobe.d/raid456.conf << EOF
opciones raid456 manejar_dispositivos_descartar_seguro=1
EOF

Ajustaremos un poco nuestros conjuntos:

#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é fue eso..?Creamos un conjunto de reglas udev que harán lo siguiente:

  • Establecer un tamaño de caché de bloques adecuado para el año 2020 para RAID 6. El valor predeterminado, parece, no ha cambiado desde la creación de Linux y ya no es adecuado desde hace tiempo.
  • Reservar un mínimo de IO durante las verificaciones/sincronizaciones de los arreglos. Esto es necesario para que sus arreglos no queden atrapados en un estado de sincronización eterna bajo carga.
  • Limitar un máximo de IO durante las verificaciones/sincronizaciones de los arreglos. Esto es necesario para que la sincronización/verificación de los RAID SSD no queme sus unidades hasta quedar crujientes. Especialmente relevante para NVMe. (¿Recuerdas el disipador? No estaba bromeando.)
  • Prohibir a través de APM que los discos detengan la rotación del eje (HDD) y establecer un tiempo de espera para la suspensión de los controladores de disco de 7 horas. Se puede desactivar completamente APM si sus discos lo permiten (-B 255). Con el valor predeterminado, los discos se detendrán después de cinco segundos. Luego, el sistema operativo querrá vaciar la caché del disco, los discos volverán a girar, y todo repetido. Los discos tienen un número máximo limitado de giros del eje. Este simple ciclo por defecto puede fácilmente dañar sus discos en un par de años. No todos los discos sufren esto, pero los nuestros, los "portátiles", con la configuración predeterminada adecuada, que hacen de un RAID un crudo parecido a un mini-MAID.
  • Configurar el readahead en los discos (giratorios) a 1 megabyte — dos bloques/chunks secuenciales RAID 6.
  • Prohibir el readahead en los propios arreglos.

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

¿Por qué así..?Buscamos la partición /boot por UUID ya que la nomenclatura de los arreglos teóricamente puede cambiar.

Buscamos las demás particiones por nombres LVM en notación /dev/mapper/vg-lv, ya que identifican las particiones de manera bastante única.

No usamos UUID para LVM ya que el UUID de los volúmenes LVM y sus snapshots puede coincidir.¿Montamos dos veces /dev/mapper/root-root..?Sí. Exactamente. Característica de BTRFS. Este sistema de archivos se puede montar varias veces con diferentes subvolúmenes.

Debido a esta misma característica, recomiendo no crear nunca snapshots de LVM de volúmenes BTRFS activos. Puede obtener sorpresas al reiniciar.

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é fue eso..?Hemos habilitado la expansión automática de los grupos LVM thin al alcanzar el 90% de espacio usado en un 5% del volumen.

Hemos aumentado el número máximo de bloques de caché para LVM cache.

Hemos prohibido a LVM buscar volúmenes LVM (PV) en:

  • dispositivos que contienen LVM cache (cdata)
  • dispositivos cacheados mediante LVM cache evitando la caché (<lv_name>_corig). Sin embargo, el propio dispositivo cacheado aún será escaneado a través de la caché (simplemente <lv_name>).
  • dispositivos que contienen metadatos de LVM cache (cmeta)
  • todos los dispositivos en VG llamado images. Aquí tendremos imágenes de discos de máquinas virtuales, y no queremos que LVM en el host active volúmenes pertenecientes al sistema operativo invitado.
  • todos los dispositivos en VG llamado backup. Aquí tendremos copias de seguridad de imágenes de máquinas virtuales.
  • todos los dispositivos cuyo nombre termina en «gpv» (guest physical volume)

Hemos habilitado el soporte para DISCARD al liberar espacio libre en LVM VG. Tenga cuidado. Esto hará que la eliminación de LV en SSD sea bastante larga. Esto se aplica especialmente a SSD RAID 6. Sin embargo, según lo planeado, utilizaremos thin provisioning, por lo que no será un inconveniente.

Actualizaremos la imagen initramfs:

#update-initramfs -u -k all

Instalaremos y configuraremos grub:

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

¿Qué discos elegir?Todos los que sean sd*. El sistema debe ser capaz de arrancar desde cualquier disco SATA o SSD que esté funcionando.

¿Por qué desactivamos os-prober..?Por su excesiva autonomía y manos traviesas.

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

Si lo necesitas, puedes dejarlo, pero ten en cuenta todo lo mencionado anteriormente. Recomiendo buscar recetas para deshacerse de las manos traviesas en la red.

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

#exit
#reboot

Como dispositivo de arranque seleccionamos cualquiera de los SATA SSD.

LVM en SATA SSD

Para este momento ya hemos arrancado en el nuevo sistema operativo, configurado la red, apt, abierto un emulador de terminal y ejecutado:

#sudo bash

Continuemos.

«Inicializamos» el array de SATA SSD:

#blkdiscard /dev/md2

Si no funcionó, intentamos:

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

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

¿Por qué otra VG..?De hecho, ya tenemos un VG con el nombre root. ¿Por qué no agregar todo a un solo VG?

Si hay varios PV en VG, para la activación correcta de VG, todos los PV deben estar presentes (online). La excepción es el RAID LVM, que no utilizamos intencionadamente.

Queremos que, en caso de fallo (léase pérdida de datos) en cualquiera de los arreglos RAID 6, el sistema operativo se inicie correctamente y nos brinde la oportunidad de resolver el problema.

Para ello, en el primer nivel de abstracción, aislaremos cada tipo de 'soporte' físico en un VG separado.

Científicamente hablando, diferentes arreglos RAID pertenecen a diferentes 'dominios de confiabilidad'. No debemos crear un punto de fallo común para ellos, colocándolos en un solo VG.

Tener LVM a nivel de 'hardware' nos permitirá cortar fragmentos de diferentes arreglos RAID de diversas maneras, combinándolos. Por ejemplo, lanzar simultáneamente bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, una configuración compleja de ZFS con cachés o cualquier otra mezcla diabólica para experimentar y comparar.

A nivel de 'hardware', no usaremos nada más que los clásicos 'thick' LVM volumes. La excepción a esta regla podría ser la partición para copias de seguridad.

Creo que para este momento, muchos lectores ya han comenzado a sospechar algo acerca de la matryoshka.

LVM en SATA HDD

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

¿Otra nueva VG..?Queremos que, en caso de fallo en el arreglo de discos que utilizaremos para la copia de seguridad de datos, nuestro sistema operativo siga funcionando correctamente, manteniendo el acceso a los datos no reservados. Por ello, para evitar problemas de activación de VG, creamos un VG separado.

Configuración de LVM cache

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

#lvcreate -L 70871154688B --name cache root

¿Por qué tan poco...?El hecho es que nuestros NVMe SSD también tienen caché SLC. 4 gigabytes 'gratuitos' y 18 gigabytes dinámicos gracias al espacio libre utilizado en 3-bit MLC. Al agotar esta caché, los NVMe SSD no serán mucho más rápidos que nuestro SATA SSD con caché. De hecho, por esta razón, no tiene sentido hacer la partición de LVM cache mucho más grande que el doble del volumen de la caché SLC del almacenamiento NVMe. Para los NVMe utilizados, el autor considera razonable hacer una caché de 32-64 gigabytes.

El tamaño mencionado de la partición es necesario para organizar 64 gigabytes de caché, alojar metadatos de caché y un respaldo de los metadatos.

Además, señalaré que tras un apagado inesperado del sistema, LVM marcará toda la caché como sucia y la volverá a sincronizar. Además, esto se repetirá con cada uso de lvchange en este dispositivo hasta que el sistema se reinicie. Por eso, recomiendo recrear la caché de inmediato con el script correspondiente.

Crearemos un LV en RAID 6 SATA para utilizarlo como dispositivo de caché.

#lvcreate -L 3298543271936B --name cache data

¿Por qué solo tres terabytes..?Para que, si es necesario, se pueda utilizar RAID 6 SATA SSD para otras necesidades. El tamaño del espacio de caché se puede aumentar dinámicamente, sobre la marcha, sin detener el funcionamiento del sistema. Para ello, es necesario detener temporalmente y volver a activar la caché, pero la ventaja distintiva de LVM-cache en comparación con, por ejemplo, bcache, es que esto se puede hacer sobre la marcha.

Crearemos un nuevo VG para la caché.

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

Crearemos un LV en el dispositivo de caché.

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

Aquí hemos ocupado todo el espacio libre en /dev/data/cache para que todas las demás particiones necesarias se creen automáticamente en /dev/root/cache. Si algo se creó en otro lugar, se puede mover utilizando pvmove.

Crearemos y activaremos la 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é un chunksize así..?A través de experimentos prácticos, el autor logró averiguar que se consigue el mejor resultado si el tamaño del bloque de LVM cache coincide con el tamaño del bloque de LVM thin. Además, cuanto más pequeño es el tamaño, mejor se comporta la configuración en caso de escritura aleatoria.

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

¡Cuidado con writeback..!Sí. Este tipo de caché retrasa la sincronización de la escritura en el dispositivo de caché. Esto significa que, en caso de pérdida de la caché, se pueden perder datos en el dispositivo de caché. Más adelante, el autor explicará qué medidas, además de NVMe RAID 1, se pueden tomar para compensar este riesgo.

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

Verificaremos qué hemos conseguido:

#lvs -a -o lv_name,lv_size,devices --units B cache
Dispositivos LV LSize
[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)

En /dev/data/cache solo debería estar [cachedata_corig]. Si algo no está bien, utiliza pvmove.

Para desactivar la caché si es necesario, se puede hacer con un solo comando:

#lvconvert -y --uncache cache/cachedata

Esto se hace en línea. LVM simplemente sincroniza la caché en el disco, la elimina y renombra cachedata_corig de nuevo a cachedata.

Configuración de LVM thin

Estimemos aproximadamente cuánto espacio necesitaremos para los metadatos de LVM thin:

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 bytes de tamaño estimado del área de metadatos para "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"

Redondearemos a 4 gigabytes: 4294967296B

Multiplicamos por dos y sumamos 4194304B para los metadatos LVM PV: 8594128896B
Crearemos una partición separada en NVMe RAID 1 para alojar los metadatos LVM thin y su copia de seguridad:

#lvcreate -L 8594128896B --name images root

¿Por qué..?Aquí puede surgir la pregunta de por qué alojar los metadatos LVM thin por separado, si de todos modos se almacenarán en caché en NVMe y funcionarán rápidamente.

La velocidad es importante aquí, pero no es la razón principal. Todo se debe a que la caché es un punto de falla. Puede ocurrir algo con ella, y si los metadatos LVM thin están en caché, esto dará lugar a una pérdida total de todo. Sin los metadatos intactos, será prácticamente imposible recuperar los volúmenes thin.

Al mover los metadatos a un volumen separado que no está en caché, pero sí es rápido, garantizamos la preservación de los metadatos en caso de pérdida o daño de la caché. En este caso, todos los daños causados por la pérdida de la caché estarán localizados dentro de los volúmenes thin, lo que simplificará enormemente el proceso de recuperación. Con alta probabilidad, estos daños podrán recuperarse mediante los registros del FS.

Además, si se realizó una instantánea del volumen thin anteriormente, y después de eso, la caché se sincronizó completamente al menos una vez, gracias a las particularidades del diseño interno de LVM thin, se garantizará la integridad de la instantánea en caso de pérdida de la caché.

Crearemos una nueva VG que se encargará de la provisión thin:

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

Crearemos un pool:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
¿Por qué -Z y?Además de para lo que este modo está diseñado, que es evitar que los datos de una máquina virtual se filtren a otra máquina virtual durante la redistribución del espacio, el zeroing se utiliza adicionalmente para aumentar la velocidad de las escrituras aleatorias en bloques de menos de 64k. Cualquier escritura de menos de 64k en un área previamente no asignada del volumen thin se convertirá en 64K alineados con los límites de la caché. Esto permitirá ejecutar la operación completamente a través de la caché, evitando el dispositivo en caché.

Movemos el 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

Verifiquemos:

#lvs -a -o lv_name,lv_size,devices --units B images
Dispositivos LV LSize
[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)

Crearemos un volumen thin para pruebas:

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

Instalaremos paquetes para pruebas y monitoreo:

#apt-get install sysstat fio

Así es como podemos observar el comportamiento de nuestra configuración de almacenamiento en tiempo real:

#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

¡Cuidado! ¡Recurso!Este código ejecutará 36 pruebas diferentes, cada una de las cuales se llevará a cabo durante 4 segundos. La mitad de las pruebas son de escritura. En 4 segundos en NVMe se puede escribir mucho. 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 intercaladas?Sí. Tiene sentido ejecutar las pruebas de lectura y escritura por separado. Además, es recomendable asegurarse de que todos los cachés estén sincronizados para que las escrituras previas no afecten a la lectura.

Los resultados variarán considerablemente en la primera ejecución y en las siguientes a medida que se llena el caché y el volumen delgado, además, dependiendo de si el sistema logró sincronizar los cachés llenos durante la última ejecución.

Entre otras cosas, recomiendo medir la velocidad en un volumen delgado ya lleno, del cual se acaba de tomar un snapshot. El autor pudo observar cómo una escritura aleatoria se acelera drásticamente justo después de crear el primer snapshot, especialmente cuando el caché aún no está completamente lleno. Esto ocurre gracias a la semántica de copy-on-write, la alineación de bloques del caché y el volumen delgado, y que la escritura aleatoria en RAID 6 se convierte en lectura aleatoria con RAID 6 seguida de escritura en el caché. En nuestra configuración, la lectura aleatoria con RAID 6 es hasta 6 veces (número de SSD SATA en el arreglo) más rápida que la escritura. Dado que los bloques para CoW se asignan secuencialmente desde el grupo delgado, la escritura, en su mayoría, también se convierte en secuencial.

Ambas características se pueden aprovechar de manera efectiva.

Snapshots 'coherentes' de caché

Para reducir el riesgo de pérdida de datos en caso de daño/pérdida del caché, el autor sugiere implementar una práctica de rotación de snapshots que garantice su integridad en este caso.

En primer lugar, gracias a que los metadatos de los volúmenes delgados se encuentran en un dispositivo no caché, los metadatos serán íntegros, y las posibles pérdidas estarán aisladas dentro de los bloques de datos.

El siguiente ciclo de rotación de snapshots garantiza la integridad de los datos dentro de los snapshots en caso de pérdida del caché:

  1. Para cada volumen delgado llamado <имя>, creamos un snapshot llamado <имя>.cached
  2. Estableceremos el migration threshold en un valor razonablemente alto: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. En el ciclo, verificamos la cantidad de bloques sucios en el caché: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' hasta que obtengamos cero. Si no hay cero durante demasiado tiempo, se puede crear temporalmente cambiando la caché al modo writethrough. Sin embargo, dada la velocidad de nuestros arreglos SATA y NVMe SSD, así como su recurso TBW, o podrás captar el momento lo suficientemente rápido sin cambiar el modo de la caché, o tu hardware consumirá completamente su recurso en unos pocos días. Debido a las limitaciones del recurso, el sistema no puede estar bajo una carga de escritura del 100% de manera constante. Nuestros NVMe SSD agotarán completamente su recurso bajo una carga de escritura del 100% en 3-4 días. Los SATA SSD durarán aproximadamente el doble. Por lo tanto, asumiremos que la mayor parte de la carga es de lectura, y la escritura se presenta en picos relativamente breves de alta actividad combinados con una carga baja en promedio.
  4. Tan pronto como se capte (o se haga) el cero, renombramos .cached a .committed. Eliminamos el antiguo .committed en el proceso.
  5. Opcionalmente, si la caché está llena al 100%, se puede recrear con un script, así limpiando. Con la caché medio vacía, el sistema funciona mucho más rápido en escritura.
  6. Establezcamos el umbral de migración en cero: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Esto prohibirá temporalmente la sincronización de la caché en el medio principal.
  7. Esperamos hasta que la caché acumule suficientes cambios #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' o hasta que se active el temporizador.
  8. Repetimos de nuevo.

¿Por qué complicarse con el umbral de migración...?Todo se debe a que, en la práctica real, la escritura 'aleatoria' en realidad no es del todo aleatoria. Si hemos escrito algo en un sector de 4 kilobytes, hay una alta probabilidad de que en los próximos minutos se realice una escritura en ese mismo o en uno de los sectores vecinos (+- 32K).

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

¿Y el código dónde..?Lamentablemente, el autor se considera a sí mismo insuficientemente competente en el desarrollo de scripts bash ya que es 100% autodidacta y practica el desarrollo impulsado por 'google', por lo que considera que el horrible código que sale de sus manos es mejor no usarlo por nadie más.

Creo que los profesionales en este campo podrán representar la lógica descrita anteriormente de forma independiente si es necesario, y quizás incluso adornarla de manera bonita como un servicio systemd, como el autor intentó hacer.

Este sencillo esquema de rotación de instantáneas no solo nos permitirá tener una instantánea completamente sincronizada en un SSD SATA, sino que también permitirá, mediante la herramienta thin_delta, identificar qué bloques han sido modificados después de su creación y, de esta manera, localizar daños en los volúmenes principales, simplificando enormemente la recuperación.

TRIM/DISCARD en libvirt/KVM

Dado que el almacenamiento de datos se utilizará para KVM bajo la gestión de libvirt, sería conveniente enseñar a nuestras VM no solo a ocupar espacio libre, sino también a liberar el espacio que ya no se necesita.

Esto se logra mediante la emulación del soporte para TRIM/DISCARD en discos virtuales. Para ello, es necesario 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'/>
>
<backingStore/>
<target dev='sda' bus='scsi'/>
<alias name='scsi0-0-0-0'/>


</disk>

<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>


</controller>

Estos DISCARD desde los sistemas operativos invitados son procesados correctamente por LVM, y los bloques se liberan adecuadamente tanto en la caché como en el pool delgado. En nuestro caso, esto ocurre, en su mayoría, de forma diferida, al eliminar una instantánea.

Copia de seguridad BTRFS

Usar scripts preconfigurados con extrema precaución y bajo su propio riesgo. El autor escribió este código para sí mismo. Estoy seguro de que muchos usuarios experimentados de Linux tienen similares soluciones improvisadas, y no es necesario copiar las ajenas.

Crearemos un volumen en el dispositivo de respaldo:

#lvcreate -L 256G --name backup backup

Formatear en BTRFS:

#mkfs.btrfs /dev/backup/backup

Crearemos puntos de montaje y montaremos las subparticiones 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

Crearemos directorios para copias de seguridad:

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

Crearemos un directorio para scripts de copia de seguridad:

#mkdir /root/btrfs-backup

Copiamos el script:

Mucho código bash aterrador. Use bajo su propio riesgo. No enviar cartas de queja 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 "Esperando por el bloqueo..."
wait_lock || terminate "Error al obtener el bloqueo. Saliendo..."
echo "Bloqueo obtenido..."
}

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"
si [ -d "$SOURCE_BASE_PATH" ]
then
echo "$SOURCE_BASE_PATH encontrado"
else
echo "$SOURCE_BASE_PATH Archivo no encontrado, creando un snapshot de $SOURCE_PATH a $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
si [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH encontrado fuera de sincronización con la fuente... eliminando..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
si [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH encontrado"
else
echo "$TARGET_BASE_PATH no encontrado. Sincronizando con $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
si [ -d "$SOURCE_PEND_PATH" ]
then
echo "$SOURCE_PEND_PATH encontrado, eliminando..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
si [ -d "$TARGET_PEND_PATH" ]
then
echo "$TARGET_PEND_PATH encontrado, eliminando..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Enviando $SOURCE_PEND_PATH a $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 "Ayuda"
;;
"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 "Ninguno..."
;;
esac
) 98>$LOCK_FILE

EOF

¿Qué hace esto...?Contiene un conjunto de comandos básicos para crear snapshots BTRFS y copiarlos a otro sistema de archivos mediante BTRFS send/receive.

La primera ejecución puede ser relativamente larga, ya que se copiarán todos los datos al principio. Ejecuciones posteriores serán muy rápidas, ya que solo se copiarán los cambios.

Otro script que pondremos en cron:

Un poco 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 días"
$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 esto...?Crea y sincroniza en el sistema de archivos de backup snapshots incrementales de los volúmenes BTRFS enumerados. Luego elimina todos los snapshots creados hace 60 días. Después de la ejecución, aparecerán snapshots fechados de los volúmenes enumerados en los subdirectorios /backup/btrfs/back/remote/

Daremos permisos de ejecución al código:

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

Verificaremos y pondremos en 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 LVM thin

Crearemos un pool thin en el dispositivo de backup:

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

Instalaremos ddrescue, ya que los scripts usarán esta herramienta:

#apt-get install gddrescue

Crearemos un catálogo para los scripts:

#mkdir /root/lvm-thin-backup

Copiamos los scripts:

Hay mucho bash 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 "Esperando por el bloqueo..."
wait_lock || terminate "Error al obtener el bloqueo. Saliendo..."
echo "Bloqueo obtenido..."
}

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 "El LV de origen no es delgado.")
exit 1
fi

if [ "$DIFF_TARGET_POOL" == "" ]
then
(>&2 echo "El LV de destino no es delgado.")
exit 1
fi

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
then
(>&2 echo "Los LVs de origen y destino pertenecen a diferentes grupos delgados.")
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 "La instantánea de metadatos de la piscina delgada ya existe. Suponiendo que es antigua. Liberará la instantánea de metadatos en 5 segundos.")
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 "Error al crear la instantánea de metadatos de la piscina delgada.")
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 'diferente|solo_izquierda|solo_derecha' | sed 's/</"/g' | sed 's/ /"/g' | awk -F'"' '{print $6 "t" $8 "t" $11}' | sed 's/diferente/copia/g' | sed 's/solo_izquierda/copia/g' | sed 's/solo_derecha/descartar/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 encontrado"
else
echo "$DISCARD_LV no se encontró en $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 encontrado"
else
echo "Base de origen no encontrada, creando instantánea de $SOURCE_VG/$SOURCE_LV a $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 "Descartando $SOURCE_BASE_LV_PATH ya que necesitamos reiniciar."
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 encontrado, fuera de sincronización con el origen... eliminando..."
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 encontrado"
else
echo "$TARGET_VG/$TARGET_LV no encontrado. Creando volumen vacío."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Es necesario reiniciar. Descartando origen en $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 "Descartando objetivo en $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ]
then
echo "$SOURCE_PEND_LV_PATH encontrado, eliminando..."
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 encontrado, eliminando..."
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 "Sincronizando $SOURCE_PEND_LV_PATH a $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 encontrado"
else
echo "$SOURCE_BASE_LV_PATH no encontrado"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH encontrado"
else
echo "$TARGET_BASE_LV_PATH no encontrado"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Comparando "$SOURCE_BASE_LV_PATH" con "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Hecho...
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 encontrado"
else
echo "$SOURCE_BASE_LV_PATH no encontrado"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH encontrado"
else
echo "$TARGET_BASE_LV_PATH no encontrado"
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 Sincronizando "$SOURCE_BASE_LV_PATH" a "$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 "Sincronizando $SYNC_LENGTH_BYTES bytes en $SYNC_OFFSET_BYTES desde $SOURCE_BASE_LV_PATH a $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 Hecho...
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 "Ayuda"
;;
"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 "Ninguno..."
;;
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 obtenidas a través de thin_delta, en otro dispositivo de bloques utilizando ddrescue y blkdiscard.

Otro script que pondremos en cron:

Un poco más de bash#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 delgados enumerados. El script dejará instantáneas inactivas de los volúmenes enumerados que son necesarias para rastrear cambios desde la última sincronización.

Este script debe editarse para especificar la lista de volúmenes delgados para los cuales se necesitan copias de seguridad. Los nombres dados son solo ejemplos. Se puede escribir un script que sincronice todos los volúmenes si se desea.

Demos permisos:

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

Verificaremos y pondremos en 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

La primera ejecución será larga, ya que los volúmenes delgados se sincronizarán completamente copiando todo el espacio utilizado. Gracias a los metadatos LVM thin, sabemos qué bloques se utilizan realmente, por lo que solo se copiarán los bloques realmente utilizados de los volúmenes delgados.

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

procfs

#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 mar 26 09:11 .
drwxr-xr-x 1 root root 16 mar 6 09:30 ..
drwxr-xr-x 1 root root 322 mar 26 02:00 .@base
drwxr-xr-x 1 root root 516 mar 6 09:39 .@snap.2020-03-06-09-39-37
drwxr-xr-x 1 root root 516 mar 6 09:39 .@snap.2020-03-06-09-39-57
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 mar 26 09:11 .
drwxr-xr-x 1 root root 16 mar 6 09:30 ..
drwxr-xr-x 1 root root 240 mar 26 09:11 @.@base
drwxr-xr-x 1 root root 22 mar 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 mar 6 09:39 @home.@snap.2020-03-06-09-39-35
drwxr-xr-x 1 root root 22 mar 6 09:39 @home.@snap.2020-03-06-09-39-57
...
drwxr-xr-x 1 root root 240 mar 6 09:39 @.@snap.2020-03-06-09-39-26
drwxr-xr-x 1 root root 240 mar 6 09:39 @.@snap.2020-03-06-09-39-56
...

#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV Tamaño
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 Tamaño
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é tienen que ver las matryoshkas?

Probablemente en que los volúmenes lógicos LVM LV pueden ser volúmenes físicos LVM PV para otros VG. LVM puede ser recursivo, como las matryoshkas. Esto proporciona a LVM una flexibilidad extraordinaria.

P.D.

En el próximo artículo, intentaremos usar varios sistemas de almacenamiento móvil similares / KVM como base para crear un clúster de almacenamiento / vm geográficamente distribuido con respaldo en varios continentes a través de escritorios en casa, internet doméstico y redes P2P.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster