No descubres si tu diseño de almacenamiento es bueno durante un despliegue tranquilo un martes. Lo descubres cuando un nodo desaparece a mitad de una escritura,
un StatefulSet se queda bloqueado y tu canal de incidentes se convierte en una recreación en vivo de “¿de quién son exactamente los datos?”
ZFS y Kubernetes pueden encajar muy bien, pero solo si aceptas una verdad directa: el dominio de fallo por defecto del ZFS local es el nodo,
y Kubernetes no se compadece de tu nodo. Si quieres que tus PV sobrevivan fallos de nodo, debes diseñarlo desde el principio: topología,
replicación, fencing y comprobaciones operativas que no dependan de la esperanza.
Qué falla realmente durante un “fallo de nodo”
“Fallo de nodo” es una frase vaga que oculta resultados muy distintos. Tu diseño de PV debe manejar los modos de fallo específicos que verás
en producción, no los educados de los diagramas.
Modo de fallo A: el nodo está muerto, el almacenamiento está intacto
Piensa: kernel panic, la NIC dejó de funcionar, la fuente de alimentación falló o el hipervisor lo reinició. Los discos están bien, el pool está bien, pero Kubernetes
no puede programar en ese nodo—al menos no inmediatamente. Si tu PV es exclusivamente local, tu pod queda atascado hasta que el nodo vuelva. Eso puede
ser aceptable para una caché. No es aceptable para un libro mayor de pagos.
Modo de fallo B: el nodo responde, el almacenamiento está enfermo
El nodo responde, pero el pool está degradado, falta un vdev, errores NVMe aumentan, o la latencia es insoportable. Kubernetes seguirá
felizmente colocando pods allí si tus reglas de topología lo permiten. ZFS seguirá intentando entregar datos, a veces heroicamente, a veces lentamente.
Necesitas señales y automatización que traten la “enfermedad” del almacenamiento como un problema de programación.
Modo de fallo C: el nodo está medio muerto (el más caro)
El nodo está “Ready” lo suficiente como para engañar a la lógica del plano de control pero no puede realizar IO fiable. Aquí obtienes timeouts, montajes colgados,
pods en Terminating atascados y reintentos en cascada.
Kubernetes es excelente reemplazando ganado. Tu PV no es ganado. Si almacenas estado real, necesitas un plan de dónde vive ese estado
y cómo se mueve cuando el host desaparece.
Hechos y contexto que debes conocer
Esto no es trivia. Son las pequeñas realidades que moldean por qué ciertos diseños ZFS+Kubernetes funcionan y otros eventualmente te devoran el fin de semana.
- ZFS nació en un mundo que odiaba la corrupción silenciosa. Sus checksums end-to-end y su auto-reparación fueron construidos para detectar la degradación de bits que RAID por sí solo no puede.
- Copy-on-write es la razón central por la que los snapshots son baratos. ZFS no “congela” un volumen; preserva punteros de bloque mientras las nuevas escrituras van a otro sitio.
- La adopción temprana de ZFS estuvo ligada a Solaris e integración estrecha. Ese legado aún influye en asunciones sobre nombres de dispositivos previsibles y stacks de almacenamiento estables.
- ZVOLs y datasets son bestias diferentes. ZVOLs se comportan como dispositivos de bloque; los datasets son sistemas de archivos. Tienen opciones de ajuste y comportamientos ante fallos distintos.
- ZFS siempre miente un poco sobre el “espacio libre”. Fragmentación, metaslabs y comportamiento de reservas significan que 80% lleno puede sentirse como 95% para la latencia.
- Ashifts importan más de lo que la gente piensa. El tamaño de sector mal alineado puede penalizar el rendimiento de forma permanente; no lo “arreglas después” sin reconstruir.
- Los scrubs no son opcionales en flotas de larga vida. Son cómo ZFS encuentra errores latentes antes de que necesites ese bloque durante una restauración.
- Las abstracciones de almacenamiento de Kubernetes se diseñaron primero para redes. Existen PV locales, pero la historia de orquestación es intencionalmente limitada: no teletransportará tus bytes mágicamente.
- La semántica “single writer” es una restricción de seguridad fundamental. Si dos nodos pueden montar y escribir el mismo sistema de archivos sin fencing, estás diseñando una máquina de corrupción.
Tres arquetipos de PV con ZFS (y cuándo usarlos)
1) PV ZFS local (ligado al nodo): rápido, simple, implacable
Este es el patrón “ZFS en cada nodo, el PV se enlaza al nodo”. Creas un dataset o zvol en el nodo, lo expones vía CSI o LocalPV,
y programas el pod consumidor en ese nodo.
Úsalo cuando: la carga tolera la caída de nodo, puedes reconstruir desde otro sitio, o es una caché de solo lectura mayormente con la verdad en otro lugar.
Evítalo cuando: necesites conmutación por error automática sin tiempo de movimiento de datos, o no puedas tolerar “pod atascado hasta que el nodo regrese”.
2) Almacenamiento en red respaldado por ZFS (iSCSI/NFS sobre ZFS): portátil, centralizado, cambia el dominio de fallo
Coloca ZFS en nodos de almacenamiento dedicados y exporta volúmenes por la red. Kubernetes lo verá como almacenamiento en red, lo que se alinea con
su modelo de programación. Tu dominio de fallo pasa a ser el servicio de almacenamiento, no el nodo de cómputo.
Úsalo cuando: necesites que los pods se muevan libremente y estés dispuesto a diseñar HA de almacenamiento con responsabilidad.
Evítalo cuando: tu red es frágil o sobresuscrita, o no puedes atender la carga operativa de un almacenamiento HA.
3) Almacenamiento local ZFS replicado con promoción (zfs send/receive, estilo DRBD, o un orquestador): resiliente, complejo
Este es el patrón “tener la torta y pagar por ella”: mantener datos locales para rendimiento, pero replicar a pares para poder promover
una réplica cuando un nodo falla. Puede hacerse con mecanismos de replicación ZFS (snapshots + send/receive) coordinados por un controlador,
o por un sistema de almacenamiento encima de ZFS.
Úsalo cuando: quieras rendimiento local con supervivencia ante fallo de nodo, y puedas imponer semántica single-writer con fencing.
Evítalo cuando: no puedas garantizar fencing o necesites replicación síncrona real pero no aceptarás los costes de latencia.
Aquí va la conclusión subjetiva: si ejecutas estado serio (bases de datos, colas, metadatos de objetos) y las fallas de nodo no deben causar
una recuperación de horas, no finjas que los PV exclusivamente locales son “altamente disponibles”. Son “altamente disponibles para ese nodo”.
Principios de diseño que evitan sorpresas desagradables
Principio 1: Decide tu dominio de fallo explícitamente
Para cada StatefulSet, escribe: “Si el nodo X desaparece, ¿aceptamos downtime? ¿Por cuánto tiempo? ¿Podemos reconstruir? ¿Necesitamos conmutación automática?”
Si no puedes responder, tu StorageClass es solo una plegaria en YAML.
Principio 2: Impón single-writer con fencing, no con buenas intenciones
Si replicas y promueves, debes impedir que dos nodos escriban el mismo volumen lógico. Kubernetes no lo hará por ti.
Tu diseño necesita una de las siguientes cosas:
- fencing duro (STONITH, corte de energía, fence del hipervisor), o
- un sistema de almacenamiento que garantice attach exclusivo, o
- una carga que sea por sí misma multi-writer segura (raro; usualmente requiere un sistema de archivos en clúster o replicación a nivel de base de datos).
Broma #1: El split-brain es como darle a dos becarios root en producción—todos aprenden mucho, y la empresa aprende a arrepentirse.
Principio 3: Elige dataset vs zvol según cómo escribe tu app
Muchas configuraciones CSI ZFS te dan la opción: dataset (sistema de archivos) o zvol (bloque). No elijas por estética.
- Datasets encajan bien con POSIX, cuotas y son fáciles de inspeccionar. Geniales para cargas de archivos generales.
- ZVOLs se comportan como volúmenes de bloque y son comunes para bases de datos montadas sobre ext4/xfs o bloque crudo. Necesitan elección cuidadosa de volblocksize.
Principio 4: Ajusta propiedades ZFS por carga, no por clúster
“Una configuración ZFS para gobernarlos a todos” es cómo terminas con bases de datos tristes o pipelines de logs tristes. Usa propiedades por-dataset:
recordsize, compression, atime, xattr, logbias, primarycache/secondarycache y reservas cuando corresponda.
Principio 5: La capacidad es una configuración de rendimiento
Con ZFS, “casi lleno” no es solo un riesgo de capacidad; es un riesgo de latencia. Planifica alertas en torno a fragmentación del pool y asignación, no solo “df dice 10% libre”.
Principio 6: Observabilidad debe incluir ZFS, no solo Kubernetes
Kubernetes te dirá que el pod está Pending. No te dirá que un solo NVMe está reintentando comandos y tu pool está limitando el throughput.
Construye dashboards y alertas sobre zpool status, contadores de error, resultados de scrub y métricas de latencia.
Realidades de CSI: lo que Kubernetes hará y no hará por ti
CSI es una interfaz, no una garantía de corrección. El driver que elijas (o construyas) determina si tus volúmenes se comportan como
almacenamiento maduro o como un proyecto de feria científica con YAML.
Kubernetes hará:
- adjuntar/montar volúmenes según el driver,
- respetar la afinidad de nodo para PV locales,
- reiniciar pods en otro lugar si un PV es portable y el scheduler puede colocarlo.
Kubernetes no hará:
- replicar tus datasets ZFS locales,
- fencear un nodo para prevenir doble-escritura,
- sanar mágicamente un pool degradado,
- entender la noción de “salud del pool” de ZFS a menos que se lo enseñes (taints, condiciones de nodo o controladores externos).
Un diseño fiable acepta estos límites y construye las piezas faltantes explícitamente: orquestación de replicación, reglas de promoción
y programación basada en salud.
Una cita operativa que envejeció bien: “La esperanza no es una estrategia.” — Gene Kranz
Tareas prácticas: comandos, salidas y decisiones
Estas son las comprobaciones que realmente ejecuto cuando algo huele mal. Cada tarea incluye: el comando, qué significa la salida y la decisión
que tomas. Úsalas en revisiones de diseño e incidentes.
Task 1: Confirmar en qué nodo está realmente ligado un PVC (chequeo de la realidad de PV local)
cr0x@server:~$ kubectl get pvc -n prod app-db -o wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
app-db Bound pvc-7b3b3b9a-1a2b-4f31-9bd8-3c1f9d3b2d1a 200Gi RWO zfs-local 91d Filesystem
cr0x@server:~$ kubectl get pv pvc-7b3b3b9a-1a2b-4f31-9bd8-3c1f9d3b2d1a -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}{"\n"}'
worker-07
Significado: Si hay nodeAffinity, el PV está ligado al nodo. Tu pod no puede reprogramarse a otro sitio sin migración del volumen.
Decisión: Si esto es una base de datos de Tier-1, deja de fingir que es HA. O aceptas downtime ligado al nodo o migras a almacenamiento replicado/portable.
Task 2: Ver por qué un pod está Pending (el scheduler te delata)
cr0x@server:~$ kubectl describe pod -n prod app-db-0
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m30s default-scheduler 0/12 nodes are available: 11 node(s) didn't match Pod's node affinity, 1 node(s) had taint {node.kubernetes.io/unreachable: }, that the pod didn't tolerate.
Significado: No es “Kubernetes raro”. Está haciendo exactamente lo que le dijiste: coloca el pod donde vive el PV, pero ese nodo está inalcanzable.
Decisión: Si necesitas recuperación automática, necesitas un PV que pueda moverse (en red) o una réplica que pueda promoverse (con fencing).
Task 3: Inspeccionar la salud del pool ZFS en el nodo (comienza con hechos)
cr0x@server:~$ sudo zpool status -x
all pools are healthy
Significado: No hay errores conocidos ni vdevs degradados. Esto no significa “el rendimiento está bien”, pero sí significa “no está obviamente en llamas”.
Decisión: Si la app está lenta pero el pool está sano, pivota hacia latencia, CPU, ARC y propiedades del sistema de archivos.
Task 4: Encontrar acumulación de errores silenciosos (el tipo aterrador)
cr0x@server:~$ sudo zpool status
pool: tank
state: DEGRADED
status: One or more devices has experienced an error resulting in data corruption.
action: Restore the file in question if possible. Otherwise restore the entire pool from backup.
scan: scrub repaired 0B in 05:12:44 with 2 errors on Sun Dec 22 03:10:24 2025
config:
NAME STATE READ WRITE CKSUM
tank DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
nvme-SAMSUNG_MZVLB1T0 ONLINE 0 0 2
nvme-SAMSUNG_MZVLB1T0 ONLINE 0 0 0
errors: Permanent errors have been detected in the following files:
tank/k8s/pv/pvc-...:<0x123>
Significado: Los errores de checksum y errores permanentes no son para “monitorear después”. Indican corrupción que ZFS no pudo reparar.
Decisión: Trátalo como un incidente de pérdida de datos: identifica el PV afectado, restaura desde backup o réplica, y planifica reemplazo de disco + verificación de scrub.
Task 5: Verificar ashift y alineación de sectores físicos (decisión para siempre)
cr0x@server:~$ sudo zdb -C tank | grep -E 'ashift|vdev_tree' -n | head
72: ashift: 12
Significado: ashift=12 implica sectores 4K. Si tus discos son 4K y ashift es 9, estás pagando una penalidad de escritura permanente.
Decisión: Si ashift es incorrecto en datos de producción, planifica migrar a un pool reconstruido. No pierdas tiempo intentando “tunearlo”.
Task 6: Comprobar espacio libre del pool y fragmentación (capacidad ≠ rendimiento usable)
cr0x@server:~$ zpool list -o name,size,alloc,free,frag,health
NAME SIZE ALLOC FREE FRAG HEALTH
tank 3.62T 3.02T 614G 61% ONLINE
Significado: 61% de fragmentación con alta asignación es un multiplicador de latencia. Las escrituras se vuelven caras y las escrituras síncronas empeoran.
Decisión: Si la frag aumenta y la latencia importa, expande el pool, reduce el churn o migra cargas calientes. También revisa recordsize/volblocksize y retención de snapshots.
Task 7: Inspeccionar propiedades del dataset para un PV (evita el “talla única que no sirve” accidental)
cr0x@server:~$ sudo zfs get -o name,property,value -s local,default recordsize,compression,atime,logbias,primarycache tank/k8s/pv/pvc-7b3b3b9a
NAME PROPERTY VALUE
tank/k8s/pv/pvc-7b3b3b9a recordsize 128K
tank/k8s/pv/pvc-7b3b3b9a compression lz4
tank/k8s/pv/pvc-7b3b3b9a atime on
tank/k8s/pv/pvc-7b3b3b9a logbias latency
tank/k8s/pv/pvc-7b3b3b9a primarycache all
Significado: atime=on suele ser un amplificador de escrituras inútil para bases de datos. recordsize=128K podría ser erróneo para patrones de IO aleatorio y pequeño.
Decisión: Para datasets de bases de datos, considera atime=off y recordsize afinado al tamaño típico de bloque (a menudo 16K para Postgres). Valida con benchmarks, no con folklore.
Task 8: Para PVs respaldados por ZVOL, comprobar volblocksize (la palanca oculta de rendimiento)
cr0x@server:~$ sudo zfs get -o name,property,value volblocksize tank/k8s/zvol/pvc-1f2e3d4c
NAME PROPERTY VALUE
tank/k8s/zvol/pvc-1f2e3d4c volblocksize 8K
Significado: volblocksize se fija al crear. Si tu BD usa páginas de 16K y escogiste 8K, puedes duplicar operaciones IO.
Decisión: Si volblocksize es incorrecto para una carga crítica, planifica migrar el volumen a un zvol con tamaño correcto.
Task 9: Confirmar si un dataset tiene reserva (evita la cascada “pool lleno”)
cr0x@server:~$ sudo zfs get -o name,property,value refreservation,reservation tank/k8s/pv/pvc-7b3b3b9a
NAME PROPERTY VALUE
tank/k8s/pv/pvc-7b3b3b9a reservation none
tank/k8s/pv/pvc-7b3b3b9a refreservation none
Significado: Sin espacio reservado. Un vecino ruidoso puede llenar el pool y tu PV “importante” fallará escrituras.
Decisión: Para PV críticos en pools compartidos, asigna reservations o separa pools. Prefiere separación de ingeniería sobre discutir durante incidentes.
Task 10: Comprobar el horario de scrub y el último resultado (los desastres lentos comienzan aquí)
cr0x@server:~$ sudo zpool status tank | sed -n '1,12p'
pool: tank
state: ONLINE
scan: scrub repaired 0B in 04:01:55 with 0 errors on Sun Dec 15 03:00:11 2025
config:
...
Significado: El scrub se ejecutó y no encontró errores. Genial. Si tu scrub no se ha ejecutado en meses, estás aplazando malas noticias.
Decisión: Establece una cadencia de scrub apropiada al tamaño del disco y churn. Luego alerta si los scrubs dejan de ocurrir o comienzan a encontrar errores.
Task 11: Validar frescura de replicación (RPO es una métrica, no una promesa)
cr0x@server:~$ sudo zfs list -t snapshot -o name,creation -s creation | tail -5
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0010 Thu Dec 25 00:10 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0020 Thu Dec 25 00:20 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0030 Thu Dec 25 00:30 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0040 Thu Dec 25 00:40 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0050 Thu Dec 25 00:50 2025
Significado: Existen snapshots en el origen. Eso aún no es replicación. Necesitas confirmar que llegaron al destino y que son recientes.
Decisión: Si los snapshots están retrasados más allá de tu RPO, limita otro tráfico, depura fallos de send/recv y deja de vender “HA” internamente.
Task 12: Ejecuta un dry run mental de zfs send/receive (sabe lo que pasaría)
cr0x@server:~$ sudo zfs send -nPv tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0050 | head
send from @ to tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0050 estimated size is 3.14G
total estimated size is 3.14G
TIME SENT SNAPSHOT
Significado: El tamaño estimado de send es razonable. Si es enorme para un cambio pequeño, puede haber mismatch de recordsize o churn excesivo.
Decisión: Si los sends incrementales son inesperadamente grandes, revisa frecuencia de snapshots, recordsize/volblocksize y si la app reescribe archivos grandes constantemente.
Task 13: Identificar bloqueos de montaje/IO a nivel de nodo (cuando los pods “se cuelgan”)
cr0x@server:~$ dmesg -T | tail -20
[Thu Dec 25 01:12:11 2025] INFO: task kworker/u32:4:12345 blocked for more than 120 seconds.
[Thu Dec 25 01:12:11 2025] zio pool=tank vdev=/dev/nvme0n1 error=5 type=1 offset=123456 size=131072 flags=1809
[Thu Dec 25 01:12:12 2025] blk_update_request: I/O error, dev nvme0n1, sector 987654
Significado: El kernel reporta tareas bloqueadas y errores de IO. Esto no es un problema de Kubernetes; es un incidente de almacenamiento en el nodo.
Decisión: Cordonea el nodo, drena cargas no stateful y comienza reemplazo/reparación. No “simplemente reinicies el pod” en una ruta IO rota.
Task 14: Ver si Kubernetes está atascado desadjuntando un volumen (síntoma del control-plane, causa de almacenamiento)
cr0x@server:~$ kubectl get volumeattachment
NAME ATTACHER PV NODE ATTACHED AGE
csi-9a2d7c5f-1d20-4c6a-a0a8-1c0f67c9a111 zfs.csi.example.com pvc-7b3b3b9a-1a2b-4f31-9bd8-3c1f9d3b2d1a worker-07 true 3d
Significado: Kubernetes cree que el volumen está adjunto a worker-07. Si worker-07 está muerto, este attachment puede bloquear la conmutación.
Decisión: Sigue el procedimiento seguro de force-detach de tu driver. Si no tienes uno, eso es una brecha de diseño—no un fallo de operaciones.
Task 15: Comprobar la presión de ARC de ZFS (misses de caché que parecen “almacenamiento lento”)
cr0x@server:~$ sudo arcstat 1 5
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
01:20:11 812 401 49 122 15 249 31 30 3 28.1G 31.9G
01:20:12 790 388 49 110 14 256 32 22 2 28.1G 31.9G
01:20:13 840 430 51 141 17 260 31 29 3 28.1G 31.9G
01:20:14 799 420 53 150 18 244 30 26 3 28.1G 31.9G
01:20:15 820 415 51 135 16 255 31 25 3 28.1G 31.9G
Significado: High miss% significa que las lecturas caen al disco. Si tus discos están bien pero la latencia sube, la presión de ARC puede ser la culpable.
Decisión: Si ARC es demasiado pequeño o la memoria está constreñida por pods, considera dimensionado de nodos, políticas de cgroup memoria y ajustes en primarycache de datasets.
Task 16: Confirmar espacio libre real del sistema de archivos vs espacio libre del pool (trampas de quota)
cr0x@server:~$ sudo zfs get -o name,property,value used,avail,quota,refquota tank/k8s/pv/pvc-7b3b3b9a
NAME PROPERTY VALUE
tank/k8s/pv/pvc-7b3b3b9a used 187G
tank/k8s/pv/pvc-7b3b3b9a avail 13.0G
tank/k8s/pv/pvc-7b3b3b9a quota 200G
tank/k8s/pv/pvc-7b3b3b9a refquota none
Significado: El dataset está en su quota; la app verá ENOSPC aunque el pool aún tenga espacio libre.
Decisión: Aumenta la quota (con change control), limpia datos o escala la app. No “añadas discos” si la quota es el límite.
Guion de diagnóstico rápido
Cuando suena la página y alguien dice “el almacenamiento está caído”, necesitas un orden de operaciones implacable. El objetivo es aislar el cuello de botella rápido:
programación, attach/mount, salud de ZFS o fallo de dispositivo subyacente.
Primero: determina si esto es un problema de scheduling/ubicación o de IO
-
¿El pod está Pending o Running?
- Si Pending: revisa los eventos de
kubectl describe podpor afinidad de nodo/taints. - Si Running pero colgado: sospecha de ruta de montaje/IO.
- Si Pending: revisa los eventos de
- ¿El PV está ligado al nodo? Revisa PV nodeAffinity. Si sí, fallo de nodo equivale a indisponibilidad del volumen a menos que tengas replicación/promoción.
Segundo: comprueba el estado de adjunto y montaje (realidad del control-plane vs nodo)
- Comprueba
kubectl get volumeattachmentpor attachments atascados. - En el nodo, verifica stalls de montaje y errores de IO con
dmesg -T. - Si tu driver CSI tiene logs, busca timeouts, errores de permisos o bucles de “already mounted”.
Tercero: revisa salud del pool y dataset ZFS (no solo “está montado?”)
zpool status -xyzpool statuscompleto por errores.zpool listpor fragmentación y espacio libre.zfs geten el dataset/zvol afectado por propiedades que coincidan con expectativas de la carga.
Cuarto: decide si recuperar reiniciando, conmutando o restaurando
- Reiniciar solo si el nodo y el pool están saludables y el problema es a nivel software (app, kubelet, CSI transitorio).
- Conmutar solo si puedes garantizar single-writer y tienes una réplica conocida y buena.
- Restaurar si tienes errores de checksum, errores permanentes o un pool comprometido.
Broma #2: Kubernetes reprogramará tu pod en segundos; tus datos se reprogramarán ellos mismos en aproximadamente nunca.
Tres micro-historias corporativas (todas demasiado reales)
Micro-historia 1: El incidente causado por una suposición errónea
Una compañía SaaS mediana construyó una plataforma Kubernetes “de alto rendimiento” sobre ZFS local. Cada nodo tenía espejos NVMe.
Sus StatefulSets usaban un driver CSI que provisionaba datasets localmente, y todo brillaba en benchmarks.
La suposición errónea fue sutil: “Si un nodo muere, Kubernetes traerá el pod a otro sitio.” Verdadero para lo stateless.
Falso para PV locales. Habían leído las palabras “PersistentVolume” e interpretaron que significaba “persistente entre nodos”.
Cuando un switch del rack falló, un conjunto de nodos quedó inalcanzable. Los pods de base de datos pasaron a Pending porque sus PVs estaban afines a nodos.
El on-call intentó “force delete” de pods, luego “recrear PVCs”, luego “simplemente escalar a cero y de vuelta”. Nada de eso movió los datos.
El outage no fue solo downtime; fue parálisis decisoria. Nadie pudo responder: ¿es seguro adjuntar el mismo dataset en otro nodo?
La respuesta fue “no es posible”, pero no lo supieron hasta el incidente.
La solución fue aburrida: reclasificaron qué cargas podían usar PVs ligados a nodos, movieron estado Tier-1 a almacenamiento portable,
y escribieron un runbook que comienza con “¿El PV está node-affined?” Esa pregunta ahorró horas en incidentes futuros.
Micro-historia 2: La optimización que salió mal
Un equipo grande quería reducir overhead de almacenamiento. Los snapshots se acumulaban, las ventanas de replicación crecían y alguien
propuso un “sprint de eficiencia”: aumentar compresión, reducir recordsize globalmente y aumentar frecuencia de snapshots para reducir RPO.
El cambio lucía bien en papel. En práctica, el recordsize menor incrementó la presión de metadatos y la fragmentación en datasets ocupados.
La mayor frecuencia de snapshots incrementó churn y tamaños de send para cargas que reescribían archivos grandes. La replicación no se hizo más rápida; se hizo más ruidosa.
Peor aún, aplicaron los cambios globalmente, incluyendo volúmenes que no lo necesitaban: agregadores de logs, caches de builds y bases de datos con patrones IO diferentes.
Las latencias cola comenzaron a subir primero. Luego profundidades de cola. Luego tickets de soporte.
La lección no fue “la compresión es mala” o “recordsize pequeño es malo”. La lección fue que los knobs de almacenamiento son específicos por carga,
y las optimizaciones globales son cómo fabricas un outage cross-funcional.
Recuperaron haciendo rollback a defaults por StorageClass y definiendo perfiles: “db-postgres”, “db-mysql”, “logs”, “cache”, cada uno mapeado a una plantilla de dataset.
El equipo de plataforma dejó de ser un culto de tuning y empezó a ser un servicio.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Una firma de servicios financieros ejecutaba ZFS en nodos de almacenamiento dedicados exportando volúmenes a Kubernetes. Nada exótico. La “innovación” fue disciplina:
scrubs semanales, alertas en cualquier error de checksum y una política de que un pool degradado dispara un incidente aunque las apps parezcan bien.
Un mes, los scrubs comenzaron a reportar un pequeño número de errores de checksum corregibles en un mirror. Las apps estaban saludables. La tentación
fue aplazar: “Lo reemplazamos el próximo trimestre.” No lo hicieron. Reemplazaron el dispositivo sospechoso y scrubearon hasta quedar limpio.
Dos semanas después, otro disco en el mismo pool sufrió una falla repentina. Porque el dispositivo anterior ya había sido reemplazado, el mirror
se mantuvo intacto y el pool quedó ONLINE. Sin scrambling, sin restauración, sin ejecutivos preguntando “¿por qué no vimos esto venir?”
El equipo no celebró con una sala de guerra. Ese era el punto. La práctica correcta fue tan aburrida que previno que existiera una historia.
En operaciones, lo aburrido es una característica.
Errores comunes: síntoma → causa raíz → solución
Esta sección es intencionadamente específica. Si reconoces un síntoma, deberías poder actuar sin reinventar la ingeniería de almacenamiento bajo estrés.
1) Pod atascado en Pending tras fallo de nodo
Síntoma: Pod de StatefulSet no se programa; eventos mencionan node affinity o “volume node affinity conflict.”
Causa raíz: PV local ligado al nodo; el nodo es inalcanzable o está tainted.
Solución: Si el downtime es aceptable, restaura el nodo. Si no, rediseña: almacenamiento portable o almacenamiento local replicado con promoción y fencing.
2) Pod Running pero la app hace timeouts en disco
Síntoma: Contenedor arriba; logs de app muestran timeouts de IO; kubectl exec se cuelga a veces.
Causa raíz: Errores de dispositivo subyacente o IO bloqueado; ZFS puede estar reintentando; nodo “medio muerto”.
Solución: Revisa dmesg, zpool status. Cordonea el nodo. Reemplaza dispositivos fallando. No reinicies pods en la misma ruta IO rota.
3) “No space left on device” pero el pool tiene espacio libre
Síntoma: La app recibe ENOSPC; zpool list muestra espacio libre.
Causa raíz: Quota o refquota del dataset alcanzada, o reservas de snapshots consumiendo espacio.
Solución: Inspecciona zfs get quota,refquota,used,avail. Aumenta quota, limpia datos o ajusta política de snapshots.
4) Replicación existe pero la conmutación pierde “escrituras recientes”
Síntoma: Tras promover, los datos están obsoletos; faltan los últimos minutos.
Causa raíz: Replicación asíncrona y RPO no cumplido (lag de snapshots, backlog de send).
Solución: Mide lag de replicación; ajusta frecuencia de snapshots y concurrencia de send; asegura ancho de banda; considera replicación síncrona si el negocio lo exige (y acepta latencia).
5) Sends incrementales inesperadamente grandes
Síntoma: La replicación incremental se infla pese a cambios pequeños.
Causa raíz: La carga reescribe archivos grandes; mal match de recordsize; fragmentación; demasiados snapshots pequeños causando churn.
Solución: Afina recordsize/volblocksize por carga; reduce churn; ajusta cadencia de snapshots; considera replicación a nivel de aplicación para patrones de reescritura intensiva.
6) Errores de montaje: “already mounted” o pods en Terminating atascados
Síntoma: Logs CSI muestran conflictos de montaje; pods quedan en Terminating; attachment permanece true.
Causa raíz: El nodo murió sin un unmount limpio; objeto de attachment obsoleto; driver no maneja bien recuperación de crash.
Solución: Usa el procedimiento de force detach soportado por el driver; aplica fencing; afina timeouts de kubelet; valida comportamiento de recuperación en staging.
7) Pool está ONLINE pero la latencia es terrible
Síntoma: Sin errores; apps lentas; p99 sube; scrubs bien.
Causa raíz: Pool casi lleno, alta fragmentación, misses de ARC, amplificación en writes sync, o escrituras aleatorias pequeñas golpeando vdevs HDD.
Solución: Revisa fragmentación y asignación; ajusta propiedades de dataset; añade capacidad; mueve cargas sync-heavy a vdevs con SLOG apropiado (solo si lo entiendes).
Listas de verificación / plan paso a paso
Paso a paso: elige la estrategia de PV correcta por carga
-
Clasifica la carga.
- Tier-0: pérdida de datos inaceptable; downtime minutos, no horas.
- Tier-1: downtime aceptable pero debe ser predecible; restauración probada.
- Tier-2: caches reconstruibles y datos derivados.
-
Elige el dominio de fallo con el que puedas vivir.
- Si Tier-0: no uses PV local ligado al nodo sin replicación + fencing.
- Si Tier-1: PV local puede estar bien si la recuperación de nodo es rápida y practicada.
- Si Tier-2: PV local suele estar bien; optimiza para simplicidad.
-
Elige dataset vs zvol.
- Dataset para archivos generales, logs y apps que se benefician de inspección fácil.
- Zvol para semántica de bloque o cuando tu stack CSI lo espera; configura volblocksize intencionalmente.
-
Define perfiles de almacenamiento como código.
- Por perfil: recordsize/volblocksize, compression, atime, logbias, quotas/reservations.
- Mapea perfiles a StorageClasses, no a conocimiento tribal.
-
Planifica y prueba fallo de nodo.
- Apaga un nodo en staging mientras la BD escribe.
- Cronometra cuánto toma hasta que el servicio está saludable de nuevo.
- Verifica que no sean posibles condiciones de split-brain.
Checklist operacional: cómo se ve “listo para producción”
- Scrubs programados y alertas si se pierden o encuentran errores.
- Salud de pools monitorizada (degradado, errores de checksum, eventos de extracción de dispositivo).
- Umbrales de capacidad basados en asignación del pool y fragmentación, no solo GB libres.
- Réplica verificada (si usas replicación): última recepción exitosa, lag y procedimiento de promoción.
- Fencing documentado y probado (si promueves réplicas): quién/qué evita dual-writer.
- Runbooks existen para: pérdida de nodo, fallo de disco, attachment atascado, restaurar desde snapshot y rollback.
- Backups probados como restauraciones, no como sensaciones tibias.
Preguntas frecuentes
1) ¿Puedo obtener “verdadera HA” con PVs ZFS locales?
No por defecto. Los PVs locales están ligados a un nodo. Para HA necesitas replicación hacia otro nodo y un mecanismo seguro de promoción con fencing,
o necesitas almacenamiento en red/portable.
2) ¿La replicación ZFS (send/receive) es suficiente para conmutación?
Es necesaria pero no suficiente. También necesitas orquestación (cuándo snapshotear, send, receive, promover) y fencing estricto de single-writer
para no corromper datos durante fallos parciales.
3) ¿Debería usar datasets o zvols para PVs en Kubernetes?
Los datasets son más simples de inspeccionar y afinar para cargas de archivos. Los zvols son mejores cuando necesitas semántica de bloque o tu driver CSI lo espera.
Para bases de datos, ambos pueden funcionar—solo afina recordsize/volblocksize deliberadamente.
4) ¿Cuál es el mayor “gotcha” durante fallos de nodo?
El estado de attach/mount queda atascado mientras el nodo es inalcanzable, y tu clúster no puede volver a adjuntar de forma segura en otro lugar sin arriesgar doble-escritura.
Si tu diseño depende de operaciones manuales de fuerza, estás apostando tu RTO a la calma humana.
5) ¿Kubernetes maneja fencing por mí?
No. Kubernetes puede eliminar pods y reprogramarlos, pero no puede garantizar que un nodo muerto no siga escribiendo al almacenamiento a menos que el sistema de almacenamiento
imponga exclusividad o implementes fencing externamente.
6) Si ejecuto ZFS en nodos de almacenamiento dedicados y exporto NFS, ¿es eso “malo”?
No inherentemente. Es un trade-off: portabilidad más sencilla para los pods, pero debes diseñar HA de almacenamiento y fiabilidad de red. Puede ser muy sensato,
especialmente para cargas mixtas, si tratas los nodos de almacenamiento como sistemas de producción de primera clase.
7) ¿Qué propiedades ZFS son más importantes para PVs?
Para datasets: recordsize, compression=lz4 (por lo general), atime=off para muchas cargas intensivas en escritura, primarycache, logbias para apps sync-heavy.
Para zvols: volblocksize y compression. Tampoco ignores quotas/reservations.
8) ¿Cómo prevengo que un vecino ruidoso llene el pool y derribe PVs críticos?
Usa cuotas de dataset para equidad, y reservas/refreservations para volúmenes críticos si compartes pools. Mejor aún: separa cargas críticas en pools o nodos distintos cuando las apuestas son altas.
9) ¿Agregar un SLOG siempre es buena idea para bases de datos?
No. Un SLOG ayuda solo para escrituras síncronas y solo si es de baja latencia y tolerante a pérdida de energía. Un SLOG malo es un placebo caro o un nuevo punto de fallo.
Mide el comportamiento sync de tu carga antes de comprar hardware.
10) ¿Cuál es la forma más limpia de manejar “el nodo desaparece” con apps con estado?
Prefiere una arquitectura donde el estado autoritativo esté replicado a nivel de aplicación (por ejemplo, replicación de base de datos) o almacenado en un
almacenamiento portable con semántica de failover clara. La replicación a nivel de almacenamiento puede funcionar, pero debe diseñarse como un sistema, no como un script.
Conclusión: próximos pasos que puedes hacer esta semana
ZFS protegerá tus datos de rayos cósmicos y discos descuidados. Kubernetes eliminará tus pods y los reprogramará en otra parte. La trampa es suponer que esos dos “felices” se alinean durante la falla de un nodo. No lo harán a menos que lo diseñes.
Pasos prácticos:
- Inventaria los StatefulSets y etiquétalos por tolerancia a fallos (Tier-0/1/2). Hazlo explícito.
- Encuentra PVs ligados a nodos y decide si ese downtime es aceptable. Si no, rediseña ahora—no durante un outage.
- Estandariza perfiles de almacenamiento (propiedades dataset/zvol) por clase de carga. Deja de hacer experimentos de tuning globales en producción.
- Añade alertas de salud ZFS y scrubs junto a métricas de Kubernetes. El almacenamiento puede estar roto mientras los pods parecen “bien”.
- Ejecuta un game day de fallo de nodo para una carga stateful. Cronoméalo. Documenta. Arregla las partes que requieren heroísmo.
Si haces solo una cosa: convierte el dominio de fallo en una decisión de diseño de primera clase. ZFS local es rápido. Pero almacenamiento rápido que no puede conmutar es solo una forma muy eficiente de estar caído.