Churn des sockets : quand les plateformes deviennent des pièges de mise à niveau

Cet article vous a aidé ?

Vous vouliez mettre à jour une image de base, faire pivoter un certificat ou passer à une nouvelle version mineure de Kubernetes. Vous avez reçu un pager à la place.
Les graphiques semblaient corrects la semaine dernière. Maintenant tout est « sain » mais lent. Des dents de scie dans la latence. Les retransmissions SYN augmentent.
Et votre « mise à jour simple » se transforme en référendum sur la plateforme.

Le churn des sockets est un de ces problèmes qui ressemble au réseau jusqu’à ce que vous réalisiez que c’est un problème de budget de systèmes distribués :
des descripteurs de fichier, des ports éphémères, des entrées NAT/conntrack, du CPU pour les handshakes, et du temps passé dans des files d’attente dont vous ignoriez l’existence.
Les plateformes deviennent des pièges de mise à niveau quand la nouvelle version change l’économie des connexions — des paramètres par défaut légèrement différents, de nouveaux sidecars, de nouveaux contrôles de santé,
un comportement différent du load balancer — et votre système tombe d’un coup.

Ce qu’est vraiment le churn des sockets (et pourquoi les mises à jour le déclenchent)

Le churn des sockets est le taux auquel votre système crée et détruit des connexions réseau. En termes TCP : ouverture de connexion (SYN/SYN-ACK/ACK),
handshakes TLS optionnels, transfert de données en régime stable, puis fermeture (FIN/ACK) et une traîne de gestion (TIME_WAIT, vieillissement conntrack, mappages NAT).
Dans les systèmes de production, « trop de churn » concerne moins une connexion unique que le coût agrégé de les démarrer et de les terminer.

Le piège est que le churn reste souvent sous le seuil — jusqu’à ce qu’une mise à jour pousse un paramètre. Peut-être qu’un sidecar modifie la réutilisation des connexions.
Peut-être que les contrôles de santé du load balancer deviennent plus agressifs. Peut-être qu’une bibliothèque cliente change les keepalive par défaut.
Peut-être que votre cluster préfère désormais IPv6 et que le chemin NAT est différent. Chaque changement est défendable. Ensemble, ils se multiplient.

Si vous ne retenez qu’une seule leçon de cet article, retenez celle-ci : le cycle de vie des connexions est une dimension de capacité.
Ce n’est pas seulement la bande passante. Pas seulement le QPS. Pas seulement le CPU. Si votre feuille de route considère les connexions comme gratuites, vous construisez volontairement un piège de mise à niveau.

Une citation à garder sur un post-it, parce qu’elle s’applique cruellement bien ici :

« L’espoir n’est pas une stratégie. » — Gene Kranz

Churn des sockets vs « le réseau est lent »

Le dépannage traditionnel « le réseau est lent » se concentre sur le débit et la perte de paquets. Les défaillances liées au churn des sockets se manifestent souvent différemment :
des connexions de courte durée amplifient la latence en queue de distribution, produisent des retransmissions en pointes, surchargent conntrack, et brûlent du CPU dans le noyau et les bibliothèques TLS.
Vous pouvez avoir beaucoup de bande passante et être quand même en panne, car le goulot est le travail par connexion et l’état par connexion.

Un système avec peu de churn peut tolérer de la gigue. Un système avec beaucoup de churn transforme une petite gigue en un troupeau tonitruant :
les retries créent plus de connexions ; plus de connexions augmentent l’enqueueing ; l’enqueueing augmente les timeouts ; les timeouts augmentent les retries. Vous connaissez la boucle.

Comment les mises à jour deviennent des pièges : les multiplicateurs cachés

Les mises à jour changent les valeurs par défaut. Les valeurs par défaut changent le comportement. Le comportement change la cardinalité. La cardinalité change l’état. L’état change la latence.
Voilà tout le film.

Multiplicateur n°1 : la réutilisation des connexions disparaît discrètement

HTTP/1.1 keepalive pouvait être activé « quelque part » et se retrouve désactivé « ailleurs ».
Ou un proxy commence à fermer les connexions inactives plus agressivement. Ou une nouvelle bibliothèque cliente passe de
pools de connexions globaux à des pools par hôte/route avec des limites plus petites.

Le churn résultant se traduit par : plus de SYN par requête, plus de sockets TIME_WAIT, plus de handshakes TLS,
et une consommation accrue de ports éphémères — en particulier sur des passerelles NAT ou SNAT au niveau des nœuds.

Multiplicateur n°2 : les sidecars et mesh ajoutent des intermédiaires avec état

Un service mesh n’est pas « juste de la latence ». C’est souvent un saut de connexion supplémentaire, une politique de handshake supplémentaire, et un comportement de buffering supplémentaire.
Si le mesh termine le TLS, il peut encourager des handshakes plus fréquents ; s’il ré-initie les connexions en amont,
il change l’endroit où les sockets sont créés, et donc l’endroit où sont consommés les ports éphémères et les entrées conntrack.

Parfois le mesh fonctionne parfaitement. Le piège survient lorsque vous mettez à jour le mesh et que ses valeurs par défaut changent — timeouts inactifs, circuit-breaking, retries.
Vous n’avez pas changé votre appli. Mais vous avez changé la façon dont votre appli parle au monde.

Multiplicateur n°3 : les probes et checks deviennent une usine à sockets

Un seul readiness probe par pod ne paraît pas beaucoup jusqu’à ce que vous le multipliiez par le nombre de pods, de nœuds, de clusters et de load balancers.
Si les probes utilisent HTTP sans keepalive (ou si la cible ferme), elles créent du churn.

Les mises à jour changent fréquemment le comportement des probes : intervalles plus rapides, endpoints différents, plus de parallélisme, plus de couches (ingress vers service vers pod).
À grande échelle, « juste une probe » devient un déni de service en arrière-plan venant de l’intérieur de votre propre maison.

Multiplicateur n°4 : NAT et conntrack deviennent la vraie « base de données »

Dans les plateformes de conteneurs, les paquets traversent souvent du NAT : pod-to-service, node-to-external, external-to-nodeport. Le NAT nécessite un état.
Linux suit cet état dans conntrack. Quand conntrack est plein, les nouvelles connexions échouent de façons qui semblent aléatoires et cruelles.

Les pièges de mise à niveau surviennent quand vous augmentez le taux de connexion même légèrement : les entrées conntrack vivent assez longtemps pour remplir la table.
Votre système dépend maintenant du plan de capacité d’une table de hachage du noyau. Félicitations, vous faites tourner un pare-feu stateful comme un magasin clé-valeur.

Multiplicateur n°5 : les changements de politique TLS/crypto transforment le CPU en goulot

Des chiffrements plus rapides et la réutilisation de session aident, mais le levier le plus important reste : la fréquence des handshakes.
Une mise à jour qui augmente les handshakes par 3× peut donner l’impression que « le CPU est devenu plus lent », parce que le chemin chaud a changé.
C’est particulièrement amusant quand la mise à jour active aussi des suites de chiffrement plus strictes ou désactive d’anciens modes de réutilisation.

Multiplicateur n°6 : des timeouts « inoffensifs » créent des reconnexions massives

Changez un timeout inactif de 120 secondes à 30 et vous ne changez pas juste un nombre. Vous changez la synchronisation.
Les clients se reconnectent maintenant plus souvent, et ils se reconnectent en vagues. Quand toute votre flotte reçoit la même valeur par défaut, vous créez des tempêtes périodiques.

Blague n°1 : si vous voulez voir du « comportement émergent », mettez le même timeout sur tous les clients et regardez-les se synchroniser comme des métronômes nerveux.

Faits et contexte : pourquoi cela se répète

Un peu d’histoire aide, car le churn des sockets n’est pas nouveau. Nous le redécouvrons juste avec du YAML plus sophistiqué.

  • TIME_WAIT existe pour protéger contre les paquets tardifs, pas pour vous gâcher la journée. C’est une sécurité qui devient une limite d’échelle sous churn.
  • Les ports éphémères sont finis. Sur Linux, la plage éphémère par défaut tourne autour de 28k ports ; ce n’est pas beaucoup à fort churn derrière un NAT.
  • Conntrack est stateful par conception. La table doit suivre les flux pour le NAT et le firewalling ; elle peut devenir un plafond dur sur le taux de connexions.
  • HTTP/2 a réduit le nombre de connexions en multiplexant des streams, mais a introduit d’autres modes de défaillance (head-of-line blocking au niveau TCP, comportement des proxies).
  • Les load balancers ont leur propre tracking de connexion. Même si vos serveurs applicatifs sont OK, un L4 peut manquer de ressources par flux en premier.
  • Kubernetes a popularisé les liveness/readiness probes. Bonne idée, mais cela a normalisé des requêtes de fond fréquentes qui peuvent devenir du churn à grande échelle.
  • Les service meshes ont relancé la gestion par saut de connexion. L’ajout « d’un proxy de plus » vaut parfois le coût, mais change l’endroit où vivent les sockets.
  • Les valeurs par défaut TCP keepalive sont conservatrices et souvent sans rapport avec l’inactivité au niveau applicatif ; on cargo-cult des sysctls sans mesurer.
  • Les retries sont multiplicatifs. Un simple changement de politique de retry peut doubler ou tripler le taux de connexions lors d’un échec partiel.

Modes de défaillance : ce qui casse en premier

Le churn des sockets ne tombe pas habituellement comme un « out of memory » net. Il casse de côté.
Voici les points de rupture communs, à peu près dans l’ordre où vous les rencontrez.

1) Épuisement des ports éphémères (généralement sur clients ou nœuds NAT)

Symptômes : les clients obtiennent des timeouts de connexion ; les logs montrent « cannot assign requested address » ; les passerelles NAT refusent de nouvelles connexions ; les retries montent en flèche.
Vous pouvez ne le voir que sur un sous-ensemble de nœuds parce que l’utilisation des ports est locale à une IP source.

2) Saturation de la table conntrack (généralement sur nœuds, firewalls, passerelles NAT)

Symptômes : drops de connexion aléatoires, nouvelles connexions échouent, logs kernel indiquant table conntrack pleine, augmentation des pertes de paquets même si le CPU est correct.
Vous pouvez « corriger » temporairement en augmentant la taille de la table, ce qui revient à acheter une poubelle plus grande pour votre fuite.

3) Débordement du backlog d’écoute et pression sur la file d’accept

Symptômes : retransmissions SYN, clients voient des timeouts sporadiques, le serveur semble sous-utilisé, mais vous droppez à la file d’accept.
Commun quand vous augmentez le taux de connexions sans augmenter la capacité d’accept ou sans tuner le backlog.

4) Limites de descripteurs de fichiers et plafonds par processus

Symptômes : « too many open files », accepts qui échouent de manière mystérieuse, ou dégradation des performances à l’approche des limites.
Le churn amplifie cela parce que vous avez plus de sockets dans des états transitoires.

5) Saturation CPU dans le noyau + TLS + couches proxy

Symptômes : sys CPU élevé, commutations de contexte accrues, CPU dominé par les handshakes TLS, pics des processus proxy.
L’app peut ne pas être le goulot ; c’est la tuyauterie.

6) Effets secondaires sur le stockage et le logging (oui, vraiment)

Un churn élevé augmente souvent le volume des logs (logs de connexion, logs d’erreur, retries). Cela peut mettre la pression sur les disques, remplir les volumes,
et créer des pannes secondaires. C’est là que l’ingénieur stockage en moi se racle la gorge.

Blague n°2 : rien ne dit « système distribué robuste » comme mettre la production hors-service parce que vos logs d’erreur de connexion ont rempli le disque.

Guide de diagnostic rapide

Quand vous êtes chronométré, vous n’avez pas le temps d’admirer la complexité. Vous avez besoin d’une séquence qui trouve le goulot vite.
Voici l’ordre que j’utilise en incidents réels, parce qu’il sépare tôt le « problème de taux/état des connexions » du « problème de débit ».

Premièrement : prouver que c’est du churn (pas du débit)

  • Vérifiez le taux de nouvelles connexions vs le taux de requêtes. Si les connexions par requête ont bondi, vous avez trouvé l’odeur.
  • Regardez les retransmissions SYN/SYN-ACK. Les problèmes de churn apparaissent par l’instabilité des handshakes.
  • Comparez les compteurs TIME_WAIT et ESTABLISHED dans le temps. Les pics de churn font monter TIME_WAIT ; les fuites font monter ESTABLISHED.

Deuxièmement : localiser où l’état est épuisé

  • Sur les clients/nœuds NAT : utilisation des ports éphémères, accumulation de TIME_WAIT, comportement SNAT.
  • Sur les nœuds/firewalls : utilisation et drops de conntrack.
  • Sur les serveurs : backlog d’écoute, file d’accept, limites de fd.

Troisièmement : identifier le déclencheur introduit par la mise à jour

  • Changements de timeout : idle timeouts, keepalive, durées de connexion proxy.
  • Nouvelles probes : liveness/readiness, health checks du load balancer, scrapes de télémétrie mesh.
  • Changements de politique de retry : mise à jour de bibliothèque cliente, retries par défaut du sidecar, ajustements des circuit breakers.
  • Politique TLS : nouveaux choix de chiffrement, changement de réutilisation de session, modifications de chaîne de certificats.

Quatrièmement : stabiliser

  • Réduire la création de connexions : activer keepalive/pooling, réduire les retries, augmenter la réutilisation.
  • Augmenter la marge d’état : élargir la plage de ports éphémères, la taille de conntrack, les limites fd (mesure d’urgence, pas solution définitive).
  • Ralentir le churn de fond : probes et health checks.

Tâches pratiques : commandes, sorties, décisions (12+)

Voici des tâches pratiques à exécuter sur des nœuds Linux (bare metal ou VM) et à interpréter rapidement.
Chaque tâche inclut : une commande, une sortie d’exemple, ce que cela signifie, et la décision à prendre.
Adaptez les chemins et les noms d’interface à votre environnement.

Tâche 1 : Compter les sockets par état (serveur ou client)

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

Ce que cela signifie : un TIME-WAIT qui écrase ESTAB indique généralement des connexions de courte durée et du churn. SYN-RECV suggère une pression sur le backlog/accept.

Décision : Si TIME-WAIT est énorme et en hausse, priorisez keepalive/pooling et vérifiez l’utilisation des ports éphémères. Si SYN-RECV est élevé, vérifiez le backlog et la file d’accept.

Tâche 2 : Identifier les pairs distants qui créent le 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

Ce que cela signifie : un ou quelques pairs génèrent la majeure partie du churn.

Décision : Allez sur ces clients/sidecars et inspectez leur keepalive, pooling et comportement de retry. Rarement une correction côté serveur uniquement.

Tâche 3 : Vérifier la plage de ports éphémères

cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999

Ce que cela signifie : Cette plage fournit ~28k ports par IP source. En NAT, cela peut être votre budget total de concurrence sortante.

Décision : Si vous épuisez les ports, élargissez la plage (avec prudence) et, surtout, réduisez le churn via la réutilisation.

Tâche 4 : Détecter la pression sur les ports éphémères (côté client)

cr0x@server:~$ ss -ant sport ge 32768 sport le 60999 | wc -l
29844

Ce que cela signifie : Vous êtes proche de la plage éphémère totale ; les collisions et les échecs de connexion deviennent probables.

Décision : Urgence : réduire les nouvelles connexions (limiter, désactiver les retries agressifs) et élargir la plage de ports. À long terme : keepalive/pooling et moins de goulots NAT.

Tâche 5 : Vérifier les erreurs « 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

Ce que cela signifie : SYN cookies indique une pression sur le backlog ; les drops conntrack indiquent une saturation d’état.

Décision : Si conntrack est plein, soulagement immédiat : réduire les nouvelles connexions, augmenter le max de conntrack, et trouver la source du churn. Si SYN flooding apparaît sous charge normale, ajustez backlog et capacité d’accept.

Tâche 6 : Mesurer l’utilisation de conntrack (nœud ou passerelle)

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

Ce que cela signifie : Vous êtes à ~95% d’utilisation. Lors d’un pic, vous allez dropper de nouveaux flux.

Décision : À court terme : augmenter le max si la mémoire le permet. Correction réelle : réduire la création de flux (réutilisation, réduire probes/retries, ajuster timeouts).

Tâche 7 : Inspecter les principaux générateurs conntrack (outil conntrack requis)

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

Ce que cela signifie : Un taux élevé de « new » et des insert_failed/drop non nuls indiquent que le churn dépasse la capacité.

Décision : Stabilisez en réduisant immédiatement la création de connexions ; puis revisitez la taille et les timeouts de conntrack.

Tâche 8 : Vérifier le backlog d’écoute et le comportement de la file d’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:*

Ce que cela signifie : Si Recv-Q croît vers Send-Q sous charge, les accepts sont en retard, le backlog se remplit, et les SYN peuvent retransmettre.

Décision : Augmentez la capacité d’accept du serveur (workers/threads), tunez le backlog (somaxconn, tcp_max_syn_backlog), et réduisez le taux de connexions (keepalive, pooling).

Tâche 9 : Vérifier les sysctls liés au backlog du 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

Ce que cela signifie : Ces plafonds influencent votre capacité à absorber des rafales de nouvelles connexions. Syncookies activés est bon ; les voir utilisés est un signe d’alerte.

Décision : Si vous droppez lors de rafales de connexion, augmentez les backlogs et corrigez la source du churn. Le tuning du backlog ne remplace pas la réutilisation.

Tâche 10 : Voir la pression TIME_WAIT et les 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

Ce que cela signifie : « tw » est TIME_WAIT. Des valeurs élevées corrèlent fortement avec le churn et la pression sur les ports éphémères (surtout côté client).

Décision : Traitez un TIME_WAIT élevé comme un symptôme. Corrigez la réutilisation des connexions et le comportement client d’abord ; les hacks sysctl viennent plus tard et ont des compromis.

Tâche 11 : Vérifier les limites et l’usage des descripteurs de fichiers

cr0x@server:~$ ulimit -n
1024
cr0x@server:~$ pidof nginx
2174
cr0x@server:~$ sudo ls /proc/2174/fd | wc -l
987

Ce que cela signifie : Vous êtes proche du plafond de fd par processus. Sous churn, des pics vous feront dépasser et les échecs paraîtront arbitraires.

Décision : Augmentez les limites (unit systemd, security limits), mais réduisez aussi le churn pour ne pas juste augmenter le plafond sur un feu.

Tâche 12 : Vérifier les hotspots CPU des handshakes TLS (signal proxy rapide)

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

Ce que cela signifie : Votre CPU paye le coût TLS par connexion. Cela empire quand le churn augmente ou que la réutilisation de session est inefficace.

Décision : Augmentez la réutilisation des connexions, confirmez la réutilisation de session, et envisagez un offload/termination seulement après avoir stoppé le générateur de churn.

Tâche 13 : Valider le comportement keepalive côté client

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

Ce que cela signifie : Pour une seule requête, num_connects=1 est normal. L’astuce est de lancer une rafale et voir si les connexions sont réutilisées.

Décision : Si les appels répétés créent toujours de nouvelles connexions, corrigez les pools clients, le keepalive du proxy, ou le comportement de fermeture en amont.

Tâche 14 : Observer les retransmissions et problèmes TCP

cr0x@server:~$ netstat -s | egrep -i "retransmit|listen|SYNs to LISTEN"
    1287 segments retransmitted
    94 SYNs to LISTEN sockets ignored

Ce que cela signifie : Les retransmissions et SYN ignorés indiquent une pression sur les handshakes — souvent due à un débordement du backlog ou à des drops conntrack/NAT.

Décision : Corrélez avec SYN-RECV et métriques de backlog ; réduisez les nouvelles connexions et ajustez le backlog si nécessaire.

Tâche 15 : Prouver que les probes créent du 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

Ce que cela signifie : Des SYN fréquents vers le port de probe suggèrent que les probes ouvrent des connexions TCP fraîches au lieu de les réutiliser.

Décision : Réduisez la fréquence des probes, assurez le keepalive quand c’est possible, ou passez à des probes exec pour des checks intra-pod quand approprié.

Trois mini-récits d’entreprise issus des mines du churn

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une entreprise de taille moyenne est passée d’un déploiement VM bricolé à Kubernetes. Ils ont fait ce qu’il faut : rollout progressif,
SLOs prudents, et plan de rollback. La mise à jour était « juste » la migration du contrôleur d’ingress vers une nouvelle version majeure.
Elle avait même passé des tests de charge. Bien sûr.

La mauvaise hypothèse : « HTTP keepalive est activé par défaut, donc le nombre de connexions ne changera pas. » Sur l’ancien ingress, l’upstream keepalive vers les pods
était configuré explicitement il y a des années par un ingénieur parti depuis longtemps. Sur le nouvel ingress, le nom de la clé de configuration a changé et leurs valeurs de chart
ont cessé de s’appliquer silencieusement. Les clients externes gardaient encore le keepalive vers l’ingress, donc les graphiques côté client semblaient normaux.

À l’intérieur du cluster, chaque requête est devenue une nouvelle connexion TCP upstream de l’ingress vers un pod. Cela a déplacé le churn vers le SNAT des nœuds,
parce que les IP de pod étaient routées via des règles iptables avec état conntrack. Les nouvelles connexions ont explosé. TIME_WAIT sur les nœuds d’ingress a flambé.
Le compteur conntrack a monté jusqu’à atteindre le max. Puis l’amusement a commencé : 502 aléatoires et timeouts upstream, mais seulement sur certains nœuds.

L’équipe d’incident a d’abord chassé la latence applicative et les requêtes de base de données parce que c’est ce qu’on apprend à faire.
Il a fallu un ingénieur réseau sceptique lançant ss sur les nœuds d’ingress pour remarquer des comptes TIME_WAIT qui ressemblaient à un annuaire téléphonique.
Ils ont restauré l’upstream keepalive, réduit temporairement l’agressivité des probes, et la panne a pris fin rapidement.

La leçon inscrite dans leur runbook était brute : partir du principe que le keepalive est désactivé jusqu’à preuve du contraire.
Et pour les mises à jour : différez la configuration effective, pas seulement les valeurs Helm. La plateforme a fait exactement ce qu’on lui a demandé. Les humains lui ont dit la mauvaise chose.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une autre entreprise voulait des rollouts plus rapides. Ils en avaient assez d’attendre la vidange des connexions pendant les déploiements, alors ils ont écourté les timeouts inactifs
sur leurs proxies L7 internes. L’idée : les connexions se libéreraient rapidement, les pods termineraient plus vite, et les déploiements s’accéléreraient.
Cela sonnait raisonnable sur une diapositive.

Le revers a été subtil. Beaucoup de clients internes utilisaient des bibliothèques qui n’établissent une connexion que quand c’est nécessaire et s’appuient sur le keepalive inactif
pour éviter de payer le coût du handshake à répétition. Quand le proxy a commencé à fermer les connexions agressivement, les clients ont répondu en se reconnectant fréquemment.
Sous trafic stable, cela s’est traduit par un flux constant de nouvelles connexions en arrière-plan. Sous pannes partielles, des tempêtes de reconnexion se produisaient.

Le CPU sur les nœuds proxy a grimpé, surtout côté handshakes TLS. Le proxy a autoscalé, ce qui a aidé brièvement, mais a aussi changé la distribution des IP sources.
Cela a déclenché un autre goulot : la passerelle NAT externe a commencé à voir une création de flux par seconde plus élevée. Conntrack sur la passerelle a rempli.
Les pannes devenaient « aléatoires » parce que différentes IP sources atteignaient l’épuisement à des moments différents.

La correction n’a pas été héroïque. Ils ont reverti le changement de timeout, puis rendu les déploiements plus rapides en réduisant intelligemment le temps de drain :
vidange des connexions avec budgets, endpoints d’arrêt gracieux, et forcer les streams longue durée à migrer plus tôt dans le rollout.
Ils ont appris que « optimiser le temps de déploiement en tuant les connexions inactives » revient à « optimiser le trafic en retirant les panneaux stop ».

La leçon : considérez les timeouts inactifs comme un paramètre de stabilité, pas comme un confort. Un timeout plus bas ne signifie pas moins de travail.
Cela signifie souvent le même travail, plus fréquemment, et aux pires moments.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une société de services financiers avait une politique qui agaçait les développeurs : chaque mise à jour de plateforme nécessitait une revue du « budget de connexions ».
Pas une revue de perf. Une revue de budget de connexions. On s’en moquait. En silence. Parfois bruyamment.

Leur équipe SRE suivait trois ratios dans le temps : connexions par requête au bord, connexions par requête entre niveaux,
et nouvelles connexions par seconde sur les nœuds NAT. Ils stockaient cela aux côtés des métriques usuelles de latence et d’erreur. Chaque changement majeur — nouveau proxy,
nouveau mesh, nouvelle bibliothèque cliente — devait montrer que ces ratios n’avaient pas bondi.

Pendant une mise à jour de cluster, ils ont remarqué tout de suite : les nouvelles connexions par seconde depuis un namespace avaient doublé,
alors que le taux de requêtes restait stable. Ils ont mis le rollout en pause. Il s’est avéré qu’une mise à jour du runtime langage avait changé le comportement DNS par défaut,
conduisant à des résolutions plus fréquentes et à une re-création des connexions quand les endpoints tournaient. L’appli « fonctionnait » toujours, mais elle churnait.

Comme ils l’ont détecté tôt, la remédiation a été minime : configurer le caching DNS du runtime et s’assurer que les pools de connexions n’étaient pas indexés trop finement.
Pas de panne. Pas de war room. Juste un ticket réglé avant que cela ne devienne critique.

La morale est ennuyeuse et donc vraie : suirveillez les bons ratios et les mises à jour ne seront plus des surprises.
Vous n’avez pas besoin de prophétie. Vous avez besoin d’un tableau de bord qui considère les connexions comme une ressource de premier ordre.

Décisions de conception qui réduisent durablement le churn

Vous pouvez tuner des sysctls jusqu’à perdre haleine. Si la plateforme continue de créer de nouvelles connexions comme habitude, vous continuerez à payer.
Les corrections durables sont architecturales et comportementales.

1) Préférez le multiplexage au niveau protocole quand c’est approprié

HTTP/2 ou HTTP/3 peuvent réduire le nombre de connexions en multiplexant des streams. Mais ne considérez pas cela comme une panacée universelle.
Si vos proxies font du downgrade ou si des intermédiaires terminent et ré-originent les connexions, le bénéfice n’est pas garanti de bout en bout.
Cela dit : quand vous contrôlez client et serveur, le multiplexage est l’un des réducteurs de churn les plus propres.

2) Rendre les pools de connexions explicites et observables

La plupart du « churn mystère » vient de pools invisibles : une taille de pool par défaut de 2 ici, un pool par hôte là, un pool par réponse DNS ailleurs.
Rendez la taille des pools paramétrable. Exposez des métriques : connexions actives, connexions inactives, acquérements en attente, et taux de création de connexions.

3) Alignez les timeouts entre les couches, mais ne les synchronisez pas

Les timeouts doivent être cohérents : timeout client < timeout proxy < timeout serveur est un schéma courant.
Mais ne les mettez pas identiques sur toute la flotte. Ajoutez de la jitter. Échelonnez les rollouts. Évitez les tempêtes périodiques de reconnexion.

4) Traitez les retries comme une ressource budgétée

Les retries ne sont pas de la « fiabilité gratuite ». Ce sont des multiplicateurs de charge qui créent plus de connexions lors d’une défaillance partielle.
Budgétez les retries par requête, utilisez le hedging prudemment, et préférez l’échec rapide avec backoff quand le système est malade.

5) Évitez les sauts NAT inutiles

Chaque frontière NAT est un goulot d’état. Réduisez-les quand vous le pouvez : routage direct, moins de points d’étranglement d’egress, meilleure topologie.
Si vous devez NATer, dimensionnez conntrack et surveillez-le comme une base de données. Parce que fonctionnellement, c’en est une.

6) Mettez des garde-fous sur les probes et health checks

Les probes doivent être peu coûteuses, cachées et aussi locales que possible. Si votre probe frappe une pile de requête complète avec auth, TLS et appels BD,
vous avez construit un amplificateur de défaillance. Utilisez des endpoints légers séparés. Évitez des intervalles de probe qui se mettent à l’échelle linéairement avec le nombre de pods de façon ingérable.

7) Modélisez le cycle de vie des connexions dans la planification de capacité

La planification de capacité modèle typiquement le QPS et la taille des payloads. Ajoutez à vos feuilles de calcul :
nouvelles connexions par seconde, durée moyenne de connexion, impact TIME_WAIT, CPU par handshake TLS, état conntrack par flux.
Si vous ne pouvez pas mesurer tout cela de façon fiable, c’est un signe que votre plateforme est déjà un piège de mise à niveau.

Erreurs courantes (symptôme → cause racine → correction)

Cette section est volontairement précise. Les conseils génériques sont bon marché ; les pannes de production ne le sont pas.

1) Symptom : timeouts sporadiques de connect() depuis les clients

Cause racine : épuisement des ports éphémères sur les nœuds clients ou les IPs sources SNAT ; trop de sockets en TIME_WAIT.

Correction : réduire la création de connexions (keepalive, pooling), élargir la plage de ports éphémères, répartir l’egress sur plus d’IP sources, et réduire les tempêtes de retries.

2) Symptom : drops aléatoires, « connection reset by peer », 5xx en bordure

Cause racine : table conntrack pleine sur nœuds ou firewalls/passerelles NAT ; insert_failed/drop en hausse.

Correction : augmenter nf_conntrack_max en dépannage, ajuster les timeouts conntrack pour votre trafic, réduire la création de flux, et supprimer les frontières NAT inutiles.

3) Symptom : retransmissions SYN, pics de SYN-RECV, retard de handshake côté clients

Cause racine : débordement du backlog d’écoute ou file d’accept qui ne se vide pas (workers/threads serveurs insuffisants) ; somaxconn trop bas.

Correction : augmenter la capacité d’accept serveur, tuner les sysctls du backlog, et réduire le taux de nouvelles connexions via keepalive et réutilisation.

4) Symptom : pics CPU lors de rafales de trafic ; nœuds proxy autoscalent

Cause racine : augmentation du taux de handshakes TLS due au churn ; réutilisation de session inefficace ; timeouts inactifs trop agressifs.

Correction : restaurer keepalive, assurer la réutilisation de session, éviter des timeouts trop courts, et vérifier que des proxies intermédiaires ne forcent pas la reconnexion.

5) Symptom : seules certaines nodes échouent ; « marche sur le nœud A, échoue sur le nœud B »

Cause racine : épuisement local des ressources (ports, conntrack, limites fd) qui varie selon le nœud à cause d’un trafic ou d’un placement de pods inégal.

Correction : confirmer le skew, rééquilibrer les charges, corriger les limites par nœud, et éliminer le générateur de churn plutôt que de poursuivre les nœuds malchanceux.

6) Symptom : les mises à jour réussissent en staging mais échouent en production

Cause racine : staging manque de la cardinalité réelle des connexions : moins de clients, moins de couches NAT, moins de probes, moins de trafic de fond, paramètres de load balancer différents.

Correction : tester en staging avec des patterns de connexion réalistes ; rejouer le taux de connexion ; suivre les ratios connexions-par-requête ; canary avec alarmes de budget de connexions.

7) Symptom : disques pleins pendant un incident réseau

Cause racine : amplification des logs due aux erreurs de connexion/retries ; logging verbeux activé pendant l’incident ; sidecars qui écrivent beaucoup.

Correction : rate-limiter les logs, échantillonner les erreurs bruyantes, déplacer les logs volumineux hors des disques critiques, et traiter le logging comme partie de l’ingénierie de fiabilité.

Listes de contrôle / plan étape par étape

Checklist : Avant la mise à jour (éviter le piège)

  1. Définir un budget de connexions : nouvelles connexions/sec acceptables par niveau, max TIME_WAIT par nœud, utilisation maximale conntrack.
  2. Prendre un snapshot des valeurs courantes : keepalive, timeouts proxy, politiques de retry, intervalles de probes, tailles conntrack.
  3. Diff des configs effectives : fichiers rendus et arguments des processus en cours, pas seulement les valeurs Helm ou les templates IaC.
  4. Ajouter des alertes canary sur le churn : nouvelles connexions/sec, retransmissions SYN, utilisation conntrack, compte TIME_WAIT.
  5. Exécuter un test de charge axé sur les connexions : pas seulement le QPS ; inclure la concurrence cliente réaliste et les retries.

Checklist : Pendant le déploiement (détecter tôt)

  1. Canaryner une tranche : une AZ, un pool de nœuds, ou un namespace ; garder la forme du trafic comparable.
  2. Surveiller les ratios : connexions-par-requête à chaque saut ; si ça monte, stoppez.
  3. Surveiller les tables d’état : conntrack_count/max, TIME_WAIT, usage fd sur les proxies.
  4. Corréler avec les changements de politique : retries, timeouts, fréquence de probes, paramètres TLS.

Checklist : Si vous êtes déjà en feu (stabiliser d’abord)

  1. Arrêter l’hémorragie : désactiver ou réduire les retries qui créent des tempêtes de connexions ; augmenter le backoff.
  2. Réduire les sources de churn : diminuer la fréquence des probes ; désactiver temporairement les scrapes/télémétries non essentielles qui ouvrent de nouvelles connexions.
  3. Augmenter la marge : élever conntrack max et limites fd là où c’est sûr ; élargir la plage de ports éphémères si nécessaire.
  4. Rollback sélectif : revenir sur le composant qui a changé le comportement de connexion (proxy/mesh/bibliothèque cliente), pas forcément toute la plateforme.
  5. Post-incident : écrire un « test de régression de connexions » et l’ajouter aux critères de release.

FAQ

1) Le churn des sockets est-il toujours mauvais ?

Non. Certains workloads sont naturellement de courte durée (appels style serverless, workers bursty). Le churn devient problématique quand il dépasse la capacité
des composants stateful : conntrack, ports éphémères, files d’attente backlog, CPU TLS, ou descripteurs de fichiers. L’objectif est un churn contrôlé, pas zéro churn.

2) Pourquoi les mises à jour déclenchent-elles du churn si nous n’avons pas changé le code applicatif ?

Parce que votre code applicatif n’est pas la seule chose qui crée des sockets. Proxies, sidecars, load balancers, health checks et bibliothèques clientes
peuvent changer des valeurs par défaut lors des mises à jour. Une « mise à jour de plateforme » est souvent une mise à jour de gestion des connexions déguisée.

3) Ne suffit-il pas d’augmenter nf_conntrack_max et d’avancer ?

Augmentez-le quand c’est nécessaire pour stopper un incident. Mais considérez cela comme un pansement. Si vous ne réduisez pas la création de flux, vous remplirez
la table plus grande aussi, et vous pouvez échanger drops de connexion contre pression mémoire et surcharge CPU. Corrigez la source du churn.

4) TIME_WAIT est-ce un bug qu’on devrait désactiver ?

Non. TIME_WAIT protège contre les paquets retardés qui corrompraient de nouvelles connexions. Vous pouvez tuner autour de son impact, mais « désactiver TIME_WAIT »
est généralement impossible ou une mauvaise idée. Réduisez les connexions de courte durée plutôt.

5) Les keepalives aident-ils toujours ?

Les keepalives aident quand ils permettent la réutilisation et réduisent les handshakes. Ils peuvent nuire si vous gardez trop de connexions inactives ouvertes et épuisez
les limites fd ou la mémoire serveur. La bonne pratique : des pools dimensionnés correctement, des timeouts inactifs sensés, et l’observabilité du comportement des pools.

6) Pourquoi cela apparaît-il davantage dans Kubernetes ?

Kubernetes encourage des patterns qui augmentent la cardinalité des connexions : beaucoup de petits pods, probes fréquentes, service NAT, et plusieurs couches de proxy.
Rien de tout cela n’est intrinsèquement mauvais. Ensemble, ils font de l’état des connexions une dimension de scalabilité principale.

7) Comment savoir si un service mesh est en cause ?

Mesurez les taux de création de connexions sur les sidecars et comparez-les aux taux de requêtes. Si le mesh introduit de nouvelles connexions upstream par requête,
ou change les timeouts inactifs, vous verrez TIME_WAIT et le CPU de handshake augmenter d’abord sur la couche proxy.

8) Quelle est la meilleure métrique unique pour alerter ?

Si vous ne pouvez en choisir qu’une : nouvelles connexions par seconde par nœud (ou par instance de proxy) couplée au taux de requêtes.
Alertez sur la dérive du ratio. Les comptes absolus varient ; les ratios révèlent les régressions.

9) Le stockage peut-il vraiment importer dans un incident de churn ?

Oui. Les incidents de churn augmentent souvent le volume des logs et de la télémétrie d’erreur. Si les logs sont sur des volumes contraints, vous pouvez cascader vers un disque plein,
des écritures lentes, et des processus bloqués. Votre incident réseau devient un incident stockage parce que le système essaie de vous dire qu’il est cassé.

10) Comment prévenir organisationnellement les pièges de mise à niveau ?

Faites du « comportement de connexion » un critère de release : un budget, des tableaux de bord, et des portes canary. Exigez que les équipes documentent les changements attendus
en keepalive, timeouts, retries, probes et politique TLS. Si ce n’est pas écrit, vous le découvrirez à 2h du matin.

Conclusion : prochaines étapes pratiques

Le churn des sockets n’est pas un bug exotique. C’est ce qui arrive quand vous traitez les connexions comme gratuites et les mises à jour comme des événements isolés.
Les plateformes deviennent des pièges de mise à niveau quand de petits changements de valeurs par défaut se multiplient en épuisement d’état à travers NAT, conntrack, proxies et noyaux.

Prochaines étapes que vous pouvez faire cette semaine :

  1. Ajouter un tableau de bord de connexions : TIME_WAIT, ESTABLISHED, nouvelles connexions/sec, retransmissions SYN, utilisation conntrack.
  2. Choisir un service et mesurer les connexions-par-requête entre niveaux. Suivez-le au fil des déploiements.
  3. Auditer les keepalive et timeouts inactifs dans les bibliothèques clientes, les proxies et les load balancers. Rendre ces valeurs explicites en config, pas en folklore.
  4. Geler les mises à jour avec un budget canary : si le ratio change, pausez le rollout.
  5. Corriger le pire générateur de churn : c’est généralement un défaut de proxy, une probe ou une politique de retry — pas le noyau.

Si vous faites ces cinq choses, votre prochaine mise à jour de plateforme pourra toujours être pénible. Mais elle ne sera pas un piège. Ce sera de nouveau une mise à jour, ce qui est
la fonctionnalité la plus sous-estimée en ingénierie de production.

← Précédent
Ubuntu 24.04 : UFW + Docker — sécuriser les conteneurs sans casser Compose (cas n°40)
Suivant →
Compromis par un serveur de test : la défaillance classique en entreprise

Laisser un commentaire