Diez meses de alertas de fsync en etcd, y la alerta siempre tuvo la razón
Read in English →Mi cluster de Talos corre sobre tres hosts de Proxmox: megatron, proxmox y patroxmus2. Una VM de control plane en cada uno. Durante casi un año hubo una alerta que no me soltó: la latencia de fsync del WAL de etcd. Sonó en diciembre, sonó en abril, sonó en junio y volvió a sonar en septiembre. Cada vez encontré algo real y lo arreglé. Cada vez volvió.
Esta es la historia de tener la razón cuatro veces y aun así estar equivocado.
Diciembre. Todo estaba en NFS
El primer síntoma ni siquiera era de etcd. Era KubeAPIErrorBudgetBurn. El API server se estaba comiendo su error budget y yo no sabía por qué. Hasta que leí los logs de etcd y aparecía “slow disk” y “heartbeat overloaded” por todo lado. Operaciones que deberían tardar menos de 100 ms estaban tardando entre 400 y 750 ms.
La causa dio pena cuando la vi. Las tres VMs de control plane tenían el disco en la Synology, por NFS. No una. Las tres. Había puesto etcd, lo más sensible a fsync que hay en el cluster, sobre almacenamiento de red.
Moví cada VM a disco local, una por una. Cordon, drain, apagar la VM, qm move_disk, prender, uncordon. Megatron quedó con un thin pool en su NVMe de 1 TB. Patroxmus2 ya tenía uno en su disco de 512 GB. El host que se llama proxmox solo tenía almacenamiento tipo directorio en un SSD SATA de 512 GB, así que la VM 145 cayó ahí.
Se fueron las advertencias. Se recuperó el error budget. De paso repartí los CronJobs de backup a lo largo de la noche para que dejaran de pelearse por el disco. Listo, pensé.
Abril. Un nodo que se quedaba pegado
Para abril un control plane, talos-cbw-xgr en el host proxmox, se había vuelto el problemático. Se quedaba pegado a bajo nivel y dejaba de responder. Yo tenía una automatización que supuestamente lo reiniciaba. No servía, porque mandaba un qm reset sin verificar nada, y un reset no le hace nada a un guest trabado tan abajo.
Saqué los números. En 30 días ese nodo tuvo 8 transiciones de estado Ready. Los otros dos control planes, cero. La VM worker del mismo host era la peor de todos los workers. O sea, era el host, no Kubernetes.
Entonces comparé la configuración de disco de las tres VMs. Las VMs 143 y 144 eran discos raw sobre LVM-thin. La VM 145 era un archivo qcow2 sobre un directorio. qcow2 mantiene tablas de metadata que obligan a hacer fsyncs extra en el host, y etcd no es otra cosa que fsyncs.
qm disk move 145 scsi0 local --format raw --delete 1
Tres minutos, 25 GB copiados, en el mismo host. Sin migrar a otra máquina. El p99 de fsync pasó de territorio de cuelgue a 16,6 ms.
También hice lo que debí hacer en diciembre. Talos corre etcd como servicio del sistema, así que kube-prometheus-stack no lo scrapea por defecto. Agregué un Service con Endpoints estáticos, un ServiceMonitor, un parche de MachineConfig para exponer listen-metrics-urls y seis alertas. Una de ellas era la advertencia de p99 de fsync que, mirando Loki hacia atrás, habría sonado unas 10 horas antes del cuelgue.
Y arreglé el script de reinicio para que hiciera stop, esperar, start y verificar.
Junio. El disco es el piso
Dos meses después el mismo nodo volvió a picar. Un pico llegó a 3.315 ms. Su hermano en NVMe no pasó de 5 ms en los mismos tres días.
Revisé el tuning del host que había agregado: swappiness, zram, el scheduler bfq para sda, memoria fija sin balloon para la VM 145. Todo seguía ahí. Hasta sobrevivió un reinicio del host. El p99 base era 14,8 ms. Por el lado del software ya no había más que sacar.
SMART decía que el disco estaba sano. Cero sectores reasignados, cero errores CRC. Simplemente es lo que es: un “512GB SSD” genérico, sin DRAM, en la ranura M.2 SATA de un Beelink U59. Mi primer instinto fueron SSDs enterprise con protección contra pérdida de energía. Después miré el control plane sano. Corre en un NVMe de consumo normal. La brecha no era “le falta PLP”. La brecha era SATA de la gama más baja contra cualquier cosa con caché DRAM.
Escribí el runbook. En esa caja hay una bahía de 2,5 pulgadas vacía. Entra un disco con DRAM, se le monta un thin pool, qm disk move 145 scsi0 fastssd. Y no lo hice, porque en realidad nada se había roto. El nodo siguió Ready. etcd se recuperó solo cada vez.
Esa misma investigación encontró un apagón de 9,5 horas en ese host que yo no había notado. El journal se corta a mitad de línea. La caja no vuelve a prender sola después de perder la corriente. Esa es otra historia.
Septiembre. Otro nodo, el mismo disco
En septiembre la alerta se mudó. EtcdDiskWalFsyncCritical en talos-tji-a3a, el control plane de patroxmus2. Esta vez no era el U59.
Asumí que era swap. En un PR anterior le había puesto swappiness en 10 a ese host. Volví a eso. Era inofensivo y no arregló nada.
Entonces me tocó correlacionar las métricas de disco del propio host contra cada pico de 7 días:
rate(node_disk_write_time_seconds_total{instance="patroxmus2",device="nvme0n1"}[1m])
/ rate(node_disk_writes_completed_total{instance="patroxmus2",device="nvme0n1"}[1m])
La latencia promedio de escritura del host vive alrededor de 1,5 ms. En cada pico de etcd, sin excepción, saltaba a entre 27 y 83 ms y se quedaba ahí unos 3 minutos. Las escrituras del host en esos momentos eran de unos 0,3 MB/s. Cero swap-out. Ningún backup corriendo. Nadie le estaba pidiendo nada al disco. El disco se queda pegado solo.
Es otro “512GB SSD” sin marca. Firmware SN10660. 38 por ciento de desgaste. Aloja la VM 144, el control plane, y la VM 114, un worker.
Ese nodo nunca pasó a NotReady. No es el líder de etcd. En sentido estricto la alerta era ruido. En el sentido útil me estaba diciendo la verdad todo el tiempo: el almacenamiento debajo de etcd no da la talla.
Lo que de verdad aprendí
La alerta tuvo la razón en diciembre, en abril, en junio y en septiembre. Lo que cambió fue qué tan profundo estaba dispuesto a mirar. NFS era real. qcow2 era real. El tuning del host era real. Todas eran capas por encima de la que de verdad estaba fallando.
También aprendí a anclarme en lo que funciona y no en lo que suena correcto. El control plane de megatron ha sido aburrido desde diciembre. Está en un NVMe de consumo con caché DRAM. Esa es la especificación. Ni enterprise, ni PLP. Solo que no sea el disco más barato del cajón.
Ojo, los dos SSD sin marca siguen en esas cajas. El U59 tiene la bahía libre. A patroxmus2 toca cambiarle el NVMe por uno con DRAM, lo que implica hacer cordon a un control plane a la vez y clonar o reinstalar desde los backups de vzdump. Ese cambio sigue pendiente.