Cortes por actualizaciones: cómo un parche malo puede tumbarlo todo

¿Te fue útil?

No “cambiaste mucho”. Aplicaste una actualización de seguridad, aumentaste una versión menor, rotaste un certificado o instalaste un paquete de firmware “aprobado por el proveedor”. Ahora la mitad de la flota no arranca, tus nodos de Kubernetes están NotReady y el canal de on-call suena como una batidora.

Los cortes por actualizaciones se sienten personales porque son evitables en retrospectiva e inevitables en el momento. La solución rara vez es “no parchear”. La solución es aprender dónde fallan realmente las actualizaciones, cómo detectarlo rápido y cómo diseñar rollouts para que un solo parche malo no pueda tumbarlo todo.

Por qué las actualizaciones derriban sistemas (incluso las “seguras”)

Pretendemos que las actualizaciones son lineales: aplicar parche, reiniciar servicio, seguir. Producción no es lineal. Es un montón de sistemas acoplados con timeouts, caches, contratos implícitos y “workarounds” temporales que se volvieron permanentes en algún momento del incidente anterior.

Un corte por actualización suele ser una de tres cosas:

  • Deriva de compatibilidad: el parche es correcto, pero tu entorno no es el que se probó. Kernel distinto, libc distinta, flags distintos, firmware de almacenamiento distinto, comportamiento del proxy distinto.
  • Acoplamiento operacional: el parche provoca un reinicio, y el reinicio provoca una cascada. Pools de conexiones se vacían, caches se enfrían, líderes se re-eligen, shards se re-balancean y tu autoscaler “ayuda” creando más carga.
  • Interacción con el estado: el código cambia cómo lee o escribe estado. Migraciones de esquema, reconstrucción de índices, cambios en el parseo de config, cadenas de certificados, feature flags y formatos de serialización. Funciona en staging porque staging no tiene 18 meses de estado desordenado.

Además, la frase más peligrosa en el parcheo es “versión menor”. Semver no te protege de la realidad operacional. Un cambio “menor” que modifica el número por defecto de hilos o altera el comportamiento DNS puede ser un gran corte si tu sistema está ajustado al comportamiento anterior.

Una cita para pegar en un post-it (idea parafraseada): el mensaje de la era Apollo de Gene Kranz era que “duro y competente” es el estándar; no puedes permitirte sorprenderte por tus propios sistemas. Eso es ingeniería de confiabilidad en una frase.

Y aquí está la verdad incómoda: los cortes por parches no son solo “errores del proveedor”. La mayoría son “errores nuestros”, porque desplegamos el parche a demasiada producción, demasiado rápido y sin una rampa de salida.

Hechos y contexto: parchear ha roto cosas desde siempre

Seis a diez hechos rápidos, porque la historia se repite y seguimos haciéndonos los sorprendidos:

  1. Windows “Patch Tuesday” (empezó en 2003) existe en parte para hacer el parcheo predecible; la previsibilidad es un control de fallos.
  2. El gusano Morris de 1988 no solo propagó malware: obligó a la industria a tomarse en serio el parcheo coordinado y la respuesta a incidentes.
  3. Actualizaciones de librerías SSL/TLS tienen un largo historial de romper clientes por parseo más estricto, cifrados deprecated o cambios en la validación de cadenas: mejoras de seguridad pueden ser regresiones de disponibilidad.
  4. Actualizaciones de kernel cambian con frecuencia drivers y temporizaciones. “Misma configuración” no significa “mismo comportamiento” cuando evolucionan scheduler y código de red.
  5. DNS es una víctima recurrente de parches: comportamiento del resolver, reglas de caché y search domains pueden cambiar y redirigir tráfico silente a la nada.
  6. Los cambios en el trust store de Java y el ecosistema de CA han causado cortes reales cuando certificados que antes validaban de repente no lo hacen tras una actualización.
  7. Actualizaciones de imágenes base de contenedor pueden romper compatibilidad de glibc, bundles de CA o incluso comportamiento de shell. “Es solo una imagen base” son las típicas palabras finales.
  8. Firmware de almacenamiento y microcode frecuentemente cambian las características de latencia. Incluso cuando “tienen éxito”, el perfil de rendimiento puede moverse lo suficiente para disparar timeouts y causar fallos en cascada.

Hay un patrón: parchear no es solo un cambio de corrección; es un cambio de comportamiento. Los cortes de producción suelen ser comportamentales.

Broma #1: El parche decía “no se requiere downtime”. No especificó para quién.

Los modos reales de fallo: dónde los parches realmente hacen daño

1) Reinicios, restarts y la manada atronadora que olvidaste

Muchos cortes no son causados por el binario nuevo: los causa el acto de sustituirlo. Reiniciar un servicio puede:

  • Perder peticiones en vuelo y provocar reintentos.
  • Invalidar caches, incrementando carga en bases de datos u object stores.
  • Resetear pools de conexión, forzando re-auth, handshakes TLS y nueva configuración de sesión.
  • Re-elegir líderes y re-balancear particiones (Kafka, etcd, Redis Cluster, y muchos más).

Si parcheas una flota “de forma uniforme”, aún puedes alinear los reinicios lo suficiente como para causar dolor sincronizado. Por eso importan los rollouts con jitter y los límites estrictos de concurrencia.

2) Cambios en defaults: el asesino silencioso

Las notas del parche dicen “mejora de rendimiento”. Lo que recibes son defaults distintos: más hilos, buffers mayores, timeouts más estrictos, nuevo comportamiento del resolver DNS, ajustes de garbage collection distintos, una nueva implementación de HTTP/2 o una estrategia de reintentos diferente.

Los defaults son efectivamente parte de tu configuración de producción, salvo que no los versionaste. Cuando cambian, heredas el cambio.

3) Parseo de config y comportamiento del entorno

Las actualizaciones que “mejoran la validación” pueden rechazar configs que antes se aceptaban. Ejemplos comunes:

  • Cambios en parseo YAML (indentación, coerción de tipos).
  • Claves de config renombradas o deprecated.
  • Validación de cadena de certificados más estricta.
  • Semánticas cambiadas: un booleano que antes significaba “activar feature” ahora significa “activar modo experimental”.

Si no tienes un linter de config en CI para la versión exacta que despliegas, estás jugando a la ruleta con cada actualización.

4) Almacenamiento, sistemas de archivos y sorpresas en el espacio kernel

Como ingeniero de almacenamiento, diré lo obvio en voz baja: las actualizaciones no solo rompen apps; rompen el suelo sobre el que están las apps.

  • Interacciones kernel + filesystem: un nuevo kernel cambia el scheduling de IO, el comportamiento de writeback o quirks de drivers. Aparecen picos de latencia, se disparan timeouts y de repente tu “corte de app” es realmente un incidente de latencia de almacenamiento.
  • Actualizaciones de firmware: el disco puede pasar SMART, pero la distribución de latencia cambia. La latencia de cola es donde la disponibilidad muere.
  • Multipath y reglas udev: un parche cambia el orden de naming o discovery, y tus mounts no vuelven. Los sistemas arrancan en modo de emergencia.

5) Rotura del ecosistema de dependencias

Aun si tu app es estable, tus dependencias pueden cambiar bajo ti: OpenSSL, libc, CA bundles, paquetes Python, JVM, Node, sidecars, service mesh, módulos de kernel.

La dependencia más peligrosa es la que no sabías que tenías—como una suposición hard-coded de que un resolver devuelve IPv4 primero, o que el backlog TCP por defecto es “suficientemente grande”.

6) Cambios en observabilidad: pierdes los ojos durante el fallo

Una actualización puede romper logging (permisos), métricas (incompatibilidad de sidecar), trazas (agent cae) o sincronización de tiempo (cambios en ntpd/chronyd). El sistema falla y también falla tu capacidad para explicar por qué.

Tres microhistorias corporativas del taller de parches

Microhistoria 1: El corte causado por una suposición equivocada

Una empresa SaaS mediana ejecutaba un setup multi-región con tráfico active-active. Parchearon un conjunto de nodos edge—reverse proxies y terminadores TLS—tras una actualización de librería. El plan de rollout fue “25% por región, luego continuar”. Razonable. Todos se fueron a casa temprano.

El tráfico no cayó de inmediato. Se puso extraño. Un pequeño pero creciente segmento de clientes empezó a fallar con errores de handshake TLS. Llegaron tickets de soporte: “funciona en Wi‑Fi, falla en móvil”, “funciona desde la oficina, falla desde casa”. Ingeniería miró gráficos y vio un aumento suave de 5xx en el edge, no lo suficiente para disparar la alerta principal. El tipo de fallo que te hace dudar de tus dashboards.

La suposición equivocada fue sutil: el equipo creía que todos los caminos de cliente negociaban el mismo comportamiento TLS. En realidad, un grupo de clientes usaba trust stores Android antiguos y algunos middleboxes empresariales realizaban inspección TLS con suposiciones frágiles. La librería TLS actualizada endureció la validación de la cadena de certificados y ya no toleraba un orden de cadena que algunos clientes antes aceptaban. La cadena era técnicamente válida, pero el mundo está lleno de implementaciones “lo suficientemente válidas”.

La solución no fue solo “revertir el parche”. Tuvieron que ajustar el orden de la cadena servida y asegurarse de entregar el intermedio correcto en una forma más compatible. También aprendieron que “tasa de éxito TLS por familia de cliente” no es una métrica de vanidad; es una métrica de disponibilidad. Su postmortem terminó con una nueva población canaria: no solo un subconjunto de servidores, sino un subconjunto de clientes, probado con sondas sintéticas que usaban pilas TLS variadas.

Luego admitieron lo peor: staging y pre-prod eran demasiado limpios. Sin middleboxes raros. Sin clientes antiguos. El parche no rompió su mundo. Rompió el mundo real.

Microhistoria 2: La optimización que salió mal

Un equipo de plataforma de datos mantenía un gran clúster PostgreSQL y una flota de servicios API. Querían despliegues más rápidos y menos “impuesto de reinicio”, así que activaron una nueva característica en su runtime de servicio: reutilización de conexión más agresiva y un pool de conexiones mayor por defecto. Se entregó como parte de una actualización inocua de dependencia.

El parche se desplegó sin problemas. La latencia incluso mejoró durante la primera hora. Luego la base de datos comenzó a tambalearse. No un crash duro—peor. CPU con picos, esperas de lock en aumento y timeouts ocasionales de consultas. El on-call vio errores en la API, pero los gráficos de BD parecían un monitor cardiaco. Cardio clásico de sistemas distribuidos.

La “optimización” aumentó el número de conexiones activas concurrentes por pod. Bajo carga, la capa API dejó de descargar carga y en su lugar mantuvo más conexiones por más tiempo. La sobrecarga de conexiones y la contención de locks en PostgreSQL aumentaron, lo que incrementó la latencia de consultas, lo cual provocó reintentos de la aplicación y aumentó aún más la carga. Un bucle de realimentación positivo ordenado.

La primera instinto del equipo fue escalar la BD. Eso lo empeoró porque la restricción real no era CPU cruda; era contención y comportamiento de colas. La solución real fue aburrida: reducir pools de conexión, aplicar timeouts de sentencias del lado servidor y añadir backpressure en la capa API. También cambiaron la guardia de rollout: cualquier parche que afecte el comportamiento de conexiones debe canariarse con una reproducción de carga y vigilancia SLO centrada en la BD.

Lección: las mejoras de rendimiento que cambian la concurrencia son esencialmente un sistema nuevo. Trátalas como tratarías un sistema nuevo: prueba, canary, limita el radio de impacto y espera rarezas.

Microhistoria 3: La práctica aburrida pero correcta que salvó el día

Una empresa regulada ejecutaba una plataforma Kubernetes interna y muchas cargas stateful. Su programa de parcheo era profundamente poco sexy: ventanas de cambio estrictas, canarios obligatorios y una regla de que cada actualización de nodo debía dejar intacto un dominio de fallo completo hasta que la validación pasara. La gente se quejaba del “proceso”. Por supuesto que sí.

Programaron una actualización de kernel que incluía un cambio en un driver de almacenamiento. Los nodos canarios se actualizaron y reiniciaron. En minutos, sus dashboards de latencia de almacenamiento mostraron una mala cola: p99 IO latency saltó y un subconjunto de pods comenzó a hacer timeout en escrituras. Nada explotó todavía; solo estaba enfermo.

Porque su proceso forzaba canarios y retenía un dominio de fallo intacto, tuvieron un lugar seguro para mover cargas. Hicieron cordon a los nodos canarios, los drenaron y migraron pods stateful a nodos no afectados. El impacto se mantuvo limitado: pequeñas degradaciones, sin corte total, sin pérdida de datos.

Luego hicieron lo aburrido: detuvieron el rollout y abrieron un ticket con el proveedor con evidencia específica—versión del driver, versión del kernel, histogramas de latencia y fragmentos de dmesg. El proveedor confirmó después una regresión provocada por cierto firmware HBA. La empresa no fue heroica; fue disciplinada.

Esa es la historia que quieres: una donde te molesta tu control de cambios porque te evitó un incidente emocionante.

Guía de diagnóstico rápido: qué revisar primero/segundo/tercero

Esta es la “tienes cinco minutos antes de que la dirección se suba al puente” guía. El objetivo no es ser perfecto; es identificar la clase de cuello de botella y detener la hemorragia.

Primero: deja de empeorarlo

  • Congela los rollouts: detén la propagación del parche. Si usas automatización, deshabilita el job y confirma que se detuvo.
  • Limita reintentos: si tienes un toggle global para tormentas de reintentos o circuit breakers, úsalo.
  • Preserva una isla conocida como buena: evita que tu último segmento sano sea “arreglado” hasta quedar en el mismo estado de fallo.

Segundo: clasifica el fallo con 3 preguntas

  1. ¿Es a nivel de arranque o a nivel de servicio? Si los nodos no arrancan, estás en territorio de kernel/driver/filesystem. Si los servicios están arriba pero fallan, es app/config/dependencia/tráfico.
  2. ¿Es localizado o sistémico? ¿Una AZ? ¿Una versión? ¿Un perfil de hardware? ¿Un cohorte de clientes? Los fallos localizados apuntan a patrones de rollout, heterogeneidad o upgrades parciales.
  3. ¿Es latencia o corrección? Picos de latencia causan timeouts y cascadas; errores de corrección causan fallos inmediatos. Las mitigaciones difieren.

Tercero: encuentra el cuello de botella rápido

Ejecuta un bucle ajustado: elige una instancia que falla, una que funciona y compáralas. Busca la diferencia que importa: versiones de paquetes, kernel, config, vars de entorno, stores de certificados, DNS, rutas, mounts y latencia de IO.

Si no puedes explicar por qué un host funciona y otro no, aún no estás troubleshooting—estás de turismo.

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

Estas son tareas prácticas que puedes hacer durante un corte por actualización. Cada una incluye un comando, un fragmento realista de salida, lo que significa y la decisión que tomas.

Task 1: Confirma qué cambió realmente (diff de versión de paquete)

cr0x@server:~$ apt-cache policy openssl | sed -n '1,12p'
openssl:
  Installed: 3.0.2-0ubuntu1.12
  Candidate: 3.0.2-0ubuntu1.12
  Version table:
 *** 3.0.2-0ubuntu1.12 500
        500 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages
        100 /var/lib/dpkg/status
     3.0.2-0ubuntu1.10 500
        500 http://archive.ubuntu.com/ubuntu jammy-security/main amd64 Packages

Significado: Estás en un build específico; puedes ligar el comportamiento a él. Si un canario está en .12 y nodos sanos en .10, tienes un sospechoso principal.

Decisión: Si la correlación coincide con los fallos, detén el rollout y considera fijar o revertir el paquete.

Task 2: Identifica la versión del kernel y el arranque reciente (chequeo de regresión de boot)

cr0x@server:~$ uname -r
6.5.0-21-generic
cr0x@server:~$ who -b
         system boot  2026-01-22 02:14

Significado: Confirma el kernel en ejecución y si el nodo se reinició recientemente como parte del parche.

Decisión: Si los fallos comenzaron tras el reboot en un kernel nuevo, trátalo como regresión de kernel/driver; prioriza revertir al kernel anterior o arrancar en conocido bueno.

Task 3: Encuentra unidades fallidas rápido (vista systemd)

cr0x@server:~$ systemctl --failed
  UNIT                      LOAD   ACTIVE SUB    DESCRIPTION
● myapp.service             loaded failed failed MyApp API Service
● node-exporter.service     loaded failed failed Prometheus Node Exporter

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

Significado: Lista rápida de qué está abajo. Si los agentes de observabilidad también fallan, espera puntos ciegos.

Decisión: Arregla los “ojos” (métricas/logs) temprano si es posible, o compensa con inspección directa en host.

Task 4: Lee las últimas 200 líneas de logs de una unidad (busca errores de parseo/config)

cr0x@server:~$ journalctl -u myapp.service -n 200 --no-pager
Jan 22 02:16:03 server myapp[1842]: FATAL: invalid config: unknown key "http.keepalive_timeout_ms"
Jan 22 02:16:03 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Jan 22 02:16:03 server systemd[1]: myapp.service: Failed with result 'exit-code'.

Significado: La actualización endureció el esquema de config; la clave antigua ya no se soporta.

Decisión: Revertir config o binario para restaurar compatibilidad; no “edites caliente” en flota sin entender el skew de versiones.

Task 5: Compara linkage binario/librerías (mismatch de dependencias)

cr0x@server:~$ ldd /usr/local/bin/myapp | egrep 'ssl|crypto|not found'
libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007f4e9b2b0000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f4e9ae20000)

Significado: Confirma contra qué OpenSSL mayor estás linkeado y si falta algo.

Decisión: Si ves “not found”, restaura la librería faltante o redeploya un build compatible; rollback vence a la improvisación.

Task 6: Revisa resolución DNS y config del resolver (un daño post-actualización común)

cr0x@server:~$ resolvectl status | sed -n '1,25p'
Global
         Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
  resolv.conf mode: stub
Current DNS Server: 10.10.0.53
       DNS Servers: 10.10.0.53 10.10.0.54
        DNS Domain: corp.internal

Link 2 (eth0)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported

Significado: Muestra qué resolver está activo y si systemd-resolved está en juego. Los parches pueden cambiar modos o corromper el manejo de /etc/resolv.conf.

Decisión: Si el servidor DNS cambió o desaparecieron los search domains, restaura la configuración del resolver y reinicia servicios impactados antes de perseguir timeouts “aleatorios”.

Task 7: Valida TLS desde el host (cadena de certificados y handshake)

cr0x@server:~$ openssl s_client -connect api.internal:443 -servername api.internal -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = api.internal
Verification: OK

Significado: El handshake funciona desde este host con este build de OpenSSL.

Decisión: Si esto falla solo en hosts actualizados, sospecha cambios en CA bundle/trust store o validación más estricta; considera fijar la versión del CA bundle o reemitir la cadena.

Task 8: Revisa deriva de tiempo (TLS y fallos de auth aman el tiempo incorrecto)

cr0x@server:~$ timedatectl
               Local time: Thu 2026-01-22 02:21:44 UTC
           Universal time: Thu 2026-01-22 02:21:44 UTC
                 RTC time: Thu 2026-01-22 02:21:45
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: no
              NTP service: active
          RTC in local TZ: no

Significado: El servicio NTP está activo pero el reloj no está sincronizado. Tras actualizaciones, los servicios de tiempo pueden fallar silenciosamente o cambiar de proveedor.

Decisión: Arregla la sincronización antes de depurar auth/TLS. Un reloj desincronizado convierte certificados válidos en “expirados”.

Task 9: Encuentra latencia de IO y saturación (regresiones de almacenamiento)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0-21-generic (server) 	01/22/2026 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           8.12    0.00    3.44    9.87    0.00   78.57

Device            r/s     w/s   rkB/s   wkB/s  await  %util
nvme0n1         12.0   180.0   640.0  9216.0  38.20  92.10

Significado: Alto %util y await elevado indican que el dispositivo está saturado y las peticiones esperan.

Decisión: Si esto empezó tras un update de kernel/firmware, trátalo como regresión de almacenamiento. Mitiga moviendo carga, reduciendo amplificación de escritura o revirtiendo la actualización.

Task 10: Revisa estado de mounts y errores (fallos de arranque y en runtime)

cr0x@server:~$ mount | egrep ' /data | /var/lib '
/dev/mapper/vg0-data on /data type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k)
cr0x@server:~$ dmesg -T | tail -n 6
[Thu Jan 22 02:20:11 2026] XFS (dm-2): Metadata corruption detected at xfs_inode_buf_verify+0x1a/0x70
[Thu Jan 22 02:20:11 2026] XFS (dm-2): Unmount and run xfs_repair

Significado: El mount existe pero el kernel reporta corrupción de metadata. A veces una actualización expone corrupción latente; otras veces un bug de driver escribe basura.

Decisión: Para el servicio que escribe en ese filesystem. Saca el sistema de rotación. Planea reparación desde rescue mode; no sigas escribiendo.

Task 11: Verifica rutas de red y MTU (la pérdida de paquetes post-update es real)

cr0x@server:~$ ip -br link show eth0
eth0             UP             00:16:3e:2b:8c:11 <BROADCAST,MULTICAST,UP,LOWER_UP>
cr0x@server:~$ ip link show eth0 | grep mtu
    mtu 9000 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
cr0x@server:~$ ping -M do -s 8972 -c 2 10.10.0.1
PING 10.10.0.1 (10.10.0.1) 8972(9000) bytes of data.
ping: local error: message too long, mtu=1500

Significado: La interfaz dice MTU 9000, pero el camino se comporta como 1500. Un parche puede haber cambiado offload NIC, config VLAN o la ruta.

Decisión: Ajusta la MTU al valor soportado por el path o arregla la configuración de red. Desajustes de MTU causan timeouts y fallos parciales extraños.

Task 12: Revisa salud de nodos Kubernetes y skew de versiones (actualizaciones de nodos en rolling)

cr0x@server:~$ kubectl get nodes -o wide
NAME        STATUS     ROLES    AGE   VERSION   INTERNAL-IP   OS-IMAGE             KERNEL-VERSION
node-01     Ready      worker   91d   v1.29.3   10.20.1.11    Ubuntu 22.04.4 LTS   6.5.0-21-generic
node-02     NotReady   worker   91d   v1.29.3   10.20.1.12    Ubuntu 22.04.4 LTS   6.5.0-21-generic
node-03     Ready      worker   91d   v1.29.3   10.20.1.13    Ubuntu 22.04.4 LTS   6.2.0-39-generic

Significado: Node-02 está NotReady y comparte versión de kernel con node-01; node-03 está en kernel más antiguo. El skew de versiones puede ser tu grupo de control.

Decisión: Si NotReady correlaciona con kernel nuevo, cordona/drena nodos afectados y pausa el rollout del OS.

Task 13: Encuentra por qué un nodo Kubernetes está NotReady (kubelet/container runtime)

cr0x@server:~$ kubectl describe node node-02 | sed -n '1,80p'
Conditions:
  Type             Status  LastHeartbeatTime                 Reason              Message
  Ready            False   Thu, 22 Jan 2026 02:24:11 +0000   KubeletNotReady     container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready

Significado: El nodo no está listo porque el plugin de red (CNI) no puede inicializar, a menudo debido a cambios en iptables/nftables, módulos del kernel o deriva de config del runtime.

Decisión: Valida pods CNI, backend de iptables y módulos del kernel. Si fue causado por una actualización, revierte la imagen del nodo o fija el modo de iptables.

Task 14: Confirma backend de iptables (nft vs legacy) tras una actualización

cr0x@server:~$ update-alternatives --display iptables | sed -n '1,25p'
iptables - auto mode
  link best version is /usr/sbin/iptables-nft
  link currently points to /usr/sbin/iptables-nft
  link iptables is /usr/sbin/iptables
  slave iptables-restore is /usr/sbin/iptables-restore
/usr/sbin/iptables-legacy - priority 10
/usr/sbin/iptables-nft - priority 20

Significado: El sistema usa el backend nft. Algunas CNIs o scripts aún asumen el comportamiento legacy de iptables.

Decisión: Si CNI rompió tras el update, vuelve temporalmente a legacy o actualiza componentes CNI; elige primero la solución de menor radio de impacto.

Task 15: Revisa conexiones activas y tormentas de reintentos (¿es el corte auto-infligido ahora?)

cr0x@server:~$ ss -s
Total: 3812 (kernel 0)
TCP:   3421 (estab 2902, closed 331, orphaned 2, timewait 331)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       26        19        7
TCP       3090      2879      211
INET      3116      2898      218
FRAG      0         0         0

Significado: Miles de conexiones establecidas. Si eso es más alto de lo normal y timewait crece, podrías estar en territorio de amplificación por reintentos.

Decisión: Aplica rate limits/circuit breakers, reduce reintentos de clientes y considera sacar algo de capacidad para restaurar estabilidad (sí, a veces menos es más).

Broma #2: Si no puedes reproducir el problema, felicidades—has construido un sistema distribuido. El resto de nosotros todavía está depurando el tuyo.

Errores comunes: síntoma → causa raíz → arreglo

1) Síntoma: “Todo está arriba, pero la latencia se duplicó y los timeouts suben”

Causa raíz: El parche cambió el scheduler de IO, la pila de red o el comportamiento de conexiones; la latencia de cola aumentó, disparando timeouts y reintentos.

Arreglo: Identifica el cuello de botella (IO vía iostat, CPU steal, pérdidas de red). Reduce concurrencia y reintentos inmediatamente. Revierte el cambio si la distribución de latencia se desplazó más allá del SLO.

2) Síntoma: “Solo fallan algunos clientes (móvil, redes empresariales, regiones específicas)”

Causa raíz: Actualización de TLS/librería endureció validación, cambió preferencia de cifrados o alteró la entrega de cadena de certificados; regresión de compatibilidad.

Arreglo: Prueba con pilas de cliente diversas. Ajusta la cadena servida, reemite certificados o fija settings compatibles. Canary en cohortes reales de clientes mediante sondas sintéticas.

3) Síntoma: “Los nodos no arrancan después del día de parches”

Causa raíz: Actualización de kernel/driver + regresión de almacenamiento o red; o initramfs sin módulo; o cambio en fstab/naming de dispositivos.

Arreglo: Arranca con el kernel anterior desde GRUB, o usa rescue mode para arreglar initramfs/módulos. Estabiliza revirtiendo la imagen del nodo; luego investiga compatibilidad de driver/firmware.

4) Síntoma: “Nodos Kubernetes quedan NotReady durante actualización de OS”

Causa raíz: Incompatibilidad del runtime o CNI, cambio de backend iptables, cambios en módulos kernel (overlay, br_netfilter) o regresión de MTU.

Arreglo: Compara nodos funcionando/fallando para backend iptables, módulos kernel y logs CNI. Revierte la imagen del nodo o fija modo de iptables; solo entonces procede con una golden image corregida.

5) Síntoma: “El servicio no arranca tras upgrade; logs muestran ‘unknown key’ o error de esquema”

Causa raíz: Cambió el esquema de config; la validación se endureció; config previamente tolerada ahora se rechaza.

Arreglo: Versiona la config y valida en CI contra la versión objetivo. Durante el incidente, revierte al binario previo o elimina/renombra la clave conflictiva.

6) Síntoma: “Errores en la base de datos tras un parche de app; conexiones se disparan”

Causa raíz: Cambió el default de pool de conexiones o la lógica de reintentos amplificó la carga; la BD se vuelve víctima compartida.

Arreglo: Limita concurrencia, reduce tamaños de pool, aplica timeouts del lado servidor y añade backpressure. Revierte el cambio que alteró el comportamiento de conexiones.

7) Síntoma: “Las métricas desaparecieron justo cuando todo se puso mal”

Causa raíz: Actualización de agente o exporter incompatible con kernel/userspace; permisos cambiados; hardening de systemd activado.

Arreglo: Restaura observabilidad mínima primero: recupera métricas y logs de nodo. Usa inspección directa en host mientras los agentes están caídos.

8) Síntoma: “Solo falla en un modelo de hardware”

Causa raíz: Interacción firmware + driver, diferencias de microcode o cambios en offload NIC por defecto.

Arreglo: Segmenta rollouts por clase de hardware. Mantén una matriz de compatibilidad. No mezcles updates de firmware con updates de OS a menos que te gusten los rompecabezas difíciles.

Listas de verificación / plan paso a paso

Checklist A: Antes de parchear (los controles aburridos que previenen outages a escala mundial)

  1. Define el radio de impacto por adelantado. % máximo de flota, % máximo por AZ, % máximo por capa de servicio. Escríbelo. Haz que la automatización lo haga cumplir.
  2. Establece un canario representativo. Mismo tráfico, misma forma de datos, mismas dependencias, misma clase de hardware. Si tu canario es “una VM solitaria”, no es un canario; es un señuelo.
  3. Requiere una vía de rollback. Reversión de imagen, downgrade de paquete, apagar feature flag, revertir config, fallback de kernel. Si el rollback requiere heroísmos, no tienes rollback.
  4. Fija lo que no debe derivar. CA bundles, libc, JVM, librerías críticas. Aún puedes actualizarlas—pero de forma intencional, no accidental.
  5. Prueba seguridad de reinicio. Haz load tests del comportamiento de reinicio, no solo del estado estacionario. Observa churn de conexiones, tiempo de calentamiento de caches y elecciones de líderes.
  6. Protege la base de datos y el almacenamiento. Pon guardrails: max conexiones, límites de cola, presupuestos de IO. Los parches suelen fallar moviendo presión aguas abajo.
  7. Vigila latencia de cola, no promedios. No alarmas por p50. Tus clientes tampoco; simplemente se van.
  8. Valida config contra la versión objetivo en CI. “Arranca en staging” no es contrato. Es una sugerencia.

Checklist B: Durante un corte por actualización (contención primero, luego cura)

  1. Congela cambios y detén la automatización de rollout. Confirma que se detuvo.
  2. Identifica el límite entre roto y sano. Versión, región, hardware, grupo de nodos, cohorte de clientes.
  3. Selecciona un nodo que falla y uno que funciona. Diféalos: versiones, configs, kernel, resolver, rutas, mounts, CA bundle.
  4. Decide rápido: avanzar o retroceder. Si no entiendes el modo de fallo en 15–30 minutos, rollback suele ser más barato.
  5. Reduce la carga mientras debuggeas. Desactiva reintentos agresivos, descarga tráfico no crítico y para jobs batch que amplifican IO.
  6. Preserva evidencia. Guarda logs, listas de paquetes e información de versión de nodos fallidos antes de reconstruirlos.

Checklist C: Después del incidente (haz que la recurrencia sea más difícil que el parche)

  1. Escribe un postmortem que nombre la falla de control. “Bug en el parche” no es causa raíz. La causa raíz es por qué llegó a demasiada producción.
  2. Añade una puerta automática. Comprobación de métricas canarias, detección de skew de versiones, validación de config, diff de dependencias, verificación de módulos kernel.
  3. Segmenta tu flota. Clases de hardware, imágenes OS y niveles de criticidad. Un rollout uniforme es un outage uniforme.
  4. Practica el rollback. Si el rollback solo se usa en emergencias, fallará en emergencias.
  5. Rastrea incidentes relacionados con actualizaciones como métrica de confiabilidad. No para castigar equipos. Para ver si tus controles funcionan.

Preguntas frecuentes (FAQ)

1) ¿Deberíamos retrasar todos los parches para evitar outages?

No. Retrasar parches intercambia riesgo de disponibilidad por riesgo de seguridad, y la cuenta llega después con intereses. Parchea, pero diseña el rollout para que un parche malo no pueda derribar todo.

2) ¿Cuándo es correcto hacer rollback?

Cuando el radio de impacto crece y no tienes un modo de fallo claro. El rollback compra tiempo y restaura servicio mientras investigas. Avanzar es para cuando entiendes la solución y puedes desplegarla con seguridad.

3) ¿Cuál es el mayor predictor de un corte por parche?

La concurrencia no controlada en rollout y comportamiento de reinicio. Entregar a demasiada parte de la flota a la vez convierte un “bug” en “incidente”. Entregar mientras también reinicias dependencias convierte “incidente” en “outage”.

4) ¿Cómo hacer canarios que importen?

Dales tráfico real y rutas de dependencia reales. Incluye comportamiento de cliente representativo (pilas TLS, DNS, proxies), no solo carga del servidor. Un canario debe poder fallar de la misma forma que la producción.

5) ¿Y el firmware de almacenamiento—debemos tratarlo distinto?

Sí. Los cambios de firmware pueden mover distribuciones de latencia sin “romper” nada. Trátalo como un cambio que afecta rendimiento: canary en el mismo modelo de hardware, vigila latencia de cola y ten un plan de rollback (o al menos un plan de “parar y aislar”).

6) ¿Por qué las actualizaciones suelen desencadenar tormentas de reintentos?

Porque reinicios y fallos parciales crean timeouts, y los timeouts disparan reintentos. Los reintentos amplifican la carga justo cuando la capacidad está reducida. Si no limitas reintentos y concurrencia, tu confiabilidad se vuelve función del peor comportamiento de cliente.

7) ¿Cómo evitamos roturas de esquema de config tras upgrades?

Valida config en CI usando la versión objetivo exacta y mantén compatibilidad backward/forward cuando sea posible. Durante rollouts, evita introducir config que solo la nueva versión entienda hasta que todos los nodos estén actualizados—or gatealo detrás de feature flags.

8) Ejecutamos Kubernetes. ¿Cuál es el patrón más seguro para parchear nodos?

Usa una imagen golden de nodo, actualiza por pequeños pools de nodos, cordon/drain con budgets de disrupción estrictos y mantén un dominio de fallo intacto hasta que el canario sea claramente exitoso. Evita mezclar actualizaciones de OS con cambios de CNI/runtime en la misma ventana.

9) ¿Cómo sabemos si es latencia de almacenamiento o latencia de app?

Mira iowait, device await/%util y errores del filesystem. Si iowait y await suben con timeouts en múltiples servicios, el almacenamiento suele ser el cuello de botella compartido. Si IO está limpio, sube en la pila: DNS, TLS, pools de conexión, CPU, locks.

10) ¿Qué métricas deben condicionar los rollouts?

Como mínimo: tasa de error, señales de saturación (CPU steal, IO await, profundidad de colas), latencia de cola (p95/p99) y salud de dependencias (latencia BD, ratio de aciertos de cache, tasa de éxito DNS). Gatea en deltas, no en números absolutos.

Próximos pasos que realmente reducen el radio de impacto

Si quieres menos cortes por actualizaciones, deja de tratar el parcheo como una tarea de fondo y empieza a tratarlo como un sistema de producción por derecho propio. El plano de control para actualizaciones—canarios, gates, rollbacks, segmentación, observabilidad—es lo que evita que un mal parche se convierta en un evento mundial.

Haz tres cosas esta semana:

  1. Haz cumplir límites de concurrencia en rollouts (por servicio, por AZ, por clase de hardware) y hazlos difíciles de omitir.
  2. Demuestra que el rollback funciona practicándolo en un servicio no crítico y documentando los pasos exactos del runbook.
  3. Añade una puerta rápida que vigile latencia de cola y salud de dependencias en los canarios antes de continuar.

No vas a prevenir cada parche malo. Puedes impedir que arrastre todo contigo. Ese es el trabajo.

← Anterior
Matemáticas de reconstrucción RAIDZ en ZFS: por qué una falla más puede ser fatal
Siguiente →
PostgreSQL vs SQLite: Confiabilidad frente a simplicidad — ¿Qué falla primero?

Deja un comentario