ZFS en la raíz: instalar para que las reversiones funcionen de verdad

¿Te fue útil?

No quieres “snapshots”. Quieres la confianza de revertir una actualización rota a las 02:00 y que el sistema arranque limpio —red, SSH, servicios, todo— sin que tengas que hurgar en los registros de initramfs como un espeleólogo con linterna frontal.

La mayoría de las guías de ZFS-on-root te llevan a “arranca una vez”. Producción exige más: diseño predecible de datasets, integración del cargador de arranque que respete los entornos de arranque, y hábitos operativos que no conviertan tu historial de snapshots en la escena de un crimen.

Qué debe significar “reversión” en un sistema real

Una reversión que “funciona” no es solo un zfs rollback que se completa sin errores. Una reversión que funciona significa:

  • El cargador de arranque puede enumerar y arrancar un estado anterior de root (entorno de arranque), no solo montar algún dataset si editas los parámetros del kernel a mano.
  • El initramfs puede importar tu pool (y desbloquearlo si está cifrado) de forma consistente y automática.
  • /boot es coherente con el kernel+initramfs dentro del entorno root que vas a arrancar —o está deliberadamente separado y gestionado como una excepción de primera clase.
  • El estado y la configuración de los servicios están acotados para que revertir root no resucite un estado en disco incompatible para aplicaciones que escriben constantemente (bases de datos, colas, almacenes de métricas).
  • El flujo de trabajo del operador es obvio: crear BE, actualizar, reiniciar en el BE, verificar, conservar o revertir.

En cristiano: no estás instalando “ZFS”, estás instalando una máquina del tiempo con un cargador de arranque como panel de control. Y el panel debe seguir funcionando después de viajar en el tiempo.

Toma de posición: si no puedes listar tus entornos de arranque desde el menú de arranque o desde una CLI predecible y arrancar en ellos sin editar nada, tus reversiones son teatro. El teatro es divertido, pero no a las 02:00.

Broma corta #1: Un plan de reversión que requiere “algunos pasos manuales” es solo un plan hacia adelante con la negación ya incluida.

Hechos y contexto interesantes (porque la historia explica los bordes afilados de hoy)

  1. ZFS nació en Sun a mediados de los 2000 con checksum de extremo a extremo y copy-on-write integrados; fue diseñado para hacer la corrupción silenciosa menos emocionante.
  2. “Almacenamiento en pool” fue un cambio de mentalidad: los sistemas de archivos dejaron de estar atados a un único dispositivo de bloques; gestionas un pool y esculpes datasets y volúmenes desde él.
  3. Los entornos de arranque se volvieron una norma cultural en Solaris/Illumos, donde actualizar a un nuevo BE y mantener el viejo arrancable era el flujo esperado, no un truco exótico.
  4. ZFS en Linux está fuera del árbol por incompatibilidades de licencias; por eso la integración de las distribuciones varía y “funcionó en mi portátil” no es un plan.
  5. GRUB ganó soporte para ZFS hace años, pero el soporte depende de flags de característica; habilitar cada característica brillante del pool puede dejar a tu cargador de arranque atascado en el pasado.
  6. Los flags de OpenZFS reemplazaron los números de versión en disco; esto facilitó la evolución incremental pero creó una nueva regla operativa: los componentes de arranque deben soportar los flags que habilites.
  7. El comportamiento de ACL y xattr difiere según las expectativas del SO; ZFS puede emular, pero configuraciones desajustadas pueden crear sorpresas sutiles de permisos tras una reversión.
  8. La compresión dejó de ser controversial cuando las CPUs modernas se volvieron rápidas; hoy lz4 suele ser una ganancia gratis, pero aún debe aplicarse con intención.
  9. Las snapshots son baratas hasta que dejan de serlo: el metadato y la fragmentación pueden aparecer sigilosamente, especialmente cuando las snapshots se conservan para siempre y datasets con alta rotación viven en el mismo árbol root.

Las reversiones fallan cuando tratas ZFS-on-root como “una elección de sistema de archivos” en lugar de “una elección de arquitectura de arranque”. El sistema de archivos es la parte fácil. El arranque es donde tu confianza va a morir.

Objetivos de diseño: las reglas que mantienen las reversiones aburridas

Regla 1: Separa lo que cambia junto

Los bits del sistema operativo (paquetes, librerías, configuración) deberían estar en el entorno de arranque. Los datos de aplicaciones de alta rotación no deberían. Si reviertes root y tus archivos de base de datos también revierten, has construido una máquina del tiempo que viola la causalidad con desparpajo.

Haz: mantén /var/lib dividido en datasets por aplicación, y pon bases de datos en datasets dedicados (o incluso en pools separados) con sus propias políticas de snapshots.

No hagas: meter todo bajo un monolítico rpool/ROOT y llamarlo “simple”. Es simple como lo es un radio de explosión enorme.

Regla 2: El arranque ve lo que necesita, y no más

El initramfs y el cargador de arranque necesitan una forma estable de encontrar y montar la raíz correcta. Eso típicamente significa: un bootfs predecible, nombres claros de BE y evitar características que el cargador no pueda leer.

Regla 3: Tu límite de reversión es un entorno de arranque, no una snapshot

Las snapshots son materia prima. Un entorno de arranque es una snapshot+clone curada (o clone de dataset) con un / coherente y normalmente una historia coherente de /boot. Usa la herramienta adecuada. Deja de intentar arrancar snapshots al azar como si fuera un truco de fiesta.

Regla 4: Las propiedades por defecto deben ser por defecto por una razón

Define propiedades en el nivel de dataset correcto. Evita poner todo en la raíz del pool a menos que quieras depurar la herencia el resto de tu vida.

Diseño de datasets que hace que los entornos de arranque se comporten

El diseño de datasets es donde la mayoría de las instalaciones “ZFS en root” fallan silenciosamente. Arrancan, sí. Pero después no puedes revertir limpiamente porque la mitad del estado está compartido, o porque los puntos de montaje se pelean, o porque /boot está en una isla que no coincide con tu BE.

Un diseño pragmático (pool único, GRUB, Linux típico)

Asume un pool llamado rpool. Creamos:

  • rpool/ROOT (contenedor, mountpoint=none)
  • rpool/ROOT/default (BE root real, mountpoint=/)
  • rpool/ROOT/<be-name> (entornos de arranque adicionales)
  • rpool/BOOT o una partición /boot no ZFS separada, dependiendo de tu distro y gusto
  • rpool/home (mountpoint=/home)
  • rpool/var (mountpoint=/var, normalmente)
  • rpool/var/log (rotación de logs; política de snapshots diferente)
  • rpool/var/lib (contenedor, a menudo)
  • rpool/var/lib/docker o .../containers (si debes, pero considera un pool separado)
  • rpool/var/lib/postgresql, rpool/var/lib/mysql etc. (separados a propósito)
  • rpool/tmp (mountpoint=/tmp, con sync=disabled solo si te gusta vivir peligrosamente; de lo contrario déjalo)

¿Y /boot?

Dos patrones viables:

  • /boot en EFI+ext4 (o vfat para ESP, ext4 para /boot): aburrido, compatible, fácil. Las reversiones necesitan gestionar versiones de kernel/initramfs con cuidado, porque /boot se comparte entre BEs. Esto sigue siendo válido si usas una herramienta de BE que lo maneje.
  • /boot en ZFS: posible con GRUB leyendo ZFS, pero debes mantener las características del pool compatibles con GRUB y ser muy deliberado con las actualizaciones. Puede ser limpio si la distro lo soporta; de lo contrario es una tentación atractiva.

Mi sesgo: si haces esto en máquinas de flota, deja /boot en una partición ext4 pequeña y mantén el ESP separado. Quita una pieza móvil del arranque temprano. Aún puedes tener semánticas de reversión fuertes con una gestión adecuada de BE; solo trata los artefactos del kernel con cuidado.

Plan de instalación: una build ZFS-on-root limpia y amigable para reversiones

Esto es un plan genérico que encaja bien con sistemas estilo Debian/Ubuntu, y conceptualmente con otros. Ajusta el gestor de paquetes y el tooling de initramfs según corresponda. El punto no son los comandos exactos; es la arquitectura y los pasos de verificación.

Particionado: elige previsibilidad

Usa GPT. Crea un ESP. Haz una partición dedicada para /boot a menos que sepas que necesitas /boot en ZFS y hayas probado la compatibilidad de GRUB con las características del pool. Mete el resto en ZFS.

Crea el pool con el arranque en mente

Elige ashift correctamente (casi siempre 12). Escoge valores sensatos: compression=lz4, atime=off, xattr=sa, y un acltype consistente para Linux (posixacl).

Cifrado: si cifras root, decide cómo se desbloquea (frase de paso en consola, archivo clave en initramfs, o desbloqueo por red). No lo “resuelvas después.” Después es cuando tu sistema no arrancará.

Construye datasets y móntalos correctamente

Define mountpoint=none en los contenedores. Pon el dataset root real bajo rpool/ROOT/<be-name> con mountpoint=/. Luego monta una raíz temporal en /mnt e instala el SO ahí.

Instala el cargador de arranque + initramfs con soporte ZFS

Necesitas los módulos de ZFS en initramfs y la lógica de importación del pool correcta. Eso incluye:

  • La línea de comando del kernel correcta root=ZFS=rpool/ROOT/<be-name> (o el equivalente de tu distro).
  • Cache del pool si tu initramfs lo usa (/etc/zfs/zpool.cache), además del hostid correcto.
  • GRUB configurado para encontrar ZFS y listar BEs (varía por distro/tooling).

Tooling de entornos de arranque: escoge uno y úsalo

Si quieres reversiones que “realmente funcionen”, usa una herramienta de BE que se integre con el flujo de actualización del cargador de arranque de tu distro. En Linux, eso puede significar herramientas como zsys (era Ubuntu), zfsbootmenu (un enfoque distinto), o hooks específicos de la distro. No prescribo uno, pero sí prescribo que dejes de crear BEs a mano sin una historia repetible de integración con el arranque.

Cita sobre fiabilidad, porque sigue siendo cierta: “La esperanza no es una estrategia.” — General Gordon R. Sullivan

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son las comprobaciones que ejecutas cuando instalas, validas o depuras. Cada tarea incluye: comando, qué significa la salida y la decisión que tomas a continuación.

Tarea 1: Confirma las suposiciones de tamaño de sector (para que ashift no te sorprenda)

cr0x@server:~$ lsblk -o NAME,MODEL,SIZE,PHY-SEC,LOG-SEC,ROTA
NAME MODEL             SIZE PHY-SEC LOG-SEC ROTA
nvme0n1 Samsung SSD    1.8T    4096    512    0
sda    ST4000NM0035    3.6T    4096   4096    1

Significado: PHY-SEC es el tamaño físico de sector. La mayoría de discos modernos son 4K físicos. Si eliges ashift=12 (2^12=4096), te alineas bien.

Decisión: Usa ashift=12 salvo que tengas un dispositivo inusual que realmente necesite 8K (raro) o estés en hardware legado 512B-only (también raro hoy).

Tarea 2: Crea el pool con valores sensatos (y te muestro lo que pasó)

cr0x@server:~$ sudo zpool create -f -o ashift=12 \
  -O compression=lz4 -O atime=off -O xattr=sa -O acltype=posixacl \
  -O mountpoint=none -R /mnt rpool /dev/nvme0n1p3
cr0x@server:~$ zpool status -v rpool
  pool: rpool
 state: ONLINE
config:

        NAME            STATE     READ WRITE CKSUM
        rpool           ONLINE       0     0     0
          nvme0n1p3     ONLINE       0     0     0

errors: No known data errors

Significado: El pool existe, ONLINE, sin errores. El -R /mnt altroot significa que los mounts van bajo /mnt para la instalación.

Decisión: Si ves algo distinto de ONLINE, para. Arregla cables, ruta de dispositivo, particionado o salud del disco antes de instalar un SO encima de una base inestable.

Tarea 3: Verifica que los flags de características del pool no sorprendan a tu cargador

cr0x@server:~$ zpool get -H feature@* rpool | head
rpool    feature@async_destroy              enabled     local
rpool    feature@bookmarks                  enabled     local
rpool    feature@embedded_data              enabled     local
rpool    feature@extensible_dataset         enabled     local
rpool    feature@filesystem_limits          enabled     local
rpool    feature@hole_birth                 enabled     local
rpool    feature@large_blocks               enabled     local
rpool    feature@lz4_compress               enabled     local
rpool    feature@spacemap_histogram         enabled     local
rpool    feature@enabled_txg                active      local

Significado: Los flags de características están enabled/active. Los cargadores (especialmente builds de GRUB) pueden no soportar todas las características en cada versión de distro.

Decisión: Si dependes de que GRUB lea ZFS para /boot, valida la compatibilidad de características. Si no estás 100% seguro, mantén /boot fuera de ZFS y deja que GRUB lea ext4.

Tarea 4: Crea datasets contenedor con disciplina de mountpoint

cr0x@server:~$ sudo zfs create -o mountpoint=none rpool/ROOT
cr0x@server:~$ sudo zfs create -o mountpoint=/ rpool/ROOT/default
cr0x@server:~$ sudo zfs create -o mountpoint=/home rpool/home
cr0x@server:~$ sudo zfs create -o mountpoint=/var rpool/var
cr0x@server:~$ sudo zfs create -o mountpoint=/var/log rpool/var/log
cr0x@server:~$ zfs list -o name,mountpoint -r rpool | head -n 10
NAME               MOUNTPOINT
rpool              none
rpool/ROOT         none
rpool/ROOT/default /mnt/
rpool/home         /mnt/home
rpool/var          /mnt/var
rpool/var/log      /mnt/var/log

Significado: Con altroot /mnt, los mountpoints aparecen bajo /mnt. Los datasets contenedor no están montados.

Decisión: Si ves datasets contenedor montados, arréglalo ahora. Los datasets contenedor suelen ser mountpoint=none para evitar conflictos de montaje y acoplamientos extraños en reversiones.

Tarea 5: Comprueba qué se montará al arranque (y si ZFS está de acuerdo)

cr0x@server:~$ zfs get -r canmount,mountpoint rpool/ROOT/default | head
NAME               PROPERTY   VALUE     SOURCE
rpool/ROOT/default canmount   on        default
rpool/ROOT/default mountpoint /         local

Significado: El dataset root se montará en /.

Decisión: Para datasets no root que quieras por-BE (raro pero a veces necesario), pon canmount=noauto y manéjalo explícitamente. No improvises.

Tarea 6: Confirma que la propiedad bootfs apunta al BE previsto

cr0x@server:~$ sudo zpool set bootfs=rpool/ROOT/default rpool
cr0x@server:~$ zpool get bootfs rpool
NAME   PROPERTY  VALUE              SOURCE
rpool  bootfs    rpool/ROOT/default local

Significado: El sistema de archivos de arranque por defecto del pool está configurado. Algunos flujos de arranque usan esto como pista.

Decisión: Cuando crees un nuevo BE para actualizar, fija bootfs a ese BE antes de reiniciar (o deja que tu herramienta de BE lo haga). Si lo olvidas, arrancarás el entorno viejo y te preguntarás por qué nada cambió.

Tarea 7: Valida hostid y zpool.cache (para que initramfs importe de forma fiable)

cr0x@server:~$ hostid
9a3f1c2b
cr0x@server:~$ sudo zgenhostid -f 9a3f1c2b
cr0x@server:~$ sudo zpool set cachefile=/etc/zfs/zpool.cache rpool
cr0x@server:~$ sudo ls -l /etc/zfs/zpool.cache
-rw-r--r-- 1 root root 2264 Jan 12 09:41 /etc/zfs/zpool.cache

Significado: Un hostid estable reduce el drama de “el pool fue accedido por otro sistema”. El cachefile ayuda al arranque temprano a encontrar dispositivos de forma fiable.

Decisión: En VMs clonadas, regenera hostid por máquina. Hostids duplicados son un clásico “funciona en dev, explota en prod”.

Tarea 8: Comprueba que initramfs incluye ZFS y importará pools

cr0x@server:~$ lsinitramfs /boot/initrd.img-$(uname -r) | grep -E 'zfs|zpool' | head
usr/sbin/zpool
usr/sbin/zfs
usr/lib/modules/6.8.0-31-generic/kernel/zfs/zfs.ko
usr/lib/modules/6.8.0-31-generic/kernel/zfs/zcommon.ko
scripts/zfs

Significado: Herramientas y módulos están en initramfs. Si no están, el arranque temprano no puede importar el pool y terminarás en un shell de initramfs con mirada perdida.

Decisión: Si faltan, instala el paquete correcto de integración ZFS-initramfs para tu distro y reconstruye initramfs.

Tarea 9: Confirma que la línea de comando del kernel apunta al dataset root correcto

cr0x@server:~$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.8.0-31-generic root=ZFS=rpool/ROOT/default ro quiet

Significado: El kernel está instruido para montar rpool/ROOT/default como root.

Decisión: Si esto apunta a una snapshot o a un dataset equivocado, arregla la configuración de GRUB o tu tooling de BE. No intentes “simplemente montarlo” post-arranque; el límite de arranque está mal.

Tarea 10: Crea un nuevo entorno de arranque (clone) y verifica que sea independiente

cr0x@server:~$ sudo zfs snapshot rpool/ROOT/default@pre-upgrade
cr0x@server:~$ sudo zfs clone rpool/ROOT/default@pre-upgrade rpool/ROOT/upgrade-test
cr0x@server:~$ zfs list -o name,origin,mountpoint -r rpool/ROOT | grep -E 'default|upgrade-test'
rpool/ROOT/default        -                           /
rpool/ROOT/upgrade-test   rpool/ROOT/default@pre-upgrade  /

Significado: upgrade-test es un clone de la snapshot y puede servir como BE. Nota: ambos muestran mountpoint /; solo uno debería estar montado a la vez.

Decisión: Fija canmount=noauto en BEs inactivos para que no se monten accidentalmente si se importan en contextos de mantenimiento.

Tarea 11: Haz que solo un BE sea montable por defecto

cr0x@server:~$ sudo zfs set canmount=noauto rpool/ROOT/default
cr0x@server:~$ sudo zfs set canmount=noauto rpool/ROOT/upgrade-test
cr0x@server:~$ sudo zfs set canmount=on rpool/ROOT/upgrade-test
cr0x@server:~$ zfs get canmount rpool/ROOT/default rpool/ROOT/upgrade-test
NAME                   PROPERTY  VALUE   SOURCE
rpool/ROOT/default      canmount  noauto  local
rpool/ROOT/upgrade-test canmount  on      local

Significado: Estás controlando qué se auto-monta. Esto reduce incidentes de “dos roots montados en un lugar extraño”.

Decisión: Para tu flota, estandariza una convención de propiedades de BE. Tu yo futuro te lo agradecerá en silencio, que es la mejor clase de gratitud.

Tarea 12: Cambia bootfs al nuevo BE y actualiza las configuraciones del cargador

cr0x@server:~$ sudo zpool set bootfs=rpool/ROOT/upgrade-test rpool
cr0x@server:~$ zpool get bootfs rpool
NAME   PROPERTY  VALUE                   SOURCE
rpool  bootfs    rpool/ROOT/upgrade-test local
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.0-31-generic
cr0x@server:~$ sudo update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.8.0-31-generic
Found initrd image: /boot/initrd.img-6.8.0-31-generic
done

Significado: Estás preparando artefactos de arranque temprano y el menú de arranque. Algunas distros necesitan hooks adicionales para la enumeración de BEs; esto muestra la forma general.

Decisión: Reinicia y valida que puedas arrancar tanto el BE nuevo como el BE viejo. Si no puedes, deja de llamarlos “entornos de arranque”. Son “datasets que esperas poder montar después”.

Tarea 13: Confirma en qué BE arrancaste realmente después del reinicio

cr0x@server:~$ findmnt -n -o SOURCE /
rpool/ROOT/upgrade-test
cr0x@server:~$ zfs list -o name,mounted,canmount rpool/ROOT
NAME                   MOUNTED  CANMOUNT
rpool/ROOT             no       on
rpool/ROOT/default      no       noauto
rpool/ROOT/upgrade-test yes      on

Significado: Root es el BE nuevo. El BE viejo no está montado.

Decisión: Si root sigue siendo default, no cambiaste realmente; arregla bootfs y/o la configuración del cargador, y vuelve a intentarlo.

Tarea 14: Audita snapshots e impacto en espacio (para que “reversión” no se convierta en “sin espacio”)

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint rpool | head -n 8
NAME                  USED  AVAIL  REFER  MOUNTPOINT
rpool                38.2G  1.67T   192K  none
rpool/ROOT           14.1G  1.67T   192K  none
rpool/ROOT/default   7.20G  1.67T  7.20G  /
rpool/ROOT/upgrade-test 7.14G 1.67T 7.14G  /
rpool/var            11.5G  1.67T  7.31G  /var
rpool/var/log        1.84G  1.67T  1.84G  /var/log
cr0x@server:~$ zfs list -t snapshot -o name,used,refer -r rpool/ROOT | tail -n 5
rpool/ROOT/default@pre-upgrade  132M  7.20G

Significado: used de la snapshot muestra el espacio retenido por cambios desde la snapshot. Alto used en snapshots suele significar rotación en datasets que no aislaste.

Decisión: Si las snapshots se inflan porque logs o capas de contenedores están dentro del BE, sepáralos ahora. La presión de espacio es el asesino número 1 de reversiones porque ZFS necesita espacio para respirar.

Tarea 15: Revisa la salud del pool y los contadores de error antes de culpar a “cosas raras de ZFS”

cr0x@server:~$ zpool status -xv
all pools are healthy

Significado: No hay errores de datos conocidos, ni vdevs degradados. Bien.

Decisión: Si ves errores de checksum, trátalo como un incidente de hardware/transporte hasta que se demuestre lo contrario. “Mi reversión no funcionó” a veces se traduce a “mi SSD está mintiendo”.

Tarea 16: Identifica un retraso en la importación al arrancar (el saboteador silencioso de reversiones)

cr0x@server:~$ systemd-analyze blame | head -n 10
14.203s zfs-import-cache.service
 6.412s dev-nvme0n1p3.device
 3.901s systemd-udev-settle.service
 2.311s network-online.target
 1.988s zfs-mount.service

Significado: El tiempo de arranque está dominado por la importación del pool esperando dispositivos o por un desajuste de cache.

Decisión: Si zfs-import-cache es lento, arregla la estabilidad de nombres de dispositivo, reconstruye zpool.cache o cambia a import-by-id. Si udev-settle es lento, audita qué lo está provocando.

Tres micro-historias corporativas desde la tierra de las consecuencias

1) Incidente causado por una suposición errónea: “Snapshots equivalen a entornos de arranque”

Una empresa SaaS mediana desplegó ZFS-on-root en un conjunto de nodos API. El ingeniero que lo impulsó era competente, pero con poco tiempo. Crearon un único dataset para /, habilitaron snapshots y declararon victoria. El resto de la organización oyó “ahora podemos revertir actualizaciones”. Esa frase tiene consecuencias.

Durante un ciclo de parches de seguridad rutinario, un nodo arrancó con la red rota tras el reinicio. El on-call hizo lo que le habían dicho: “revierte”. Ejecutaron una reversión en el dataset root a la última snapshot, reiniciaron, y… obtuvieron la misma red rota. Luego probaron snapshots más antiguas. Mismo resultado. Ahora están enojados con ZFS, lo cual es injusto pero común.

El problema raíz fue que su /boot vivía en una partición ext4 compartida entre todos los “estados”, y el initramfs se había regenerado con un desajuste de módulos. La reversión restauró el contenido del root, pero no revirtió el par kernel+initramfs en /boot. Estaban, de hecho, arrancando un initramfs nuevo en un root viejo. Linux es muchas cosas, pero “perdonador con incompatibilidades ABI en el arranque” no es una de ellas.

La solución no fue mágica. Introdujeron entornos de arranque reales: clones de rpool/ROOT/<be> y un flujo donde las actualizaciones ocurren en un BE nuevo, y /boot se actualiza de forma coordinada. También cambiaron el lenguaje interno: dejaron de llamar snapshots “reversiones” y empezaron a decir “cambio de entorno de arranque”. La precisión redujo el número de sorpresas nocturnas.

2) Optimización que salió mal: “Activemos dedup en root, es mayormente idéntico”

Una firma financiera adoraba la idea de entornos de arranque pero odiaba el espacio. Alguien notó que múltiples BEs se parecen: mismas librerías, mismos binarios, solo unas actualizaciones. La deduplicación pareció un truco limpio: almacenar bloques idénticos una vez, ahorrar espacio y obtener BEs “infinitos”.

Habilitaron dedup en el árbol root y funcionaron contentos un tiempo. Luego llegó una ola de actualizaciones: kernels, runtimes, tooling de contenedores. La tabla de dedup creció, la presión de memoria aumentó y las máquinas empezaron a thrashear. No catastrófico al principio—solo un poco de latencia, un poco de rareza. Luego ocurrió un reinicio y el tiempo de arranque se disparó. La importación fue lenta, servicios agotaron tiempo y un nodo cayó del balanceador. Se repitió en el clúster, uno a uno, durante el mantenimiento.

Dedup no estaba “roto”. Hizo exactamente lo que hace: intercambia memoria y trabajo de metadatos por espacio. En filesystems root con rotación y muchas snapshots/BEs, ese intercambio puede ser brutal. Lo peor es que el modo de fallo no es un error limpio; es muerte por mil operaciones lentas.

La recuperación fue aburrida y cara: migraron desde los datasets con dedup a otros sin dedup, reiniciaron en BEs limpios y aceptaron que clones + compresión eran suficientes. Dejaron de optimizar para el dashboard del equipo de almacenamiento y empezaron a optimizar para la fiabilidad del reinicio. Esa es la dirección correcta de la vergüenza.

3) Práctica aburrida pero correcta que salvó el día: “Siempre mantener un BE conocido bueno y probar su capacidad de arranque”

Una empresa de análisis sanitario tenía una práctica que sonaba tediosa: cada mes creaban un entorno de arranque “conocido bueno”, lo etiquetaban con el ticket de cambio y lo mantenían al menos un ciclo de release completo. También tenían un requisito: tras cualquier actualización, el operador debía demostrar que aún podía arrancar el BE anterior seleccionándolo en el menú de arranque y alcanzar el modo multiusuario. Luego reiniciaban de vuelta al BE nuevo.

Les costaba quizás diez minutos por ventana de actualización. La gente se quejaba. La gente siempre se queja de rituales que previenen problemas que no ha sufrido personalmente.

Un trimestre, una actualización de driver de un proveedor rompió el orden de enumeración de discos en un lote de servidores. Nada dramático, solo lo suficiente para que la lógica de importación del initramfs se ralentizara y a veces eligiera la ruta equivocada primero. Algunas máquinas fallaron al arrancar en el entorno actualizado. Pero como el equipo tenía un BE conocido bueno probado y un procedimiento establecido, el on-call seleccionó el BE anterior, el sistema arrancó y tuvieron tiempo para arreglar el BE nuevo sin un outage completo.

Esto es lo que “correcto” parece en producción: pasos sin glamour que intercambian cinco minutos ahora por cinco horas que no gastas después. Además, acorta los postmortems, lo cual es una forma de amabilidad.

Guía de diagnóstico rápido: encuentra el cuello de botella pronto

Cuando una reversión/entorno de arranque ZFS-on-root falla, puedes perder horas en la capa equivocada. No lo hagas. Comprueba en este orden.

Primero: ¿Estás arrancando el BE que crees?

  • Desde un sistema en ejecución: findmnt -n -o SOURCE /
  • Confirma la cmdline: cat /proc/cmdline
  • Confirma zpool get bootfs rpool

Interpretación: Si tu cambio de BE no se aplicó, todo lo demás es ruido. Arregla la selección de arranque antes de depurar userland.

Segundo: ¿El arranque temprano puede importar el pool rápida y consistentemente?

  • systemd-analyze blame | head para retrasos en zfs-import-*
  • journalctl -b -u zfs-import-cache.service para timeouts y dispositivos faltantes
  • Comprueba que /etc/zfs/zpool.cache exista y coincida con la realidad
  • Comprueba consistencia de hostid: hostid

Interpretación: Los problemas de importación se disfrazan de “fallos de arranque aleatorios”. Normalmente son deterministas una vez miras el descubrimiento de dispositivos y la cache.

Tercero: ¿/boot es coherente con el BE root?

  • Lista kernels e initramfs: ls -lh /boot
  • Verifica que initramfs contenga módulos ZFS: lsinitramfs ... | grep zfs
  • Revisa la generación del menú de GRUB y las entradas

Interpretación: Si compartes /boot entre BEs, necesitas una política. Sin política tienes “lo que hizo la última actualización”, que no es estrategia de ingeniería.

Cuarto: Si arranca pero la reversión no “restaura la cordura”, revisa los límites de dataset

  • ¿Los logs están en el BE? zfs list -o name,mountpoint | grep /var/log
  • ¿El estado de la app se comparte inesperadamente entre BEs? Revisa datasets en /var/lib
  • Presión de espacio por snapshots: zfs list -t snapshot, zpool list

Interpretación: La mayoría de la decepción en reversiones es autoinfligida vía mala separación de datasets.

Errores comunes: síntomas → causa raíz → solución

1) Síntoma: “La reversión tuvo éxito, pero el sistema sigue roto tras el reinicio”

Causa raíz: /boot está compartido y no se revirtió; incompatibilidad kernel/initramfs con el root revertido. O revertiste un dataset pero no cambiaste el BE que usa el cargador.

Solución: Usa BEs (basados en clones) y coordina actualizaciones de kernel/initramfs. Confirma que root=ZFS=... apunte al BE que pretendes. Considera mantener artefactos del kernel por-BE si tu tooling lo soporta, o implementa reglas estrictas de retención de kernels.

2) Síntoma: Arranque cae a shell de initramfs: “cannot import ‘rpool’”

Causa raíz: Módulos ZFS faltantes en initramfs, zpool.cache incorrecto, rutas de dispositivo inestables, o hostid desajustado (común tras clonar).

Solución: Reconstruye initramfs con ZFS incluido; regenera /etc/zfs/zpool.cache; asegura hostid único; prefiere identificadores de dispositivo estables. Valida inspeccionando el contenido del initramfs.

3) Síntoma: GRUB no puede ver el pool o no puede leer /boot en ZFS

Causa raíz: Características del pool habilitadas que la build de GRUB no puede leer, o módulo ZFS de GRUB faltante/antiguo.

Solución: Mantén /boot fuera de ZFS, o congela las características del pool a lo que GRUB soporte, o actualiza la pila del cargador de arranque controladamente. No “simplemente zpool upgrade” en pools críticos de arranque sin pensar.

4) Síntoma: La lista de entornos de arranque existe, pero seleccionar uno antiguo provoca panic o cuelga

Causa raíz: El BE antiguo tiene un initramfs que no puede importar el pool con el nombrado actual, configuración de cifrado o conjunto de módulos; o el userland espera un kernel más nuevo.

Solución: Prueba periódicamente el arranque de BEs antiguos. Mantén al menos un BE conocido bueno actualizado lo suficiente para arrancar en el hardware/firmware actual. Si guardas BEs antiguos, trátalos como archivos, no como objetivos de arranque.

5) Síntoma: “zfs rollback” falla con dataset ocupado, o la reversión rompe montajes

Causa raíz: Intentar revertir el dataset root en vivo, o conflictos de mountpoint por contenedores montados, o canmount mal gestionado.

Solución: Revierte cambiando BEs, no haciendo rollback del root montado. Asegura que los datasets contenedor tengan mountpoint=none. Pon BEs inactivos en canmount=noauto.

6) Síntoma: El espacio sigue disminuyendo, snapshots “no se eliminan”

Causa raíz: Las snapshots retienen bloques; los clones dependen de snapshots; eliminar snapshots que son orígenes de clones no liberará espacio como esperas.

Solución: Entiende dependencias clone/snapshot. Promociona clones cuando sea necesario. Separa datasets con rotación. Implementa políticas de retención por dataset.

7) Síntoma: El arranque se vuelve más lento con el tiempo

Causa raíz: Retrasos de importación por descubrimiento de dispositivos, metadatos de snapshots hinchados, o una “optimización” como dedup que incrementó el trabajo de metadatos.

Solución: Mide con systemd-analyze. Arregla el método de importación y la cache. Reduce la rotación de snapshots en root. Evita dedup en root a menos que tengas una razón muy buena y mucha RAM.

Broma corta #2: Dedup en root es como una moto deportiva en invierno: impresionante hasta que la física te presenta personalmente.

Listas de verificación / plan paso a paso

Lista A: Antes de instalar

  • Decide: /boot en ext4 (recomendado para flota) o /boot en ZFS (solo con compatibilidad GRUB verificada).
  • Elige nombre de pool (rpool es común) y esquema de nombres de BE (default, 2025q1-upgrade, etc.).
  • Confirma tamaños de sector (lsblk) y fija ashift=12.
  • Decide método de cifrado y la historia operacional de desbloqueo.
  • Escribe los límites de dataset: al mínimo separa /var/log y rutas de base de datos.

Lista B: Crear pool + datasets (amistoso para reversiones)

  • Crea el pool con mountpoint=none, compression=lz4, atime=off, xattr=sa, acltype=posixacl.
  • Crea rpool/ROOT contenedor y rpool/ROOT/default montado en /.
  • Crea datasets separados para /home, /var, /var/log y datos de aplicaciones.
  • Fija bootfs al BE que pretendes arrancar.
  • Configura cachefile y genera un hostid estable.

Lista C: Instalar SO + hacerlo arrancable

  • Instala userspace de ZFS y módulos del kernel.
  • Asegura que initramfs incluya scripts y módulos de importación de ZFS.
  • Instala cargador y asegura que la línea de kernel incluya root=ZFS=rpool/ROOT/<be>.
  • Si /boot es compartido: define retención de kernels y proceso de actualización; no confíes en vibras.

Lista D: Validar comportamiento de reversión (la parte que todos omiten)

  • Crea un BE nuevo (snapshot + clone) antes de tu primera actualización real.
  • Cambia bootfs al BE nuevo y regenera artefactos de arranque.
  • Reinicia en el BE nuevo; confirma con findmnt.
  • Reinicia en el BE viejo desde el menú de arranque; confirma que aún arranca.
  • Solo entonces: realiza la actualización real y repite la danza.

Preguntas frecuentes

1) ¿Las snapshots de ZFS son suficientes para reversiones del SO?

No. Las snapshots son bloques de construcción. Para una reversión del SO necesitas un entorno de arranque que el cargador pueda seleccionar y arrancar limpiamente.

2) ¿Debería poner /boot en ZFS?

Si gestionas una flota y quieres menos sorpresas en arranque temprano, deja /boot en ext4. Ponlo en ZFS solo si has probado la compatibilidad de GRUB con las características del pool y el flujo de actualización.

3) ¿Cuál es la decisión de dataset más importante para reversiones?

Separa el estado con rotación del BE: al mínimo /var/log y bases de datos. Si no, las snapshots y clones se vuelven anclas de espacio y las semánticas de reversión se vuelven raras.

4) ¿Cuántos entornos de arranque debo conservar?

Mantén al menos dos arrancables: el actual y uno conocido bueno. Más está bien si tienes una política de retención y entiendes dependencias snapshot/clone.

5) ¿Por qué mi pool se importa lentamente al arrancar?

Normalmente por inestabilidad en el descubrimiento de dispositivos o un zpool.cache obsoleto. A veces hostid duplicado en sistemas clonados. Mide con systemd-analyze blame e inspecciona logs de zfs-import-*.

6) ¿Puedo ejecutar zfs rollback en rpool/ROOT/default mientras el sistema está en ejecución?

No. Revertir una root montada es cómo creas modos de fallo nuevos y emocionantes. Cambia BEs en su lugar, luego reinicia.

7) ¿El cifrado en ZFS root es seguro operativamente?

Sí, si diseñas el workflow de desbloqueo. Decide desde el principio si desbloqueas por frase en consola, archivo clave en initramfs o mecanismo remoto. “Lo solucionamos después” a menudo se convierte en “no podemos arrancar”.

8) ¿Debo habilitar dedup para ahorrar espacio entre BEs?

Generalmente no para root. La compresión suele ser el defecto correcto. Dedup puede ser válido en casos muy concretos con mucha RAM y medición cuidadosa, pero no es un toggle casual.

9) ¿Qué propiedades son los defaults seguros habituales para datasets root?

Comúnmente: compression=lz4, atime=off, xattr=sa, y acltype=posixacl. Valida las expectativas de las aplicaciones respecto a ACLs y xattrs, especialmente con runtimes de contenedores.

10) ¿Cómo sé en qué BE estoy actualmente?

findmnt -n -o SOURCE / debería mostrar algo como rpool/ROOT/<be>. También revisa cat /proc/cmdline por root=ZFS=....

Conclusión: próximos pasos que puedes hacer esta semana

Si ya tienes ZFS en root y no estás seguro de que las reversiones realmente funcionen, no esperes a la próxima mala actualización para averiguarlo. Haz tres cosas:

  1. Comprueba tu identidad de arranque actual: revisa /proc/cmdline, findmnt / y zpool get bootfs. Asegúrate de que la historia concuerde.
  2. Crea un entorno de arranque real y arráncalo: snapshot, clone, cambia bootfs, regenera artefactos de arranque, reinicia. Luego arranca el viejo también. Si alguno falla, arréglalo ahora mientras estás tranquilo.
  3. Rediseña límites de datasets donde viva la rotación: separa /var/log y datos de apps fuera del BE. Añade políticas de retención. Deja de permitir que los logs tengan tus reversiones como rehenes.

ZFS-on-root vale la pena cuando se instala como planeas usarlo: como un sistema de reversión controlado con un cargador de arranque que entiende tu intención. El objetivo no es ser ingenioso. El objetivo es recuperación aburrida.

← Anterior
HBM en CPUs: Cuando la memoria entra en el paquete
Siguiente →
Docker “Too Many Requests” al descargar imágenes: Soluciona el throttling del registro de una vez

Deja un comentario