Tous les articles

Trois composants sains, un MASQUERADE cassé

Une histoire de debug : les NodePorts ne répondaient plus après une mise à jour de Netbird. La cause : une mark Netfilter partagée par trois composants.

Après la mise à jour de Netbird en v0.30+, l’accès aux NodePorts ne fonctionnait plus sur nos clusters OVH OKS. Rien ne plantait. Les pods étaient sains, les nœuds Ready, l’agent Netbird connecté. Pourtant, les paquets vers un NodePort ne menaient nulle part.

Première règle : qu’est-ce qui a changé ?

Le seul changement était la mise à jour de Netbird. Ça réduit la recherche mais n’explique rien : Netbird seul ne casse pas les NodePorts. Quelque chose interagissait avec lui.

Suivre le paquet

Quand tous les composants ont l’air sains, on arrête de lire des logs et on regarde le paquet. nftables sait le tracer à travers chaque hook et chaque chaîne :

nft add table inet debug
nft add chain inet debug trace '{ type filter hook prerouting priority -301; }'
nft add rule inet debug trace tcp dport 30080 meta nftrace set 1
nft monitor trace

(30080 n’est qu’un exemple de NodePort. La priorité -301 passe avant la table raw, on voit donc le paquet dès le début.)

La trace montre quelles règles touchent le paquet, dans quel ordre, et quelle mark il porte à chaque étape. C’est ainsi que le problème est passé de « les NodePorts sont cassés » à « ce paquet n’est pas masqueradé ».

Comment kube-proxy décide de masquerader

kube-proxy ne masquerade pas directement. Il positionne un bit dans la mark du paquet (KUBE-MARK-MASQ) et une chaîne ultérieure (KUBE-POSTROUTING) masquerade tout ce qui porte ce bit. C’est un contrat : ce bit veut dire « masquerade-moi ».

Les marks forment un espace partagé de 32 bits. Netbird et la pile réseau d’OKS utilisent aussi des bits de mark pour leurs besoins. Après la mise à jour, leurs bits chevauchaient celui dont kube-proxy dépend : la mark était posée ou modifiée par plusieurs composants, et MASQUERADE ne s’appliquait plus quand il le fallait.

Le correctif, en deux couches

  1. Contournement : une modification de règle validée sur un cluster réel, puis industrialisée en DaemonSet qui l’applique sur chaque nœud, pour qu’un redémarrage ou un nouveau nœud ne ramène pas le bug.
  2. Cause racine : une issue documentée chez Netbird, parce qu’un contournement qu’on maintient éternellement est une dette.

Ce que j’en retiens

  • Personne n’est propriétaire d’un bug qui vit entre trois composants. Quelqu’un doit l’écrire. Je l’ai aussi documenté dans notre wiki interne.
  • Tracer d’abord, théoriser ensuite. La trace donne un fait à discuter.
  • Un espace partagé (marks, ports, noms de tables, tables de routage) est l’endroit où des composants « sains » entrent en collision.
M'écrire Tous les articles