Introducción

ZFS es un sistema de ficheros que cuenta con una serie de características avanzadas, tales como auto-reparación o copy-on-write. No obstante, aún siendo extremádamente útil en el entorno de servidores, ZFS no está tan extendido como cabría esperar. Su licencia CDDL (incompatible con la licencia GPL de Linux) y diversos acontecimientos durante su desarrollo pusieron bastantes trabas en la implementación de este sistema de ficheros en el Kernel Linux. Dicho esto vamos a simular un escenario con Vagrant donde tendremos una máquina a la que le agregaremos varios discos, para posteriormente, instalar ZFS y “jugar” un poco con él.

Preparación del escenario

Si queréis probarlo, os dejo el Vagrantfile donde básicamente creamos una máquina Debian Buster con 5 discos adicionales de 400Mb cada uno, y añadimos los repositorios de back-ports de Debian. Estos repositorios nos harán falta a la hora de instalar el paquete, ya que al no tener una licencia GPL, el propio Debian no puede incluirlo en la rama main y mucho menos en los paquetes de la instalación. Una vez creada la máquina, actualizamos el sistema e instalamos el paquete zfsutils-linux. Pero antes debemos instalar los linux-headers, que en mi caso corresponde con la versión del kernel 4.19.0.6.

apt upgrade -y
apt install -y linux-headers-4.19.0-6-amd64
apt install -yt buster-backports dkms spl-dkms
apt install -yt buster-backports zfs-dkms zfsutils-linux

Para comprobar que zfs está funcionando podemos ejecutar:

root@zfsMachine:~# systemctl status zfs-mount
● zfs-mount.service - Mount ZFS filesystems
   Loaded: loaded (/lib/systemd/system/zfs-mount.service; enabled; vendor preset: enabled)
   Active: active (exited) since Tue 2020-01-21 21:20:39 CET; 19min ago
     Docs: man:zfs(8)
 Main PID: 6631 (code=exited, status=0/SUCCESS)
    Tasks: 0 (limit: 1150)
   Memory: 0B
   CGroup: /system.slice/zfs-mount.service

ene 21 21:20:39 zfsMachine systemd[1]: Starting Mount ZFS filesystems...
ene 21 21:20:39 zfsMachine systemd[1]: Started Mount ZFS filesystems.

Creación y configuración de los “pool”

Un pool en ZFS sería algo así lo que un grupo de volúmenes en LVM. La principal diferencia es que con ZFS fusionamos lo que es la capa de volúmenes lógicos y la capa de RAID software. Es por esto que durante la creación y configuración de un pool, podemos definir el tipo de RAID que vamos a crear, así como otras diversas opciones; discos caché, tamaño de sectores, etc. Los tipos de RAID soportados por ZFS son los siguientes.

En esta ocasión vamos a trabar con RAIDZ-2 por lo que necesitaremos 4 discos. La creación de estos pool se realiza con el comando zpool.

zpool create -f EjemploRaidZ raidz2 /dev/sdb /dev/sdc /dev/sdd /dev/sde
zpool status
  pool: EjemploRaidZ
 state: ONLINE
  scan: none requested
config:

	NAME          STATE     READ WRITE CKSUM
	EjemploRaidZ  ONLINE       0     0     0
	  raidz2-0    ONLINE       0     0     0
	    sdb       ONLINE       0     0     0
	    sdc       ONLINE       0     0     0
	    sdd       ONLINE       0     0     0
	    sde       ONLINE       0     0     0

errors: No known data errors

De la misma forma que en los RAID tradicionales (como cuando los gestionamos con mdadm), podemos añadir discos de reserva (hot spare). De esta manera, si uno de los discos falla, el hot spare se reemplazaría de forma automática. Para añadir un disco en modo reserva ejecutamos:

zpool add EjemploRaidZ spare sdf
zpool status
  pool: EjemploRaidZ
 state: ONLINE
  scan: none requested
config:

	NAME          STATE     READ WRITE CKSUM
	EjemploRaidZ  ONLINE       0     0     0
	  raidz2-0    ONLINE       0     0     0
	    sdb       ONLINE       0     0     0
	    sdc       ONLINE       0     0     0
	    sdd       ONLINE       0     0     0
	    sde       ONLINE       0     0     0
	spares
	  sdf         AVAIL

errors: No known data errors

Simulación de fallos y pruebas de redundancia

Vamos a ejecutar una serie de comandos para simular un fallo de un disco con el parámetro offline y vamos a obsevar como se reemplaza automáticamente y sin perder información. Antes vamos a crear varios ficheros para comprobar que siguen manteniendo su integridad. Aunque a priori la gestión de zfs pueda parecerse en gran medida a la gestión de RAID con mdadm, la verdad es que conceptualmente no tienen nada que ver. Ya de primeras cuando gestionamos un RAID software con mdadm, necesitamos (tras haber particionado, aplicado una capa de LVM, o no) montar el dispositivo de bloques que se nos ha generado. En contraposición, el pool que se nos genera con zfs se monta automáticamete en la raiz del sistema con el nombre que le hayamos asignado. No obstante, también podríamos generar volúmenes y posteriormente darles el formato que queramos (con la ventaja de que seguiría funcionando por debajo con zfs). Aunque esto último lo veremos en el siguiente apartado. Por el momento vamos a generar una serie de ficheros en /EjemploRaidZ.

#Creamos un arbol de directorios
mkdir -p /EjemploRaidZ/directorio/subdirectorio/
#Creamos ficheros aleatorios
dd if=/dev/urandom of=/EjemploRaidZ/ficheroRandom bs=64K count=300
dd if=/dev/urandom of=/EjemploRaidZ/directorio/ficheroRandom bs=64K count=600
echo "Prueba de un fichero" > /EjemploRaidZ/directorio/subdirectorio/fichtest

Antes de hacer fallar uno de los discos, vamos a generar un checksum de los dos ficheros aleatorios con el comando md5sum. De esta manera si cambia aunque sea un byte de alguno de los dos ficheros, el checksum será completamente distinto.

md5sum ficheroRandom 
935a8851f013f41e0dab7afad20b7377  ficheroRandom
md5sum directorio/ficheroRandom 
49b5591a6b7daecbecd185bae9e6f769  directorio/ficheroRandom

Para simular el fallo del disco, vamos a ejecutar el siguiente comando.

zpool offline -f EjemploRaidZ sdc
zpool status
  pool: EjemploRaidZ
 state: DEGRADED
status: One or more devices are faulted in response to persistent errors.
	Sufficient replicas exist for the pool to continue functioning in a
	degraded state.
action: Replace the faulted device, or use 'zpool clear' to mark the device
	repaired.
  scan: none requested
config:

	NAME          STATE     READ WRITE CKSUM
	EjemploRaidZ  DEGRADED     0     0     0
	  raidz2-0    DEGRADED     0     0     0
	    sdb       ONLINE       0     0     0
	    sdc       FAULTED      0     0     0  external device fault
	    sdd       ONLINE       0     0     0
	    sde       ONLINE       0     0     0
	spares
	  sdf         AVAIL

errors: No known data errors

md5sum ficheroRandom 
935a8851f013f41e0dab7afad20b7377  ficheroRandom
md5sum directorio/ficheroRandom 
49b5591a6b7daecbecd185bae9e6f769  directorio/ficheroRandom

Como podemos comprobar, aunque el RAID esté degradado, gracias a que tiene una tolerancia a fallos (gracias a los 2 bits de paridad repartidos por todos los discos), seguimos obteniendo los mismos checksum por lo tanto la integridad de los ficheros está garantizada. No obstante ahora mismo nos encontramos sin redundancia, por lo que si quisiésemos acoplar el disco de reserva solo tenemos que ejecutar:

zpool replace -f EjemploRaidZ sdc sdf
zpool status
  pool: EjemploRaidZ
 state: DEGRADED
status: One or more devices are faulted in response to persistent errors.
	Sufficient replicas exist for the pool to continue functioning in a
	degraded state.
action: Replace the faulted device, or use 'zpool clear' to mark the device
	repaired.
  scan: resilvered 28.7M in 0 days 00:00:00 with 0 errors on Sun Jan 26 17:27:07 2020
config:

	NAME          STATE     READ WRITE CKSUM
	EjemploRaidZ  DEGRADED     0     0     0
	  raidz2-0    DEGRADED     0     0     0
	    sdb       ONLINE       0     0     0
	    spare-1   DEGRADED     0     0     0
	      sdc     FAULTED      0     0     0  external device fault
	      sdf     ONLINE       0     0     0
	    sdd       ONLINE       0     0     0
	    sde       ONLINE       0     0     0
	spares
	  sdf         INUSE     currently in use

errors: No known data errors

Ahora tenemos dos opciones; podemos indicar que hemos reparado el disco que nos ha fallado, o podemos eliminarlo del RAID y dejar funcionando el disco que antes teníamos de reserva. Si optamos por la primera opción, tal y como nos indica la propia salida del estado del zpool, ejecutamos zpool clear EjemploRaidZ sdc para indicar que ya hemos reparado el disco. Si por el contrario, el disco que nos ha fallado no podemos repararlo, el método correcto sería eliminarlo del RAID (dejar trabajando al antiguo disco de reserva) y añadir un nuevo disco como reserva. En este segundo escenario ejecutaríamos zpool detach EjemploRaidZ sdc para extraerlo del RAID y después añadir otro disco como reserva, tal y como hicimos antes.

Restauración de un RAID con “rollback”

ZFS tiene una funcionalidad parecida al rollback de las bases de datos de Oracle, y es que en un determinado momento en el que hemos tenido un número de fallos mayor al nivel de redundancia que teníamos disponible, podemos volver a un estado de este mismo RAID, anteior a dichos fallos. Para poder hacer esto primero tendremos que guardar una snapshot, que será nuestro “punto de guardado” al que podremos volver posteriormente. Para crearlo tan solo ejecutamos:

zpool checkpoint EjemploRaidZ

Vamos a generar los checksum igual que antes para comprobar la integridad de los archivos una vez que restauremos el checkpoint.

md5sum directorio/ficheroRandom 
49b5591a6b7daecbecd185bae9e6f769  directorio/ficheroRandom
md5sum ficheroRandom 
935a8851f013f41e0dab7afad20b7377  ficheroRandom

Ahora establecemos como FAULTED dos de los discos del RAID.

zpool offline -f EjemploRaidZ sdc
zpool offline -f EjemploRaidZ sde
zpool status
  pool: EjemploRaidZ
 state: DEGRADED
status: One or more devices are faulted in response to persistent errors.
	Sufficient replicas exist for the pool to continue functioning in a
	degraded state.
action: Replace the faulted device, or use 'zpool clear' to mark the device
	repaired.
  scan: resilvered 28.6M in 0 days 00:00:00 with 0 errors on Sun Jan 26 17:37:59 2020
checkpoint: created Sun Jan 26 17:49:02 2020, consumes 482K
config:

	NAME          STATE     READ WRITE CKSUM
	EjemploRaidZ  DEGRADED     0     0     0
	  raidz2-0    DEGRADED     0     0     0
	    sdb       ONLINE       0     0     0
	    sdf       FAULTED      0     0     0  external device fault
	    sdd       ONLINE       0     0     0
	    sde       FAULTED      0     0     0  external device fault

errors: No known data errors

Para restaurar el estado del RAID, lo exportamos e importamos utilizando el parámtero --rewind-to-checkpoint.

zpool export EjemploRaidZ
zpool import --rewind-to-checkpoint EjemploRaidZ
zpool status
  pool: EjemploRaidZ
 state: ONLINE
  scan: none requested
config:

	NAME          STATE     READ WRITE CKSUM
	EjemploRaidZ  ONLINE       0     0     0
	  raidz2-0    ONLINE       0     0     0
	    sdb       ONLINE       0     0     0
	    sdf       ONLINE       0     0     0
	    sdd       ONLINE       0     0     0
	    sde       ONLINE       0     0     0

Y comprobamos los checksum

md5sum ficheroRandom 
935a8851f013f41e0dab7afad20b7377  ficheroRandom
md5sum directorio/ficheroRandom 
49b5591a6b7daecbecd185bae9e6f769  directorio/ficheroRandom

Como podemos ver, todo está correcto!

Gestión eficiente del almacenamiento

Copy on write [COW]

Básicamente COW es una funcionalidad cuyo fin es el ahorro de espacio (tanto en capacidad como en inodos) a través de un tipo de copia diferencial. Para hacer uso de esto, tenemos que especificarlo con el parámetro específico de cada paquete. Por ejemplo, en el caso del comando cp, tendríamos que utilizar reflink=always. De esta forma, se crearía un fichero que funcionaría como un puntero al original e iría aumentando de tamaño conforme se fuese diferenciando del original. No obstante, si nos encontramos en la versión de OpenZFS, empaquetada para Debian Buster, a día de hoy, esta funcionalidad no está completamente soportada.

root@zfsMachine:/EjemploRaidZ# cp --reflink=always ficheroRandom ficheroRandom2
cp: failed to clone 'ficheroRandom2' from 'ficheroRandom': Operation not supported

Deduplicación

En esencia es igual que el COW, solo que cuando lo activamos, no nos haría falta especificarlo como hicimos antes. Para activarlo tan solo ejecutamos:

# Sintaxis
# zfs set dedup=on|verify zfspool[/vol]
zfs set dedup=on EjemploRaidZ

Creación de volúmenes lógicos

Esta funcionalidad es de gran utilidad ya que podríamos utilizar ZFS como base de una SAN, gracias a que estos volúmenes se comportan como cualquier dispositivo de bloques a ojos del sistema operativo. Podríamos particionarlos, darles un formato, etc, y todo esto funcionando por debajo ZFS, incluyendo todas las ventajas que hemos mencionado antes! Para crear estos dispositivos de bloques necesitamos tener un zpool, como el que hicimos antes. La sintaxis básica sería la siguiente:

zfs create -V [tamaño] [nombre-del-pool]/[nombre-del-volumen]

En mi caso sería:

zfs create -V 200mb EjemploRaidZ/vol1
zfs list
NAME                USED  AVAIL     REFER  MOUNTPOINT
EjemploRaidZ        209M   428M     35.9K  /EjemploRaidZ
EjemploRaidZ/vol1   208M   637M     17.9K  -

Para poder darle algún tipo de uso dentro del sistema, tendríamos que darle un formato reconocible, en este caso lo formatearemos con ext4 y lo montaremos en /home/vagrant/vol1:

mkfs.ext4 /dev/zvol/EjemploRaidZ/vol1
mount /dev/zvol/EjemploRaidZ/vol1 /home/vagrant/vol1

Comprobamos como se ha montado

lsblk -f
NAME   FSTYPE     LABEL        UUID                                 FSAVAIL FSUSE% MOUNTPOINT

...
zd0                                                                  174.2M     1% /home/vagrant/vol1

Cifrado

Esta funcionalidad nos sería bastante util en casos como por ejemplo, almacenamiento de datos crítios en copias de seguridad. Para activarlo sobre una unidad de nuestro sistema de ficheros debemos ejecutar un comando con la siguiente sintaxis:

zpool create -O encryption=off|on|distintos-algoritmos -O keyformat=passphrase \
-O keylocation=prompt NombreDelPool /dev/DispositivoDeBloque

En mi caso voy a utilizar el volumen que creamos antes

zpool create -O encryption=on -O keyformat=passphrase -O keylocation=prompt \
PoolCifrado /dev/zvol/EjemploRaidZ/vol1

Y comprobamos que se ha creado correctamente con el cifrado correspondiente

zfs get encryption PoolCifrado
NAME         PROPERTY    VALUE        SOURCE
PoolCifrado  encryption  aes-256-ccm  -

Snapshots

El sistema de snapshots utilizado por ZFS permite realizar instantáneas del sistema que, se almacenan en el mismo dispositivo de bloques donde se está utilizando dicho sistema de ficheros. Esto nos impediría utilizarlas en caso de desastre, pero siempre podemos recurrir a la replicación y copiarlas en otro dispositivo separado de ZFS. Aunque al principio estas snapshots no ocupan espacio, comenzarán a crecer en función de los cambios que experimente el sistema desde que se creó la instantánea. Para crear o eliminar un snapshot la sintaxis es la siguiente.

zfs snapshot zpool/vol@nombre-de-snapshot
zfs destroy zpool/vol@nombre-de-snapshot

Y en caso de que quisiésemos volver a una snapshot que ya hicimos con anterioridad ejecutaríamos un comando como este:

# utilizaríamos el parámetro -r en caso de que
# quisiésemos eliminar automáticamente la snapshot
# tras hacer el "rollback"
zfs rollback -r zpool/vol@nombre-de-snapshot

Otras funcionalidades

Tal y como hemos visto a lo largo de esta entrada, ZFS es verdaderamente un sistema de ficheros avanzado, pero no se queda ahí. Además de todo lo que ya hemos visto (COW, RAID, Volumenes, etc), dispone de otras aplicaciones.

Discos caché y log en RAID

Para un aumento del rendimient ZFS nos ofrece la posibilidad de añadir discos caché. Esto tendría sentido en el supuesto de tener el almacenamiento principal del RAID en discos mecánicos y añadir un disco de estado sólido como un M.2 NVME PCIE. También nos permite añadir especificar un disco log. Inicialmente pensaríamos que es un disco para guardar los ficheros de log del sistema, como los de las unidades systemd, aplicaciones como apache2, nginx, etc. Sin embargo, es una unidad también de tipo caché, que se encarga de hacer más liviano el copy-on-write, sobre todo cuando se necesitan estructuras síncronas. A este tipo de dispositivo se le conoce como ZFS Intent Log o ZIL. Si quisiésemos definir este tipo de unidades en el RAID, tan solo tenemos que añadir a la derecha de la unidad en cuestión el parámetro cache en caso de disco caché convencional o log en caso de un disco orientado al ZIL que hemos explicado antes.

Conclusión

ZFS es verdaderamente un sistema de ficheros avanzado. Aporta una serie de funcionalidades que lo hacen sumamente útil en el caso de que necesitemos un sistema de ficheros robusto. Más allá de la redundancia que aporta según el tipo de RAID que utilicemos, hemos podido comprobar otras funciones adicionales respecto a los sistemas tradicionales como el copy-on-write o la posibilidad de generar dispositivos de bloques completamente funcionales, además de la creación de snapshots. No obstante, debido a la complejidad de su instalación y la incompatibilidad de la licencia CDDL, incompatible con la licencia de Linux GPL, hace que su uso no esté tan extendido. Dicho esto, aunque creo que el uso ZFS merece la pena debido a todo el potencial que tiene, su licencia lo condenó a caer cada vez más en el desuso, y todavía más con el desarrollo contínuo de BTRFS, el cual sí tiene una licencia compatible con el Kernel Linux