5.1.4. Volúmenes lógicos

A diferencia de otros como zfs o btrfs, ext4 no soporta nativamente la gestión de volúmenes lógicos. Sin embargo, podemos manejar discos y particiones virtuales, ayudándonos de un software adicional llamado LVM. Las ventajas de su uso, ya se han establecido en epígrafes anteriores:

  • Permite agrandar indiscriminadamente el disco: basta con comprar un nuevo disco físico e incluirlo como integrante del disco virtual.

    ../../_images/1%2B1disks.png
  • Permite agrandar indiscriminadamente las particiones sin cuidarse de que el espacio que constituye la partición deba ser contiguo. Cuando tratamos particiones físicas, ampliar una partición es todo un engorro, sobre todo si está encajonada entre otras dos particiones. Por ejemplo, ampliar en el siguiente gráfico sda3, implica también mover sda4:

    ../../_images/ampl-part.png

Logramos, por tanto, muchísima más versatilidad que usando particiones fisicas. En contraprestación, hay una desventaja evidente: las particiones lógicas sólo son visibles con el software de LVM.

Definiciones

Antes de pasar a exponer cómo crear y manejar volúmenes lógicos, es pertinente fijar el significado de algunos términos que se usarán en la exposición:

Volúmen físico (PV)

Son las particiones físicas o incluso discos físicos completos sobre las que se definen los discos virtuales.

Grupo de volúmenes (PV)

Es el conjunto de volúmenes físicos que conforma un disco virtual.

Volúmenes lógicos (LV)

Es el dispoitivo virtual de bloques que alberga un sistemas de ficheros, esto es, lo que hemos venido definiendo como partición virtual a lo largo de nuestra exposición.

En la tarea de constituir volúmenes lógicos:

  • Deben definirse cuáles son los volúmenes físicos.

  • Deben definirse cómo se agrupan esos volúmenes físicos en grupos de volúmenes. Lo habitual es que haya un grupo de volúmenes.

  • Debe particionarse cada grupo de volúmenes en volúmenes logicos.

Nota

Es importante tener presente que GRUB soporta LVM y, por tanto, es capaz de arrancar un Linux aunque los ficheros de su fase 3 y el kernel se encuentren dentro de volúmenes lógicos.

Es muy probable que el software para gestionar volúmenes lógicos ya se encuentre instalado, pero si no es así:

# apt install lvm2

Nota

LVM permite también la creación de RAIDs, pero no es propósito de este apartado, tratarlo. Bajo el epígrafe dedicado a RAUDs hay todo un apartado dedicado a la creación de RAIDs con LVM.

5.1.4.1. Creación

Dentro del grupo de volúmenes podemos incluir todas las particiones que deseemos crear, excepto aquellas necesarias para el arranque del disco. Por tanto, un particionado físico del disco apropiado para crear volúmenes lógicos puede ser el siguiente:

../../_images/part-lvm.png

que usando sgdisk puede realizarse de esta forma:

$ sgdisk -a 8 -n "0:40:2047" -t "0:0xef02" -c "0:BOOTBIOS" \
         -a 2048 -n "0:2048:+100M" -t "0:0xef00" -c "0:EFI" \
         -N 0 -t "3:0x8e00" -c "3:LVM" /tmp/0.disk

de forma que creamos las dos particiones necesarias para el arranque aparte, y una tercera partición destinanda a ser el volumen físico que constituya el grupo de volúmenes. La partición destinada para este fin se nota con el código 0x8E00.

Nota

Por supuesto, podemos crear volúmenes lógicos partiendo también de un particionado DOS

Como estamos particionando un fichero, no podemos acceder directamente a sus particiones como sí sería posible si particionáramos un disco (p.e. de /dev/sda aparecerían /dev/sda1, file:/dev/sda2, etc.). Para hacerlas accesibles es necesario asegurarse de que está cargado el módulo loop:

# modprobe loop

y usar losetup para asociar/desasociar el fichero a un dispositivo de bucle:

# losetup /dev/loop0 /tmp/0.disk
# losetup
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE      DIO LOG-SEC
/dev/loop0         0      0         0  0 /tmp/0.disk   0     512
# losetup -d /dev/loop0

Con todo, asociar el fichero al dispositivo no provoca que aparezcan las particiones, para ello es necesario usar partx

# partx -a /dev/loop0
# ls -1 /dev/loop0*
/dev/loop0
/dev/loop0p1
/dev/loop0p2
/dev/loop0p3
# partx -d /dev/loop0
# ls -1 /dev/loop0*
/dev/loop0

Hecho el particionado y expuestas las particiones, lo primero es declarar que la tercera partición es un volumen físico[1]:

# pvcreate /dev/loop0p3

tras lo cual ya puede constituirse un grupo de volúmenes llamado «VGtest» con este volumen físico:

# vgcreate VGtest /dev/loop0p3
# vgs VGtest
  VG       #PV #LV #SN Attr   VSize    VFree
  VGtest   1   0   0 wz--n-  <19,95g <19,95g

Esta última acción permite empezar a crear particiones lógicas con la orden lvcreate. Por ejemplo:

# lvcreate -L 2G VGtest -n primera
# lvcreate -L 5G VGtest -n segunda
# vgs VGtest
  VG       #PV #LV #SN Attr   VSize    VFree
  VGtest   1   0   0 wz--n-  <19,95g <12,95g
# lvs VGtest
  LV      VG    Attr       LSize Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  primera VGxxx -wi-a----- 2,00g
  segunda VGxxx -wi-a----- 5,00g

Estas acciones han particionado 7 de los 20GB del grupo de volúmenes y crean dos dispostivos virtuales, /dev/VGtest/primera y /dev/VGtest/segunda que podemos tratar como si de particiones fisicas se tratasen. Por ejemplo, podemos dotarlas de un sistema de ficheros:

# mkfs.ext4 -L PRIMERA /dev/VGtest/primera
# mkfs.ext4 -L SEGUNDA /dev/VGtest/primera

5.1.4.2. Disponibilidad

Habitualmente la aparición de los volúmenes lógicos es automática al hacer disponibles los volúmenes que constituyen el grupo de volúmenes. Si no es así, pueden habilitar con la orden:

# vgchange -ay VGtest

Lo que es más útil es deshabilitar los volúmenes lógicos:

# vgchange -an VGtest

ya que es indispensable hacerlo si queremos hacer desaparecer los volúmenes físicos sobre los que se asientan. Por ejemplo, para el caso que nos ocupa, en que hacemos pruebas con un fichero, la única forma de usar partx y losetup para desasociar el fichero al dispositivo de bucle es deshabilitar los volúmenes lógicos porque de lo contrario, fallará:

# partx -d /dev/loop

al encontrar la partición /dev/loop03 ocupada.

5.1.4.3. Consulta

Hay tres tipos de entidades (volúmenes físicos, grupos de volúmenes y volúmenes lógicos) y dos tipos de consultas sobre ellas: la resumida y la extensa, por lo que podemos llegar a hacer seis consultas distintas. En los seis casos, puede añadirse como argumento una entidad concreta (PV, PV o LV) para recibir información exclusivamente de ella o no añadir ninguna y recibir información de todas.

Para volúmenes físicos, las órdenes son:

# pvs
  PV           VG       Fmt  Attr PSize   PFree
  /dev/sdc2    vgdebian lvm2 a--  <465,70g  79,39g
  /dev/loop0p3 VGtest   lvm2 a--  <19,95g <12,95g
# pvdisplay /dev/loop0p3
  --- Physical volume ---
  PV Name               /dev/loop0p3
  VG Name               VGtest
  PV Size               19,95 GiB / not usable 4,98 MiB
  Allocatable           yes
  PE Size               4,00 MiB
  Total PE              5106
  Free PE               3314
  Allocated PE          1792
  PV UUID               36SmEX-lPxG-qFW2-iMGl-1c5T-CLlb-LqLY1

Para grupos de volúmenes:

# vgs
  VG       #PV #LV #SN Attr   VSize    VFree
  VGtest     1   2   0 wz--n-  <19,95g <12,95g
  vgdebian   1   4   0 wz--n- <465,70g  79,39g

# vgdisplay VGtest
  --- Volume group ---
  VG Name               VGtest
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  3
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               0
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               <19,95 GiB
  PE Size               4,00 MiB
  Total PE              5106
  Alloc PE / Size       1792 / 7,00 GiB
  Free  PE / Size       3314 / <12,95 GiB
  VG UUID               P3dDgq-AeHA-7Vur-Jy48-fzlm-wnC1-jf0x85

Y para volúmenes lógicos:

# lvs
  LV      VG       Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  primera VGtest   -wi-a-----   2,00g
  segunda VGtest   -wi-a-----   5,00g
  home    vgdebian -wi-a----- 370,00g
  lxc     vgdebian -wi-a-----   2,00g
  raiz    vgdebian -wi-a----- <12,45g
  swap    vgdebian -wc-a-----  <1,86g

# lvdisplay /dev/VGtest/primera
  --- Logical volume ---
  LV Path                /dev/VGtest/primera
  LV Name                primera
  VG Name                VGtest
  LV UUID                KBMwih-Mctp-rOcv-W3aK-iqTG-2vXo-KlpQxs
  LV Write Access        read/write
  LV Creation host, time choquereta, 2019-11-27 07:40:59 +0100
  LV Status              available
  # open                 0
  LV Size                2,00 GiB
  Current LE             512
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:4

Ver también

El significado de los atributos indicados por lvs puede consultarse en esta página de Github.

5.1.4.4. Modificación

La ventaja fundamental de las volúmenes lógicos es que podemos ampliarlos a voluntad sin que el espacio tenga que ser contiguo. Por ejemplo:

# lvextend -L 3G /dev/VGtest/primera

Aumenta hasta 3GiB el primer volumen lógico, aunque no el sistema de ficheros contenido, por lo que el espacio ocupable seguirá siendo de 2GiB. Para ampliar también el sistema de fichero es necesario, en este caso:

# resize2fs /dev/Vgtest/primera

No obstante, podemos incluir la opción -r y lvextend se encargará de comprobar cuál el sistema de ficheros y aplicar el comando adecuando para que éste colonice el nuevo espacio disponible. En consecuencia las dos órdenes anteriores son equivalente a:

# lvextend -r -L 3G /dev/VGtest/primera

También puede indicarse, en vez de el nuevo tamaño, el incremento. Esta orden:

# lvextend -r -L +1G /dev/VGtest/segunda

aumenta hasta los 6GiB el volumen lógico cuyo anterior tamaño era 5GiB. Es posible también usar porcentajes en vez de tamaños o incrementos absolutos a través de la opción -l[2]:

# lvcreate -l 100%FREE VGtest -n tercera

De esta manera, la nueva partición ocupará todo el espacio libre que uqede en el grupo de volúmenes. El disco físico se ha acabado, pero si «compráramos» otro, podríamos añadirlo como volumen lógico a VGtest y volveríamos a disponer de espacio libre:

# truncate -s 10G /tmp/1.disk
# losetup /dev/loop1 /tmp/1.disk
# pvcreate /dev/loop1
# vgextend VGtest /dev/loop1
# vgs VGtest
  VG     #PV #LV #SN Attr   VSize  VFree
  VGtest   2   2   0 wz--n- 29,94g  9,94g

Ahora el grupo de volúmenes tiene 30GiB, ya que hemos añadido 10GiB más.

Todas estas operaciones son de incremento y no requieren siquiera que desmontemos los sistemas de ficheros para ser llevadas a cabo. En cambio, las operaciones de reducción son más traumáticas ya que, por lo general, requieren dejar hueco y en el caso de reducir particiones lógicas, desmontar previamente el sistema de ficheros que contiene. Por lo demás, el procedimiento es semejante:

# lvresize -r -L -2G /dev/VGtest/segunda
# lvs /dev/VGtest/segunda
  LV      VG     Attr       LSize Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  segunda VGtest -wi-a----- 4,00g
# lvreduce -r -l -25%LV /dev/VGtest/segunda
# lvs /dev/VGtest/segunda
  LV      VG     Attr       LSize Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  segunda VGtest -wi-a----- 3,00g

5.1.4.5. Aprovisionamiento fino

El aprovisionamiento fino (thin provisioning, en inglés) consiste en reservar para un volumen lógico más espacio de disco del que realmente tiene disponible. Es un concepto muy comúnmente usando en virtualización para cuyas máquinas virtuales podemos usar discos de un tamaño mucho mayor del que realmente ocupa su fichero correspondiente. Algo parecido se logra con la orden truncate. Se contrapone al aprovisionamiento grueso, que consiste en justo lo contrario, esto es, en reservar exactamente el tamaño del volumen. Hasta ahora, hemos practicado con LVM este último tipo de aprovisionamiento.

Sin embargo, LVM soporta aprovisionamiento fino que, en principio, puede servir para dos propósitos distintos:

  • Ajustar el tamaño máximo de varios volúmenes lógicos a parte (o todo) del tamaño del grupo de volúmenes. Si no tenemos claro, cuáles van a ser las necesidades de crecimiento de estos volúmenes, esto puede ahorrarnos la necesidad de hacerlos excesivamente pequeños y, según sea la evolución posterior, tener después que aumentarles progresivamente el tamaño.

  • Si instalamos sobre un disco pequeño con intención de que el sistema acabe corriendo sobre un disco mayor, crear los volúmenes lógicos con el tamaño que tendrán en el disco final. Usaremos esto al plantear la instalación del servidor.

Lo primero, antes de empezar, es asegurarse de que está instalado el paquete thin-provisioning-tools, hecho lo cual, podemos hacer pruebas con un nuevo disco:

# truncate -s 500M 0.disk
# losetup /dev/loop0 0.disk
# vgcreate VGtest /dev/loop0

Para probar el concepto crearemos dos volúmenes lógicos: el primero normal y el segundo un «pool», esto es, un volumen en el que encerraremos los volúmenes para los que deseamos aprovisionamiento fino:

# lvcreate -L 100m -n fuera VGtest
# lvcreate --thinpool pool -l 100%FREE VGtest
# lvs VGtest
  LV    VG     Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  fuera VGtest -wi-a----- 100,00m
  pool  VGtest twi-a-tz-- 388,00m             0,00   10,84

El primero es un volumen de 100MiB y, además, no podrá cambiar su tamaño a menos que agrandemos el grupo de volúmenes con, por ejemplo, otro disco. El segundo es el pool dentro del que podemos crear volúmenes lógicos con aparentemente cualquier tamaño:

# lvcreate -T -n tvol1 -V 1G VGtest/pool
# lvcreate -T -n tvol2 -V 2G VGtest/pool
# lvs VGtest
  LV    VG     Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  fuera VGtest -wi-a----- 100,00m
  pool  VGtest twi-aotz-- 388,00m             0,00   11,04
  tvol1 VGtest Vwi-a-tz--   1,00g pool        0,00
  tvol2 VGtest Vwi-a-tz--   2,00g pool        0,00

Al crear ambos volúmenes, se nos refiere la opción activation/thin_pool_autoextend_threshold, porque su valor es 100:

# lvm dumpconfig activation/thin_pool_autoextend_threshold
thin_pool_autoextend_threshold=100

La variable fija el tanto por cierto de ocupación del pool para el que una vez alcanzado se incrementa el tamaño en un porcentaje fijado por:

# lvm dumpconfig activation/thin_pool_autoextend_percent
thin_pool_autoextend_percent=20

pero justamente un valor de 100% deshabilita la posibilidad. Si ponemos un valor más bajo (p.e el 85%), sí se llevará a cabo la ampliación automática… si se puede, que no es nuestro caso, ya que el grupo de volúmenes está completamente lleno.

Si probamos a dar formato a uno de los volúmenes del pool, comprobaremos el sistema de ficheros participa de la ficción del tamaño:

# mkfs.ext4 -L TVOL2 /dev/VGtest/tvol2
# mount /dev/VGtest/tvol2 /mnt/
# df -h /mnt/
S.ficheros               Tamaño Usados  Disp Uso% Montado en
/dev/mapper/VGtest-tvol2   2,0G   6,0M  1,8G   1% /mnt

Sin embargo, el espacio real es el que ocupa el pool:

# lvs VGtest/pool
  LV   VG     Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  pool VGtest twi-aotz-- 388,00m             25,14  12,21

Nota

No soportan aprovisionamiento fino ni el instalador de debian, ni GRUB, lo cual dificulta enormemente meter el sistema de ficheros raíz en un volumen de este tipo. Lo primero impide crear o aprovechar volúmenes de aprovisionamiento fino durante el proceso de instalación; y lo segundo obliga a no incluir dentro de ellos el núcleo y el sistema de ficheros inicial (initrd), lo cual se traduce en montar /boot aparte o en copiar ambos ficheros en la partición ESP.

5.1.4.6. Instantáneas

Una instantánea consiste en el almacenamiento del estado del sistema de archivos en el momento de llevarla a cabo. Para ello, no se hace una copia del sistema (lo cual es costoso en tiempo y equivaldría a una copia de seguridad), sino que al modificarse por primera vez tras la creación de la instantánea el contenido de un fichero, se guarda en la instantánea una copia de éste sin la modificación. De esta forma, el estado de la instantánea es la suma de los archivos que no han llegado a ser modificados, guardados en el volumen original, más la copia de los archivos sí modificados, guardados en la instantánea.

Las instantáneas reparan las pérdidas por modificación o borrado no deseados, papel que también puede cumplir una copia de seguridad, pero a diferencia de éstas, no sirven como medida contra los fallos de disco o la corrupción de datos, ya que requieren el sistema original y pueden estar almacenadas sobre el mismo dispositivo físico.

Hay dos mecanismos para la creación de instantáneas:

  • Mediante el uso de sistemas de ficheros que las soporten nativamente como ZFS o BtrFS.

  • Mediante software adicional que se encargue de hacer las instantáneas como el software que nos ocupa en Linux o la restauración del sistema en los sistemas Windows.

Para llevar a cabo instantáneas con LVM el procedimiento es simple: basta con crear un volumen lógico que se declare que sirve para almacenar la instantánea de otro volumen lógico. Por ejemplo, vamos a dar formato a uno de los volúmenes lógicos y a escribir dentro de él:

# mkfs.ext4 -L PRIMERA /dev/VGtest/primera
# mkdir /tmp/{original,snap}
# mount /dev/VGtest/primera /tmp/original
# echo "Hola" > /tmp/original/saluto.txt

Hecho lo cual, podemos crear una instantánea del estado actual de «primera»:

# lvcreate -s -n primera_snap -L 50M /dev/VGtest/primera

donde es importante tener presente que el tamaño como máximo puede ser el tamaño del volumen original[3] (-l 100%ORIGIN). También se puede añadir un parámetro adicional para indicar sobre qué volumen físico de los que integran el grupo de volúmenes se quiere definir la instantánea. Hecho esto, podemos practicar dos estrategias:

  1. La obvia que es continuar trabajando sobre el volumen original y, si en algún momento decidimos revertir los cambios, recuperar la instantánea. Un ejemplo en que es una buena opción esta estrategia es ante una actualización. Si sospechamos que algo puede ir mal, podemos hacer antes una instantánea y dependiendo del resultado, revertir al estado anterior a la instalación o desechar la instantánea.

  2. Trabajar sobre la instantánea sin alterar el volumen original, ya que si recuperamos la instantánea lo que haremos será aplicar los cambios de la instantánea sobre el volumen original. Esta opción es adecuada en los casos en que queremos que el sistema siga funcionando en producción del mismo modo, mientras nosotros introducimos cambios (p.e. en algunos scripts). De nuevo, podrá darse el caso de que decidamos aplicar los cambios (recuperando la instantánea) o desecharlos (borrándola).

Trabajando sobre el propio volumen

Para nuestro ejemplo, podemos hacer algunos cambios simples:

# echo "Adios" >> /tmp/original/saludo.txt
# cp /etc/resolv.conf /tmp/original

Con estas acciones la instantánea debería ir paulatimente incrementando su ocupación (ya que necesita almacenar las copias de los ficheros) y es conveniente vigilar este dato, ya que malograremos la instantánea, si superamos la capacidad máxima:

# lvs -o data_percent /dev/VGtest/primera_snap
Data%
0.09

Nota

Si comprobamos que la ocupación se aproxima al 100%, podemos usar lvextend como se hace en los volúmenes lógicos normales.

Dependiendo de si los cambios sobre el volumen son aceptables o no, tocará desechar la instantánea:

# lvremove /dev/VGtest/primera_snap

o revertir los cambios sobre el volumen:

# umount /tmp/original
# lvconvert --merge /dev/VGtest/primera_snap

cuyo efecto será que el volumen vuelta al estado de la instantánea y que el volumen con la instantánea desaparezca automáticamente.

Advertencia

Es importante que tanto el volumen original como el de la instantánea se encuentren desmontados en el momento de hacer la fusión.

Trabajando sobre la instantánea

Alternativamente, podemos trabajar sobre la instantánea y dejar sin alterar el volumen original. Para ello, basta con montar la instantánea:

# mount /dev/VGtest/primera_snap /tmp/snap

y realizar los cambios sobre ella:

# echo "Adios" >> /tmp/snap/saludo.txt
# cp /etc/resolv.conf /tmp/snap

En este caso, las operaciones para desechar o revertir los cambios son justamente las opuestas. Si deseamos aplicar los cambios sobre el volumen, necesitamos fusionar con la instantánea:

# umount /tmp/{original,snap}
# lvconvert --merge /dev/VGtest/primera_snap

y, si deseamos desecharlos, eliminar el volumen de la instantánea:

# umount /tmp/snap
# lvremove /dev/VGtest/primera_snap

Obsérvese que si nuestra intención fuera tener varias instantáneas de un mismo volumen lógico, entonces tendríamos que crear un volumen de instantánea para cada una de ellos, lo que resultaría en la reserva (y malgasto) de una ingente cantidad de espacio en el grupo de volúmenes. La solución es utilizar el aprovisionamiento fino para almacenar los volúmenes de instantánea.

Instantáneas con aprovisionamiento fino

Dado que el aprovisionamiento fino permite crear múltiples volúmenes lógicos sin que realmente exista el espacio ocupado por todos ellos, es muy útil combinarlo con las capacidades de instantánea de LVM para tener un sistema de archivos con varios puntos de restauración.

Puede utilizarse una volumen lógico fuera del «pool» como volumen original (véase esta documentación de RedHat), pero dado que este volumen original debe quedar como de sólo lectura y desactivo, planteemos que el volumen original está dentro del propio «pool». Para ilustrarlo partamos del siguiente disco ficticio al que definimos un grupo de volúmenes:

# truncate -s 1G 0.disk
# losetup /dev/loop0 0.disk
# vgcreate VGtest /dev/loop0

y un «pool» para albergar un volumen y sus instantáneas:

# lvcreate --thinpool pool -L 500M VGtest

y el propio volumen:

# lvcreate -T -n original -V 350M VGtest/pool
# mkfs.ext4 -L ORIGINAL /dev/VGtest/original
# mount /dev/VGtest/original /mnt

A partir de este momento podemos ir generando instantáneas que almacenen distintos estados del sistema de archivos. Por ejemplo, si inmediatamente generamos una instantánea, tendremos el estado en el que el sistema estaba vacío:

# lvcreate -s -n 0snap VGtest/original

Podemos crear, borrar y modificar archivos:

# echo "Primer archivo" > /mnt/uno.txt

y estos cambios se aplicarán a la partición original, ya que es esta la que se encuentra activa:

# lvs VGtest
  LV       VG     Attr       LSize   Pool Origin   Data%  Meta%  Move Log Cpy%Sync Convert
  0snap    VGtest Vwi---tz-k 352,00m pool original
  original VGtest Vwi-aotz-- 352,00m pool          7,79
  pool     VGtest twi-aotz-- 500,00m               5,57   11,72

En un estado posterior, podremos generar una nueva instantánea, que fijará estos cambios:

# lvcreate -s -n 1snap VGtest/original

Todas las instantáneas que generemos de este modo están inactivas (no aparece la a entre sus atributos) y, además, no se activarán aunque así lo ordenemos (atributo k). Si nuestra intención fuera recuperar algún archivo de ellas deberíamos corregir esto, hacer las operaciones pertinentes y volver a dejar todo como estaba. Por ejemplo, si por error perdemos uno.txt:

# rm -f /mnt/uno.txt

aún podremos recuperarlo de la instantánea «1snap», para lo cual deberemos hacer:

# lvchange -kn -ay -pr VGtest/1snap

que permite la activación (-k n), activa el volumen (-a y) y, por precaución, hace el volumen de sólo lectura (-p r). Hecho esto, podemos montar el volumen sobre otro punto:

# mount /dev/VGtest/1snap /tmp/mnt

recuperar el archivo perdido:

# cp /tmp/mnt/uno.txt /mnt

y dejar todo como estaba en un principio:

# umount /tmp/mnt
# lvchange -an -ky -prw Vgtest/1snap

Hay, no obstante, un modo más cómodo de gestionar todo esto, que veremos justamente a continuación:

snapper

snapper es un programita de la línea de comandos que nos permite hacer y mantener instantáneas de una manera bastante cómoda. Además, gestiona tanto instantáneas basadas en aprovisionamiento fino de LVM como basadas en BtrFS, lo cual permite usar una misma interfaz de usuario para ambos tipos de instantáneas[4].

Partamos del supuesto anterior:

# truncate -s 1G 0.disk
# losetup /dev/loop0 0.disk
# vgcreate VGtest /dev/loop0
# lvcreate --thinpool pool -L 500M VGtest
# lvcreate -T -n original -V 350M VGtest/pool
# mkfs.ext4 -L ORIGINAL /dev/VGtest/original
# mount /dev/VGtest/original /mnt

en que ya hemos preparado el volumen lógico con un sistema de archivos y lo hemos montado. A partir de este momento, podemos centrarnos en que el sistema de archivos está montado sobre /mnt y usar snapper olvidádonos de los comandos propios de LVM. Lo primero es crear una configuración asociada al punto de montaje que nos sirva para generar las instantáneas:

# snapper -c misnap create-config -f "lvm(ext4)" /mnt

Nota

Si utilizáramos snapper para gestionar las instantáneas de un sistema de archivos BtrFS, la única diferencia sería la declaración del sistema (-f «btrfs»), todo lo demás sería idéntico y las órdenes a partir de ésta las mismas.

Creada la configuración con nombre «misnap», podemos consultar las instantáneas que en este punto hay creadas:

# snapper -c misnap list
 # | Tipo   | Pre número | Fecha | Usuario | Limpieza | Descripción | Información del usuario
---+--------+------------+-------+---------+----------+-------------+------------------------
0  | single |            |       | root    |          | current     |

Ahora mismo sólo hay un estado del sistema de archivos montado sobre /mnt, el 0, lo cual es normal, porque aún no hemos creado ninguna instantánea. Esta instantánea 0 siempre representará el estado en funcionamiento, este es, siempre será el estado sobre el que se reflejen los cambios cuando hagamos operaciones sobre el sistema de archivos.

Aunque podemos olvidarnos de qué es lo que ocurre por debajo (o sea, cómo se comporta LVM) e incluso desconocerlo, podemos echarle un ojo, visto que acabamos aprender cómo gestionar las instantáneas directamente:

# lvm vgtest
  lv                 vg     attr       lsize   pool origin   data%  meta% move log cpy%sync convert
  original           vgtest vwi-aotz-- 352,00m pool          5,68
  pool               vgtest twi-aotz-- 500,00m               4,06 11,43

Como es natural, al no haberse generado ninguna instantánea, no se ha creado ningún volumen nuevo. Generemos una primera instantáne que refleje el estado inicial del sistema de archivos (completamente vacío):

# snapper -c misnap create --description "Estado inicial"

lo cual debe provocar la aparición de una primera instantánea:

# snapper -c misnap list
 # | Tipo   | Pre número | Fecha                    | Usuario | Limpieza | Descripción    | Información del usuario
---+--------+------------+--------------------------+---------+----------+----------------+------------------------
0  | single |            |                          | root    |          | current        |
1  | single |            | vie 15 oct 2021 17:20:11 | root    |          | Estado inicial |

representada por el identificador 1. Internamente, es obvio que ha surgido un nuevo volumen de aprovisionamiento fino:

# lvm vgtest
  lv                 vg     attr       lsize   pool origin   data%  meta% move log cpy%sync convert
  original           vgtest vwi-aotz-- 352,00m pool          5,68
  original-snapshot1 vgtest vri---tz-k 352,00m pool original
  pool               vgtest twi-aotz-- 500,00m               4,06 11,43

y el comportamiento es idéntico al que se producía cuando operábamos directamente con LVM. Operemos un cambio sobre el sistema de archivos:

# echo "1" > /mnt/uno.txt

La ventaja de usar snapper es que es mucho más cómodo ver y recuperar estados antiguos. Por ejemplo, para comprobar qué diferencia hay entre el estado actual (el 0) y el inicial (el 1):

# snapper -c misnap status 1..0
+..... /mnt/uno.txt

O sea, 0 ha añadido (»+») el archivo indicado al estado representado con 1. Lo contrario (ha perdido un archivo), se representa con «-» y el cambio en el contenido de un archivo con una «c». Si queremos guardar este nuevo estado:

# snapper -c misnap create --description "Estado 1º"
# snapper -c misnap list
 # | Tipo   | Pre número | Fecha                    | Usuario | Limpieza | Descripción    | Información del usuario
---+--------+------------+--------------------------+---------+----------+----------------+------------------------
0  | single |            |                          | root    |          | current        |
1  | single |            | vie 15 oct 2021 17:20:11 | root    |          | Estado inicial |
2  | single |            | vie 15 oct 2021 17:34:31 | root    |          | Estado 1º      |

Supongamos ahora que cometemos un desliz y cambias equivocadamente el contenido del archivo:

# echo "" > /mnt/uno.txt
# snapper -c misnap status 2..0
c..... /mnt/uno.txt
# snapper -c misnap status 1..2
+..... /mnt/uno.txt

Podemos comprobar cuál es el cambio muy fácilmente:

# snapper -c misnap diff 2..0 /mnt/uno.txt
--- /mnt/.snapshots/2/snapshot/uno.txt  2021-10-15 17:27:26.000000000 +0200
+++ /mnt/uno.txt        2021-10-15 17:37:07.000000000 +0200
@@ -1 +1 @@
-1
+

que muestra la diferencia haciendo uso de la orden diff. Si hubiéramos prescindido del archivo concreto, la orden diff se hubiera ejecutado para cada uno de los archivos distintos entre ambos estados. También podemos recuperar una versión antigua del archivo en el estado actual:

# snapper -c misnap undochange 2..0 /mnt/uno.txt
crear:0 modificar:1 eliminar:0
# cat /mnt/uno.txt
1

que devolverá el archivo al estado que tenía cuando se hizo la instantánea 2. También pueden eliminarse instantáneas con la suborden delete:

# snapper -c misnap delete 2

Las instantáneas generadas con snapper no se limitan a esto. La configuración creada inicialmente determina su comportamiento:

# snapper -c misnap get-config
Clave                  | Valor
-----------------------+----------
ALLOW_GROUPS           |
ALLOW_USERS            |
BACKGROUND_COMPARISON  | yes
EMPTY_PRE_POST_CLEANUP | yes
EMPTY_PRE_POST_MIN_AGE | 1800
FREE_LIMIT             | 0.2
FSTYPE                 | lvm(ext4)
NUMBER_CLEANUP         | yes
NUMBER_LIMIT           | 50
NUMBER_LIMIT_IMPORTANT | 10
NUMBER_MIN_AGE         | 1800
QGROUP                 |
SPACE_LIMIT            | 0.5
SUBVOLUME              | /mnt
SYNC_ACL               | no
TIMELINE_CLEANUP       | yes
TIMELINE_CREATE        | yes
TIMELINE_LIMIT_DAILY   | 10
TIMELINE_LIMIT_HOURLY  | 10
TIMELINE_LIMIT_MONTHLY | 10
TIMELINE_LIMIT_WEEKLY  | 0
TIMELINE_LIMIT_YEARLY  | 10
TIMELINE_MIN_AGE       | 1800

El significado de estas variables puede consultar en la página del manual snapper-configs. Por ejemplo, la generación automática de instantáneas está habilitada por defecto y un algoritmo (cleanup) que determina qué instantáneas periódicas conservar. De hecho, como nos hemos demorado un poco al escribir este epígrafe, al programa le ha dado tiempo ha hacer una instantánea automática:

# snapper -c misnap list
 # | Tipo   | Pre número | Fecha                    | Usuario | Limpieza | Descripción    | Información del usuario
---+--------+------------+--------------------------+---------+----------+----------------+------------------------
0  | single |            |                          | root    |          | current        |
1  | single |            | vie 15 oct 2021 17:20:11 | root    |          | Estado inicial |
2  | single |            | vie 15 oct 2021 17:34:31 | root    |          | Estado 1º      |
3  | single |            | vie 15 oct 2021 18:00:32 | root    | timeline | timeline       |

Obsérvese que hay instantáneas generadas manualmente y otras generadas automáticamente; y en las variables de configuración éstas refieren las limitaciones en el número de unas y otras. Al generar una instantánea manualmente se pueden marcar como importante del siguiente modo:

# snapper -c misnap create -d "Estado 3º" -u "important=yes"

lo cual hace que el algoritmo de limpieza de las instantáneas manuales las trate de un modo especial. En cualquier caso, si se quiere gestionar las instantáneas de forma totalmente manual, basta con cambiar la configuración:

# snapper -c misnap set-config "TIMELINE_CREATE=no" "NUMBER_CLEANUP=no"

y se deshabilitará la creación de instantáneas automáticas y la limpieza de instantáneas antiguas (de lo que tendremos que encargarnos nosotros usando la suborden delete).

Notas al pie