Querías actualizar una imagen base, rotar un certificado o migrar a un minor más reciente de Kubernetes. En su lugar recibiste un pager.
Los gráficos parecían bien la semana pasada. Ahora todo está “saludable” pero lento. Latencias con picos en sierra. Retransmisiones SYN en aumento.
Y tu “actualización simple” se convierte en un referéndum sobre la plataforma.
La rotación de sockets es uno de esos problemas que parece de red hasta que te das cuenta de que es un problema presupuestario de sistemas distribuidos:
descriptores de archivos, puertos efímeros, entradas NAT/conntrack, CPU para handshakes y tiempo esperando en colas que no sabías que existían.
Las plataformas se convierten en trampas de actualización cuando la nueva versión cambia la economía de las conexiones: defaults ligeramente distintos, nuevos sidecars, nuevas comprobaciones de salud,
comportamiento diferente del balanceador de carga, y tu sistema se precipita por un acantilado.
Qué es realmente la rotación de sockets (y por qué las actualizaciones la desencadenan)
La rotación de sockets es la tasa a la que tu sistema crea y destruye conexiones de red. En términos TCP: apertura de conexión (SYN/SYN-ACK/ACK),
handshakes TLS opcionales, fase de datos en estado estable y luego cierre (FIN/ACK) y una cola de operaciones administrativas (TIME_WAIT, envejecimiento de conntrack, mapeos NAT).
En sistemas de producción, “demasiado churn” tiene menos que ver con una sola conexión y más con la sobrecarga agregada de iniciarlas y terminarlas.
La trampa es que la rotación a menudo se mantiene por debajo del umbral, hasta que una actualización empuja un parámetro. Tal vez un sidecar proxy cambia la reutilización de conexiones.
Tal vez las comprobaciones de salud del balanceador se vuelven más agresivas. Tal vez una biblioteca cliente cambia valores predeterminados de keepalive.
Tal vez tu clúster ahora prefiere IPv6 y la ruta NAT es distinta. Cada cambio es justificable. Juntos, se multiplican.
Si extraes una lección de este artículo, que sea esta: el ciclo de vida de las conexiones es una dimensión de capacidad.
No solo el ancho de banda. Ni solo QPS. Ni solo CPU. Si tu hoja de ruta trata las conexiones como gratuitas, estás construyendo una trampa de actualización a propósito.
Una cita que vale la pena pegar en una nota adhesiva, porque aquí aplica dolorosamente bien:
“La esperanza no es una estrategia.” — Gene Kranz
Rotación de sockets vs. “la red está lenta”
La resolución tradicional de “la red está lenta” se centra en el throughput y la pérdida de paquetes. Los fallos por rotación de sockets suelen presentarse distinto:
conexiones de corta vida amplifican la latencia de cola, producen retransmisiones espigadas, sobrecargan conntrack y consumen CPU en kernel y librerías TLS.
Puedes tener ancho de banda de sobra y aún así estar caído, porque el cuello de botella es el trabajo por conexión y el estado por conexión.
Un sistema con baja rotación puede tolerar jitter. Uno con alta rotación convierte un pequeño jitter en una estampida:
los reintentos crean más conexiones; más conexiones aumentan las colas; las colas aumentan los timeouts; los timeouts aumentan reintentos. Ya conoces el ciclo.
Cómo las actualizaciones se convierten en trampas: los multiplicadores ocultos
Las actualizaciones cambian defaults. Los defaults cambian comportamiento. El comportamiento cambia la cardinalidad. La cardinalidad cambia el estado. El estado cambia la latencia.
Esa es toda la película.
Multiplicador #1: la reutilización de conexiones desaparece silenciosamente
HTTP/1.1 keepalive podría haber estado habilitado “en algún lugar” y deshabilitarse “en algún otro lugar”.
O un proxy comienza a cerrar conexiones inactivas con más agresividad. O una nueva biblioteca cliente pasa de
pools de conexión globales a pools por host/per-ruta con límites más pequeños.
La rotación resultante aparece como: más SYNs por petición, más sockets TIME_WAIT, más handshakes TLS
y mayor consumo de puertos efímeros —especialmente en gateways NAT o SNAT a nivel de nodo.
Multiplicador #2: los sidecars y meshes añaden intermediarios con estado
Un service mesh no es “solo latencia”. A menudo es un salto extra de conexión, política adicional de handshakes y comportamiento de buffering extra.
Si el mesh termina TLS, puede fomentar handshakes más frecuentes; si re-inicia conexiones upstream,
cambia el lugar donde se crean los sockets y, por tanto, dónde se consumen puertos efímeros y entradas conntrack.
A veces el mesh está perfectamente bien. La trampa aparece cuando actualizas el mesh y sus defaults cambian —timeouts de inactividad, circuit-breaking, reintentos.
No cambiaste tu app. Cambiaste cómo tu app habla con el mundo.
Multiplicador #3: las comprobaciones de salud se convierten en una fábrica de sockets
Una sola readiness probe por pod no suena a mucho hasta que la multiplicas por número de pods, nodos, clusters y balanceadores.
Si las sondas usan HTTP sin keepalive (o si el objetivo cierra), generan churn.
Las actualizaciones con frecuencia cambian el comportamiento de las sondas: intervalos más cortos, endpoints distintos, mayor paralelismo, más capas (ingress a servicio a pod).
A escala, “solo una sonda” se convierte en una denegación de servicio en segundo plano desde dentro de tu propia casa.
Multiplicador #4: NAT y conntrack se vuelven la verdadera “base de datos”
En plataformas con contenedores, los paquetes a menudo atraviesan NAT: pod-to-service, node-to-external, external-to-nodeport. NAT requiere estado.
Linux rastrea ese estado en conntrack. Cuando conntrack se llena, las conexiones nuevas fallan de formas que parecen aleatorias y crueles.
Las trampas de actualización ocurren cuando aumentas la tasa de conexiones aunque sea ligeramente: las entradas de conntrack viven lo bastante como para llenar la tabla.
Tu sistema ahora depende del plan de capacidad de una tabla hash del kernel. Felicidades, estás ejecutando un cortafuegos con estado como almacén clave-valor.
Multiplicador #5: cambios en TLS y políticas de criptografía convierten la CPU en cuello de botella
Cifrados más rápidos y la reanudación de sesiones ayudan, pero la palanca más grande sigue siendo: con qué frecuencia haces handshake.
Una actualización que aumente handshakes por 3× puede parecer “la CPU se volvió más lenta”, porque la ruta caliente cambió.
Esto es especialmente divertido cuando la actualización también habilita conjuntos de cifrado más estrictos o desactiva modos antiguos de reanudación.
Multiplicador #6: cambios “inocuos” en timeouts crean reconexiones masivas
Cambiar un timeout de inactividad de 120 segundos a 30 no solo modifica un número inactivo. Cambia la sincronización.
Los clientes ahora se reconectan más a menudo, y lo hacen en oleadas. Cuando toda tu flota obtiene el mismo default, creas tormentas periódicas.
Broma #1: Si alguna vez quieres ver “comportamiento emergente”, pon el mismo timeout en todos los clientes y míralos sincronizarse como metrónomos nerviosos.
Hechos y contexto: por qué esto sigue ocurriendo
Un poco de historia ayuda, porque la rotación de sockets no es nueva. Simplemente la redescubrimos con YAML más sofisticado.
- TIME_WAIT existe para proteger contra paquetes retrasados, no para arruinar tu día. Es una función de seguridad que se vuelve un límite de escalado bajo churn.
- Los puertos efímeros son finitos. En Linux, el rango efímero típico es de alrededor de 28k puertos; no es mucho con alta rotación detrás de NAT.
- Conntrack es con estado por diseño. La tabla debe rastrear flows para NAT y firewalling; puede convertirse en un tope duro para la tasa de conexiones.
- HTTP/2 redujo el conteo de conexiones multiplexando streams, pero introdujo modos de fallo distintos (head-of-line en TCP, comportamiento de proxies).
- Los balanceadores de carga tienen su propio seguimiento de conexiones. Incluso si tus servidores de aplicación están bien, un balanceador L4 puede quedarse sin recursos por flujo primero.
- Kubernetes popularizó sondas de liveness/readiness. Buena idea, pero normalizó peticiones de fondo frecuentes que a escala pueden convertirse en churn.
- Los service meshes revivieron la gestión de conexiones por salto. “Un proxy más” a veces merece la pena, pero cambia dónde residen los sockets.
- Los defaults de TCP keepalive son conservadores y a menudo irrelevantes para la inactividad a nivel de aplicación; la gente copia sysctls sin medir.
- Los reintentos son multiplicativos. Un único cambio en la política de reintentos puede duplicar o triplicar la tasa de conexiones bajo fallos parciales.
Modos de fallo: qué se rompe primero
La rotación de sockets no suele fallar como un “out of memory” limpio. Falla de lado.
Aquí están los puntos de ruptura comunes, más o menos en el orden en que tiendes a encontrarlos.
1) Agotamiento de puertos efímeros (generalmente en clientes o nodos NAT)
Síntomas: los clientes obtienen timeouts de conexión; los logs muestran “cannot assign requested address”; los gateways NAT descartan conexiones nuevas; los reintentos se disparan.
Puede que solo lo veas en un subconjunto de nodos porque el uso de puertos es local a una IP de origen.
2) Agotamiento de la tabla conntrack (generalmente en nodos, firewalls, gateways NAT)
Síntomas: caídas aleatorias de conexiones, nuevas conexiones fallan, logs del kernel sobre tabla conntrack llena, aumentan los drops de paquetes aunque la CPU esté bien.
Puedes “arreglarlo” temporalmente aumentando el tamaño de la tabla, que es como comprar un bote de basura más grande para una fuga.
3) Desbordamiento del backlog de escucha y presión en la cola de accept
Síntomas: retransmisiones SYN, los clientes ven timeouts intermitentes, el servidor parece infrautilizado, pero estás descartando en la cola de accept.
Común cuando aumentas la tasa de conexiones sin aumentar la capacidad de accept o sin afinar backlog.
4) Límites de descriptores de archivos y topes por proceso
Síntomas: “too many open files”, accepts que fallan misteriosamente o degradación al aproximarse a los límites.
El churn magnifica esto porque hay más sockets en estados transitorios.
5) Saturación de CPU en kernel + TLS + capas de proxy
Síntomas: sys CPU alto, aumento de cambios de contexto, CPU dominada por handshakes TLS, procesos proxy con picos.
La aplicación podría no ser el cuello de botella; es la tubería.
6) Efectos secundarios en almacenamiento y logging (sí, de verdad)
La alta rotación suele aumentar el volumen de logs (logs de conexión, logs de errores, reintentos). Eso puede retroceder en los discos, llenar volúmenes
y crear fallas secundarias. Aquí es donde el ingeniero de almacenamiento en mí carraspea.
Broma #2: Nada dice “sistema distribuido robusto” como tumbar producción porque los logs de errores de conexión llenaron el disco.
Guía rápida de diagnóstico
Cuando estás contra el reloj, no tienes tiempo para admirar la complejidad. Necesitas una secuencia que encuentre el cuello de botella rápido.
Este es el orden que uso en incidentes reales, porque separa “problema de tasa/estado de conexiones” de “problema de throughput” temprano.
Primero: prueba que es churn (no throughput)
- Comprueba la tasa de conexiones nuevas vs tasa de peticiones. Si las conexiones por petición se dispararon, encontraste el olor.
- Mira retransmisiones SYN/SYN-ACK. Los problemas de churn aparecen como inestabilidad en el handshake.
- Compara los conteos de TIME_WAIT y ESTABLISHED a lo largo del tiempo. Picos en TIME_WAIT indican churn; leaks elevan ESTABLISHED.
Segundo: localiza dónde se agota el estado
- En clientes/nodos NAT: uso de puertos efímeros, acumulación de TIME_WAIT, comportamiento SNAT.
- En nodos/firewalls: uso de conntrack y drops.
- En servidores: backlog de escucha, cola de accept, límites de fd.
Tercero: identifica el desencadenante introducido por la actualización
- Cambios de timeouts: timeouts inactivos, settings de keepalive, vidas de conexiones de proxy.
- Nuevas sondas: liveness/readiness, health checks de balanceador, scrapes de telemetría del mesh.
- Cambios en políticas de reintento: actualización de biblioteca cliente, reintentos por default en sidecar, ajustes de circuit breaker.
- Política TLS: nuevos cifrados por default, cambios en reanudación de sesión, cambios en la cadena de certificados.
Cuarto: estabiliza
- Reduce creación de conexiones: activa keepalive/pooling, reduce reintentos, aumenta la reutilización.
- Aumenta margen de estado: expande el rango efímero, aumenta tamaño de conntrack, límites de fd (como medida temporal, no permanente).
- Frena el churn de fondo: sondas y health checks.
Tareas prácticas: comandos, salidas, decisiones (12+)
Estas son tareas prácticas que puedes ejecutar en nodos Linux (bare metal o VM) e interpretar rápido.
Cada tarea incluye: un comando, salida de ejemplo, qué significa y qué decisión tomar.
Ajusta rutas y nombres de interfaces para tu entorno.
Tarea 1: Contar sockets por estado (servidor o cliente)
cr0x@server:~$ ss -ant | awk 'NR>1 {s[$1]++} END{for (k in s) printf "%s %d\n", k, s[k]}' | sort -k2 -n
ESTAB 842
TIME-WAIT 19234
SYN-RECV 12
FIN-WAIT-1 3
FIN-WAIT-2 7
Qué significa: TIME-WAIT que eclipsa ESTAB suele indicar conexiones de corta vida y churn. SYN-RECV sugiere presión en backlog/accept.
Decisión: Si TIME-WAIT es enorme y creciente, prioriza keepalive/pooling y revisa uso de puertos efímeros. Si SYN-RECV está elevado, revisa backlog y cola de accept.
Tarea 2: Identificar peers remotos que generan churn
cr0x@server:~$ ss -ant state time-wait | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head
15422 10.42.18.91
2981 10.42.22.14
1710 10.42.19.37
Qué significa: Uno o pocos peers generan la mayor parte del churn.
Decisión: Ve a esos clientes/sidecars e inspecciona sus keepalive, pooling y comportamiento de reintentos. Rara vez es un arreglo solo en el servidor.
Tarea 3: Comprobar el rango de puertos efímeros
cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
Qué significa: Ese rango ofrece ~28k puertos por IP de origen. Bajo NAT, eso podría ser todo tu presupuesto de concurrencia saliente.
Decisión: Si estás agotando puertos, expande el rango (con cuidado) y, más importante, reduce churn mediante reutilización.
Tarea 4: Detectar presión de puertos efímeros (lado cliente)
cr0x@server:~$ ss -ant sport ge 32768 sport le 60999 | wc -l
29844
Qué significa: Estás cerca del total del rango efímero; colisiones y fallos de connect se vuelven probables.
Decisión: Emergencia: reduce conexiones nuevas (throttle, deshabilita reintentos agresivos) y expande el rango de puertos. A largo plazo: keepalive/pooling y menos cuellos NAT.
Tarea 5: Buscar errores “cannot assign requested address”
cr0x@server:~$ sudo journalctl -k -n 50 | egrep -i "assign requested address|tcp:|conntrack"
Jan 13 08:41:22 node-17 kernel: TCP: request_sock_TCP: Possible SYN flooding on port 443. Sending cookies.
Jan 13 08:41:27 node-17 kernel: nf_conntrack: table full, dropping packet
Qué significa: SYN cookies indica presión en el backlog; drops de conntrack indican agotamiento de estado.
Decisión: Si conntrack está lleno, necesitas alivio inmediato: reduce conexiones nuevas, aumenta conntrack max y encuentra la fuente del churn. Si aparece SYN flooding con carga normal, ajusta backlog y capacidad de accept.
Tarea 6: Medir utilización de conntrack (nodo o gateway)
cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 247981
net.netfilter.nf_conntrack_max = 262144
Qué significa: Estás al ~95% de utilización. Bajo un pico, empezarás a droppear nuevos flows.
Decisión: Corto plazo: aumenta el max si la memoria lo permite. Solución real: reduce la creación de flows (reutiliza conexiones, reduce sondas/reintentos, ajusta timeouts).
Tarea 7: Revisar los mayores generadores en conntrack (requiere conntrack tool)
cr0x@server:~$ sudo conntrack -S
entries 247981
searched 0
found 0
new 98214
invalid 73
ignore 0
delete 97511
delete_list 97511
insert 98214
insert_failed 331
drop 1289
early_drop 0
icmp_error 0
expect_new 0
expect_create 0
expect_delete 0
search_restart 0
Qué significa: Alta tasa de “new” y non-zero insert_failed/drop indica que el churn excede la capacidad.
Decisión: Estabiliza reduciendo creación de conexiones inmediatamente; luego revisa dimensionamiento y timeouts de conntrack.
Tarea 8: Comprobar backlog de escucha y comportamiento de la cola de accept
cr0x@server:~$ ss -lnt sport = :443
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 4096 4096 0.0.0.0:443 0.0.0.0:*
Qué significa: Si Recv-Q crece hacia Send-Q bajo carga, los accepts están rezagados, el backlog se llena y los SYNs pueden retransmitir.
Decisión: Aumenta la capacidad de accept del servidor (workers/threads), afina backlog (somaxconn, tcp_max_syn_backlog) y reduce la tasa de conexiones (keepalive, pooling).
Tarea 9: Comprobar sysctls relacionados con backlog del kernel
cr0x@server:~$ sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_syncookies
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_syncookies = 1
Qué significa: Estos límites influyen en cuánto absorbes los picos de nuevas conexiones. Syncookies habilitados es bueno; verlos usados es una señal de aviso.
Decisión: Si estás droppeando con ráfagas de conexión, sube backlogs y arregla la fuente del churn. Afinar backlog no sustituye la reutilización.
Tarea 10: Ver la presión de TIME_WAIT y los timers
cr0x@server:~$ cat /proc/net/sockstat
sockets: used 28112
TCP: inuse 901
orphan 0
tw 19234
alloc 1234
mem 211
UDP: inuse 58
RAW: inuse 0
FRAG: inuse 0 memory 0
Qué significa: “tw” es TIME_WAIT. Valores altos se correlacionan con churn y presión de puertos efímeros (especialmente en clientes).
Decisión: Trata alto TIME_WAIT como un síntoma. Arregla reutilización de conexiones y comportamiento del cliente primero; los hacks de sysctl vienen después y con compensaciones.
Tarea 11: Comprobar límites de descriptores y uso
cr0x@server:~$ ulimit -n
1024
cr0x@server:~$ pidof nginx
2174
cr0x@server:~$ sudo ls /proc/2174/fd | wc -l
987
Qué significa: Estás cerca del tope por proceso. Bajo churn, los picos te llevarán al fallo y las fallas parecerán arbitrarias.
Decisión: Aumenta límites (unidad systemd, limits.conf), pero también reduce churn para no solo subir el techo sobre un incendio.
Tarea 12: Comprobar hotspots de CPU por handshakes TLS (señal rápida en proxy)
cr0x@server:~$ sudo perf top -p 2174 -n 5
Samples: 5K of event 'cycles', 4000 Hz, Event count (approx.): 123456789
Overhead Shared Object Symbol
18.21% libcrypto.so.3 [.] EVP_PKEY_verify
12.05% libssl.so.3 [.] tls13_change_cipher_state
8.44% nginx [.] ngx_http_ssl_handshake
Qué significa: Tu CPU está pagando el coste por conexión TLS. Esto empeora cuando sube el churn o la reanudación de sesión es inefectiva.
Decisión: Aumenta reutilización de conexiones, confirma la reanudación de sesión y considera offload/terminación solo después de detener el generador de churn.
Tarea 13: Validar comportamiento de keepalive desde el cliente
cr0x@server:~$ curl -s -o /dev/null -w "remote_ip=%{remote_ip} time_connect=%{time_connect} time_appconnect=%{time_appconnect} num_connects=%{num_connects}\n" https://api.internal.example
remote_ip=10.42.9.12 time_connect=0.003 time_appconnect=0.021 num_connects=1
Qué significa: Para una sola petición, num_connects=1 es normal. El truco es ejecutar ráfagas y ver si se reutilizan conexiones.
Decisión: Si llamadas repetidas siempre crean nuevas conexiones, arregla pools cliente, keepalive del proxy o comportamiento de cierre upstream.
Tarea 14: Observar retransmisiones y problemas TCP
cr0x@server:~$ netstat -s | egrep -i "retransmit|listen|SYNs to LISTEN"
1287 segments retransmitted
94 SYNs to LISTEN sockets ignored
Qué significa: Retransmisiones e SYNs ignorados indican estrés en el handshake —a menudo por desbordamiento de backlog o drops de conntrack/NAT.
Decisión: Correlaciona con SYN-RECV y métricas de backlog; reduce conexiones nuevas y afina backlog donde convenga.
Tarea 15: Probar que las sondas están creando churn
cr0x@server:~$ sudo tcpdump -ni any 'tcp dst port 8080 and (tcp[tcpflags] & tcp-syn != 0)' -c 10
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
08:44:01.112233 eth0 IP 10.42.18.91.51234 > 10.42.9.12.8080: Flags [S], seq 123456, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
08:44:01.112310 eth0 IP 10.42.18.91.51235 > 10.42.9.12.8080: Flags [S], seq 123457, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
Qué significa: SYNs frecuentes al puerto de la sonda sugieren que las sondas abren conexiones TCP nuevas en lugar de reutilizarlas.
Decisión: Reduce frecuencia de sondas, asegura keepalive cuando sea posible o cambia a exec probes para comprobaciones intra-pod cuando proceda.
Tres micro-historias corporativas desde las minas del churn
Micro-historia 1: El incidente causado por una suposición equivocada
Una empresa mediana migró de un despliegue en VM hecho a mano a Kubernetes. Hicieron lo responsable: rollout por fases,
SLOs cuidadosos y plan de rollback. La actualización fue “solo” mover el controlador de ingress a una versión mayor más reciente.
Incluso pasó las pruebas de carga. Por supuesto que sí.
La suposición equivocada: “HTTP keepalive está activado por defecto, así que los conteos de conexión no cambiarán”. En el antiguo ingress, el keepalive upstream a pods
se configuró explícitamente años atrás por un ingeniero que ya se había ido. En el nuevo ingress, el nombre de la clave de configuración cambió y sus valores en el chart
dejaron de aplicarse silenciosamente. Los clientes externos todavía mantenían keepalive hacia el ingress, así que los gráficos del lado cliente parecían normales.
Dentro del clúster, cada petición se convirtió en una nueva conexión TCP upstream desde el ingress a un pod. Eso desplazó el churn al SNAT de nodo,
porque las IPs de los pods se enrutaban mediante reglas iptables con estado conntrack. Las nuevas conexiones se dispararon. TIME_WAIT en los nodos de ingress se infló.
El conteo de conntrack fue subiendo hasta que alcanzó el máximo. Entonces empezó la diversión: 502s aleatorios y timeouts upstream, pero solo en ciertos nodos.
La respuesta al incidente inicialmente persiguió la latencia de la aplicación y consultas a la base de datos porque eso es a lo que todos están entrenados.
Hizo falta un ingeniero de red escéptico ejecutando ss en los nodos de ingress para notar conteos de TIME_WAIT que parecían una guía telefónica.
Restauraron keepalive upstream, redujeron la agresividad de las sondas temporalmente y el outage terminó rápido.
La conclusión que escribieron en su runbook interno fue directa: supón que el keepalive está desactivado hasta que demuestres lo contrario.
Y para actualizaciones: diff de la configuración efectiva, no solo valores de Helm. La plataforma hizo exactamente lo que le dijeron. Los humanos le dijeron lo incorrecto.
Micro-historia 2: La optimización que salió mal
Otra compañía quería rollouts más rápidos. Estaban hartos de drenar conexiones durante despliegues, así que acortaron los timeouts de inactividad
en sus proxies L7 internos. La idea era que las conexiones se liberarían rápido, los pods acabarían antes y los despliegues serían más ágiles.
Sonaba razonable en una presentación.
El contratiempo fue sutil. Muchos clientes internos usaban librerías que solo establecen conexión cuando es necesario y dependen del keepalive inactivo
para evitar pagar repetidamente el coste del handshake. Cuando el proxy empezó a cerrar conexiones con agresividad, los clientes respondieron reconectando frecuentemente.
Bajo tráfico estable, eso se tradujo en un flujo constante de nuevas conexiones en segundo plano. Bajo fallos parciales, ocurrieron tormentas de reconexión.
La CPU en los nodos proxy subió, mayormente en handshakes TLS. Los proxies hicieron autoscaling, lo que ayudó brevemente, pero también cambió la distribución de IPs origen.
Eso desencadenó otro cuello de botella: el gateway NAT externo empezó a ver mayor creación de flows por segundo. Conntrack en el gateway se llenó.
Las caídas se volvieron “aleatorias” porque distintas IPs de origen alcanzaban el agotamiento en distintos momentos.
La solución no fue heroica. Revirtieron el cambio de timeout y luego aceleraron despliegues reduciendo inteligentemente el tiempo de drenaje:
drenado de conexiones con presupuestos, endpoints de shutdown graceful y forzar que streams de larga duración migren antes en el rollout.
Aprendieron que “optimizar tiempo de despliegue matando conexiones inactivas” es como “optimizar tráfico quitando señales de stop”.
La lección: trata los timeouts inactivos como un parámetro de estabilidad, no como una conveniencia. Un timeout menor no significa menos trabajo.
A menudo significa el mismo trabajo, más frecuentemente y en los peores momentos.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Una compañía de servicios financieros tenía una política que molestaba a los desarrolladores: cada actualización de plataforma requería una revisión de “presupuesto de conexiones”.
No una revisión de rendimiento. Una revisión de presupuesto de conexiones. La gente se burlaba. En voz baja. A veces en voz alta.
Su equipo SRE monitoreaba tres ratios a lo largo del tiempo: conexiones por petición en el borde, conexiones por petición entre capas
y conexiones nuevas por segundo en nodos NAT. Lo almacenaban junto a las métricas habituales de latencia y errores. Cada cambio mayor —nuevo proxy,
nuevo mesh, nueva biblioteca cliente— tenía que mostrar que estos ratios no aumentaban.
Durante una actualización de clúster, notaron inmediatamente: conexiones nuevas por segundo desde un namespace se duplicaron,
pero la tasa de peticiones se mantuvo. Pausaron el rollout. Resultó que una actualización del runtime de un lenguaje cambió el comportamiento por defecto de DNS,
llevando a re-resoluciones más frecuentes y re-establecimiento de conexiones cuando los endpoints rotaban. La app aún “funcionaba”, pero hacía churn.
Porque lo detectaron temprano, la remediación fue pequeña: configurar el cache DNS del runtime y asegurar que los pools de conexión no estuvieran indexados demasiado finos.
Sin outage. Sin war room. Solo un ticket que se arregló antes de que importara.
La moraleja es aburrida y por eso cierta: traza las ratios correctas y las actualizaciones dejan de ser sorpresas.
No necesitas profecía. Necesitas un dashboard que trate las conexiones como un recurso de primera clase.
Decisiones de diseño que reducen el churn permanentemente
Puedes afinar sysctls hasta ponerte azul. Si la plataforma sigue creando conexiones nuevas como hábito, seguirás pagando.
Las soluciones duraderas son arquitectónicas y de comportamiento.
1) Prefiere multiplexación a nivel de protocolo cuando encaje
HTTP/2 u HTTP/3 pueden reducir el número de conexiones multiplexando streams. Pero no lo trates como una cura universal.
Si tus proxies degradan o si intermediarios terminan y re-originate conexiones, puede que no obtengas el beneficio end-to-end.
Aun así: cuando controlas cliente y servidor, la multiplexación es uno de los reductores de churn más limpios.
2) Haz pools de conexión explícitos y observables
La mayor parte del “churn misterioso” viene de pools invisibles: un tamaño por defecto de pool de 2 aquí, una clave por-host allá, un pool por respuesta DNS en otro sitio.
Haz que el dimensionado de pools sea un parámetro de configuración. Exporta métricas: conexiones activas, conexiones inactivas, adquisiciones pendientes y tasa de creación de conexiones.
3) Alinea timeouts entre capas, pero no los sincronices
Los timeouts deben ser consistentes: timeout inactivo del cliente < timeout inactivo del proxy < timeout inactivo del servidor es un patrón común.
Pero no los pongas idénticos en toda la flota. Añade jitter. Escalona rollouts. Evita tormentas periódicas de reconexión.
4) Trata los reintentos como un recurso presupuestado
Los reintentos no son “fiabilidad gratis”. Son multiplicadores de carga que crean más conexiones bajo fallos parciales.
Presupuesta reintentos por petición, usa hedging con cuidado y prefiere fallar rápido con backoff cuando el sistema está enfermo.
5) Evita saltos NAT innecesarios
Cada límite NAT es un cuello de botella stateful. Redúcelos cuando puedas: enrutamiento directo, menos cuellos de botella de egress, mejor topología.
Si debes hacer NAT, dimensiona conntrack y monitorízalo como monitorizas bases de datos. Porque funcionalmente es una base de datos.
6) Pon guardarraíles en sondas y health checks
Las sondas deberían ser baratas, cacheadas y lo más locales posible. Si tu sonda toca la pila completa de peticiones con auth, TLS y llamadas a BD,
construiste un amplificador de fallos. Usa endpoints ligeros separados. Evita intervalos de sondas que escalen linealmente con pods de forma incontrolable.
7) Modela el ciclo de vida de conexiones en la planificación de capacidad
La planificación de capacidad típicamente modela QPS y tamaños de payload. Añade a tus hojas de cálculo:
conexiones nuevas por segundo, vida media de conexión, impacto de TIME_WAIT, CPU por handshake TLS, estado conntrack por flow.
Si no puedes medir esto de forma fiable, eso es una señal de que tu plataforma ya es una trampa de actualización.
Errores comunes (síntoma → causa raíz → solución)
Esta sección es deliberadamente específica. El consejo genérico es barato; las fallas en producción no lo son.
1) Síntoma: timeouts sporádicos de connect() desde clientes
Causa raíz: agotamiento de puertos efímeros en nodos clientes o IPs SNAT; demasiados sockets en TIME_WAIT.
Solución: reduce creación de conexiones (keepalive, pooling), expande rango efímero, distribuye egress en más IPs origen y reduce tormentas de reintentos.
2) Síntoma: caídas aleatorias, “connection reset by peer”, 5xx en el borde
Causa raíz: tabla conntrack llena en nodos o gateways firewall/NAT; insert_failed/drop en aumento.
Solución: aumenta nf_conntrack_max como parche temporal, afina timeouts de conntrack para tu tráfico, reduce creación de flows y elimina límites NAT innecesarios.
3) Síntoma: retransmisiones SYN, picos en SYN-RECV, clientes ven demora en handshake
Causa raíz: desbordamiento de backlog de escucha o cola de accept que no drena (threads/ workers insuficientes); somaxconn demasiado bajo.
Solución: aumenta capacidad de accept del servidor, afina sysctls de backlog y reduce la tasa de nuevas conexiones mediante keepalive y reutilización.
4) Síntoma: picos de CPU durante ráfagas; nodos proxy escalan automáticamente
Causa raíz: mayor tasa de handshakes TLS por churn; reanudación de sesión ineficaz; timeouts inactivos demasiado agresivos.
Solución: restaura keepalive, asegura reanudación de sesión, evita timeouts demasiado cortos y verifica que proxies intermedios no estén forzando reconexiones.
5) Síntoma: solo algunos nodos fallan; “funciona en nodo A, falla en nodo B”
Causa raíz: agotamiento local de recursos (puertos, conntrack, límites fd) que varía por nodo debido a tráfico desigual o colocación de pods.
Solución: confirma el sesgo, reequilibra workloads, corrige límites por nodo y elimina el generador de churn en lugar de perseguir los nodos desafortunados.
6) Síntoma: actualizaciones exitosas en staging pero fallan en producción
Causa raíz: staging carece de la cardinalidad real de conexiones: menos clientes, menos capas NAT, menos sondas, menos tráfico de fondo, ajustes de balanceador distintos.
Solución: stagea con patrones realistas de conexión; replay de tasa de conexiones; monitorea conexiones-por-petición; canary con alarmas de presupuesto de conexiones.
7) Síntoma: discos se llenan durante un incidente de red
Causa raíz: amplificación de logs por errores de conexión/reintentos; logging verboso habilitado en incidente; sidecars escribiendo a gran ritmo.
Solución: limita la tasa de logs, muestrea errores ruidosos, mueve logs de alto volumen fuera de discos críticos y trata el logging como parte de ingeniería de fiabilidad.
Listas de verificación / plan paso a paso
Checklist: Antes de la actualización (evitar la trampa)
- Define un presupuesto de conexiones: conexiones nuevas/seg aceptables por capa, máximo TIME_WAIT por nodo, máxima utilización de conntrack.
- Haz snapshot de los defaults actuales: settings de keepalive, timeouts de proxy, políticas de reintento, intervalos de sondas, tamaños de conntrack.
- Diff de la configuración efectiva: archivos de config renderizados y args de procesos en ejecución, no solo valores Helm o plantillas IaC.
- Añade alertas canary sobre churn: conexiones nuevas/seg, retransmisiones SYN, utilización de conntrack, conteos TIME_WAIT.
- Ejecuta una prueba de carga enfocada en conexiones: no solo QPS; incluye concurrencia cliente realista y reintentos.
Checklist: Durante el rollout (detectar temprano)
- Canary en una porción: una AZ, un node pool o un namespace; mantiene la forma del tráfico comparable.
- Vigila ratios: conexiones-por-petición en cada salto; si sube, detén.
- Vigila tablas de estado: conntrack_count/max, TIME_WAIT, uso de fd en proxies.
- Correlaciona con cambios de política: reintentos, timeouts, frecuencia de sondas, settings TLS.
Checklist: Si ya estás en llamas (estabilizar primero)
- Detén la hemorragia: deshabilita o reduce reintentos que generan tormentas de conexiones; aumenta backoff.
- Reduce fuentes de churn: baja frecuencia de sondas; deshabilita temporalmente scrapes/telemetría no esenciales que abren conexiones nuevas.
- Aumenta holgura: sube conntrack max y límites de fd donde sea seguro; expande rango efímero si es necesario.
- Haz rollback selectivo: revierte el componente que cambió el comportamiento de conexiones (proxy/mesh/biblioteca cliente), no necesariamente toda la plataforma.
- Post-incidente: escribe una “prueba de regresión de conexiones” y añádela a los criterios de lanzamiento.
Preguntas frecuentes
1) ¿La rotación de sockets siempre es mala?
No. Algunos workloads son naturalmente de corta vida (llamadas estilo serverless, trabajadores bursty). El churn se vuelve malo cuando supera la capacidad
de componentes stateful: conntrack, puertos efímeros, colas backlog, CPU de TLS o descriptores de archivo. La meta es churn controlado, no churn cero.
2) ¿Por qué las actualizaciones desencadenan churn si no cambiamos código de aplicación?
Porque tu código de aplicación no es lo único que crea sockets. Proxies, sidecars, load balancers, sondas de salud y bibliotecas cliente
pueden cambiar defaults durante una actualización. Una “actualización de plataforma” suele ser una actualización de gestión de conexiones disfrazada.
3) ¿Simplemente aumentamos nf_conntrack_max y listo?
Auméntalo cuando debas para detener un incidente. Pero trátalo como un torniquete. Si no reduces la creación de flows, llenarás la tabla más grande también,
y podrías cambiar drops de conexión por presión de memoria y sobrecarga de CPU. Arregla la fuente del churn.
4) ¿TIME_WAIT es un bug que deberíamos desactivar?
No. TIME_WAIT protege contra paquetes retrasados que podrían corromper nuevas conexiones. Puedes afinar su impacto, pero “desactivar TIME_WAIT”
suele ser imposible o una mala idea. Reduce conexiones de corta vida en su lugar.
5) ¿Los keepalives siempre ayudan?
Los keepalives ayudan cuando permiten reutilización y reducen handshakes. Pueden perjudicar si mantienes demasiadas conexiones inactivas abiertas y agotas límites de fd
o memoria del servidor. La práctica correcta es pools con tamaño adecuado, timeouts inactivos sensatos y observabilidad del comportamiento del pool.
6) ¿Por qué esto aparece más en Kubernetes?
Kubernetes fomenta patrones que aumentan la cardinalidad de conexiones: muchos pods pequeños, sondas frecuentes, NAT de servicio y múltiples capas de proxy.
Nada de esto es inherentemente erróneo. En conjunto, convierten el estado de conexión en una dimensión primaria de escalado.
7) ¿Cómo saber si un service mesh es el culpable?
Mide tasas de creación de conexiones en los sidecars y compáralas con las tasas de petición. Si el mesh introduce nuevas conexiones upstream por petición,
o cambia comportamiento de timeouts inactivos, verás TIME_WAIT y aumento de CPU por handshakes en la capa de proxy primero.
8) ¿Cuál es la mejor métrica para alertar?
Si solo puedes elegir una: conexiones nuevas por segundo por nodo (o por instancia proxy) emparejada con tasa de peticiones.
Alerta sobre la deriva de la ratio. Los conteos absolutos varían; las ratios revelan regresiones.
9) ¿Realmente importa el almacenamiento en un incidente de rotación de sockets?
Sí. Los incidentes de churn suelen aumentar volumen de logs y telemetría de errores. Si los logs están en volúmenes limitados, puedes cascadas a disco lleno,
escrituras lentas y procesos bloqueados. Tu incidente de red se convierte en incidente de almacenamiento porque el sistema intenta decirte que está roto.
10) ¿Cómo prevenimos trampas de actualización a nivel organizacional?
Haz que el “comportamiento de conexiones” sea un criterio de lanzamiento: un presupuesto, dashboards y puertas canary. Exige que los equipos documenten cambios esperados en
keepalive, timeouts, reintentos, sondas y política TLS. Si no está escrito, se descubrirá a las 2 a.m.
Conclusión: siguientes pasos prácticos
La rotación de sockets no es un bug exótico. Es lo que ocurre cuando tratas las conexiones como gratuitas y las actualizaciones como eventos aislados.
Las plataformas se convierten en trampas de actualización cuando pequeños cambios por defecto se multiplican en agotamiento de estado a través de NAT, conntrack, proxies y kernels.
Siguientes pasos que puedes hacer esta semana:
- Añade un dashboard de conexiones: TIME_WAIT, ESTABLISHED, conexiones nuevas/seg, retransmisiones SYN, utilización de conntrack.
- Elige un servicio y mide conexiones-por-petición entre capas. Rastrea esto durante despliegues.
- Audita keepalive y timeouts inactivos en bibliotecas cliente, proxies y balanceadores. Hazlos configuración explícita, no folklore.
- Pon puertas a las actualizaciones con un presupuesto canary: si la ratio cambia, pausa el rollout.
- Arregla el peor generador de churn: suele ser un default de proxy, una sonda o una política de reintentos —no el kernel.
Si haces esos cinco, tu próxima actualización de plataforma todavía puede ser molesta. Pero no será una trampa. Volverá a ser una actualización, que es
la característica más subestimada en ingeniería de producción.