Aller au contenu

Blog ·

Comment un agent choisit le bon serveur de bordure

DNS à plusieurs A, bordures de niveau 4 et PROXY protocol v2, avec les mesures : ce qu’un client fait de plusieurs adresses, et pourquoi revenir est difficile.

Par Provibr

Si vous faites tourner un agent sur le matériel de vos clients, l’adresse qu’il compose vit à deux endroits gênants : dans le DNS public, et dans le fichier d’identifiants que vous avez distribué à l’enrôlement. Les deux sont difficiles à changer ensuite, et l’un des deux indique à tout l’internet où se trouve votre plan de contrôle.

Nous avons placé une couche de serveurs de bordure devant le nôtre. Voici ce que cela a demandé, y compris les parties qui ne sont apparues qu’une fois de vrais agents passés par là.

Le problème, c’est l’adresse, pas le trafic

Le nom d’hôte de la passerelle se résolvait vers l’origine. Chaque agent enrôlé le connaissait, et quiconque savait taper dig aussi. Une inondation volumétrique visant cette adresse met le plan de contrôle à terre pour tout le monde, et il n’y a rien d’astucieux à faire après coup.

L’objectif était étroit, et l’énoncer garde la conception honnête : l’adresse de l’origine disparaît du chemin qu’empruntent les agents, si bien qu’une inondation frappe une machine remplaçable en dix minutes. Ce n’était explicitement pas un objectif que les bordures protègent le contenu. Le TLS mutuel le fait déjà de bout en bout, et une bordure capable d’inspecter le trafic serait une régression.

Niveau quatre, car terminer le TLS casserait ce qui compte

La conception évidente est un proxy inverse qui termine le TLS à la bordure. C’est la mauvaise, ici, pour deux raisons propres à ce genre de système.

La passerelle authentifie les agents sur leur certificat client, épinglé contre une empreinte conservée par agent. Terminer le TLS à la bordure y met fin à la poignée de main mutuelle : l’origine devrait alors faire confiance à ce que la bordure affirme. Et cela supposerait de copier la clé privée de la passerelle sur plusieurs machines virtuelles bon marché dont toute la raison d’être est d’être jetables.

Chaque bordure est donc un simple relais TCP. Elle voit du texte chiffré, ne détient aucun secret et n’exécute aucune application : une surface assez petite pour que « remplacez-la » réponde à la plupart des problèmes.

Cela coûte une chose. Chaque connexion semble désormais provenir de la bordure, et l’origine ne sait donc plus qui appelle.

PROXY protocol v2, et la différence entre une affirmation et une mesure

Le correctif standard est le PROXY protocol : le relais préfixe chaque connexion d’un petit en-tête binaire portant l’adresse d’origine. La passerelle l’analyse et remet la véritable adresse dans ses journaux.

La décision de conception qui mérite d’être copiée est de conserver deux valeurs plutôt qu’une.

  • L’adresse issue de l’en-tête est ce que l’autre bout dit être le client. C’est une affirmation.
  • L’adresse de la socket extérieure, la machine d’où les octets sont réellement venus, est quelque chose que nous avons mesuré nous-mêmes.

Un en-tête falsifié peut faire mentir la première. La seconde reste vraie. Les garder comme deux champs distincts permet de lire une ligne de journal des années plus tard sans se demander laquelle des deux on regarde, et rend facile la réponse honnête à « peut-on faire confiance à l’adresse IP » : rien n’en dépend, sinon une ligne de journal, car l’identité dépend du certificat du pair.

Deux notes de mise en œuvre qui nous ont épargné des ennuis :

Reniflez, n’exigez pas. Une migration vers le PROXY protocol n’est pas atomique. Si les premiers octets ressemblent à un ClientHello TLS plutôt qu’à la signature du protocole, remettez-les dans le flux et transmettez la connexion telle quelle. Déployer l’analyseur et l’activer sur les relais deviennent ainsi deux étapes indépendantes.

Tenez une table indexée sur la connexion interne. Une fois les octets arrivés dans une bibliothèque gRPC, l’adresse du pair est un port de bouclage, et l’agent comme la bordure sont devenus introuvables. Indexer le véritable pair sur le port source de la connexion interne, et l’effacer à la fermeture, est le seul endroit où cette information survit.

Un petit gain d’exploitation s’y cache aussi : une connexion qui s’ouvre et se referme sans rien envoyer ne produit aucune ligne de journal. Les contrôles de santé font cela en permanence, et ne plus les écrire a supprimé des dizaines de milliers de lignes inutiles par jour.

Découverte : l’ensemble A est la liste

Comment l’agent apprend-il qu’il existe plus d’une bordure ? Les options étaient des enregistrements TXT avec une charge structurée, des enregistrements SRV, ou simplement plusieurs enregistrements A sur le même nom.

Les enregistrements A multiples l’ont emporté grâce à un seul argument. L’ensemble A est déjà la liste des endroits où se connecter. Un enregistrement TXT serait une seconde source de vérité pour le même fait, et les deux peuvent se contredire. SRV ajouterait des poids dont nous n’avons pas besoin, puisque l’agent mesure par lui-même au lieu qu’on lui dise laquelle préférer. Le TTL est court : retirer une bordure est donc un changement DNS plus un TTL de patience.

Ce qu’un client fait réellement de plusieurs enregistrements A

Avant de construire la moindre logique de sélection, il valait la peine de mesurer ce que l’on obtient gratuitement. Un client HTTP moderne essaie les adresses de l’ensemble d’enregistrements et répartit son délai de connexion entre elles. Nous avons placé en premier une adresse connue comme morte, puis nous avons mesuré :

Comportement de l’adresse morteTemps pour joindre la vivante
Connexion refusée7 ms
Pas de route vers l’hôte7,5 ms
Abandon silencieux du SYN, délai de connexion de 25 s12,5 s
Abandon silencieux du SYN, délai de connexion de 10 s5,0 s

Le motif est assez clair pour que l’on conçoive en conséquence : les refus francs sont gratuits, et un trou noir coûte à peu près le délai de connexion divisé par le nombre d’adresses. Le basculement fonctionnait donc déjà sans aucune modification de l’agent, et le changement DNS a pu partir avant la logique de sélection. Cela veut dire aussi que votre délai de connexion est désormais fonction du nombre d’enregistrements que vous publiez, ce qui n’a rien d’évident tant que l’on n’a pas ce tableau sous les yeux.

Le basculement n’est pas la sélection

Le basculement gratuit retient l’adresse qui répond en premier, ce qui n’est pas la meilleure. Un agent à Amsterdam est resté des jours sur une bordure située dans un autre pays, parce que celle-là avait répondu la première et que la session a ensuite vécu une semaine.

L’agent mesure donc. Les détails qui ont compté :

Sondez avec une poignée de main TLS complète, pas avec une connexion TCP. Une bordure peut accepter une connexion et ne rien transmettre, ce qui est exactement la défaillance que la sonde doit attraper. Une connexion nue reçoit une réponse aussi enjouée d’un relais cassé que d’un relais qui fonctionne.

Donnez à chaque sonde son propre budget, en parallèle. Une bordure qui accepte puis se tait peut laisser une poignée de main pendante au-delà du délai de connexion : le budget par adresse se tient donc à côté de lui plutôt que de s’appuyer dessus.

Conservez tout le classement, pas seulement le vainqueur. L’adresse choisie et la liste ordonnée vont dans le fichier d’état de l’agent, accompagnées de l’ensemble d’adresses à partir duquel elles ont été mesurées. Cela rend un redémarrage bon marché, et cela veut dire qu’un basculement survit à un redémarrage au lieu de retourner joyeusement à l’adresse qui vient d’échouer.

Invalidez sur l’ensemble, pas sur le choix. Si une bordure est ajoutée ou retirée, la mesure est périmée même si l’adresse choisie s’y trouve encore.

Ne faites rien quand il n’y a rien à choisir. Avec moins de deux candidates, tout le mécanisme est sauté, ce qui est le cas en développement, où le nom se résout vers une seule adresse.

Et la règle qui gouverne tout cela : la sélection ne doit jamais devenir un nouveau mode de défaillance. Le DNS refuse, rien ne répond, le fichier d’état ne peut pas être écrit : chacun de ces chemins se termine par « aucun choix », et l’agent se connecte exactement comme il le faisait avant que tout ceci n’existe.

Revenir est plus difficile que basculer

Basculer est facile. La moitié difficile, c’est qu’ensuite l’agent reste sur la bordure la plus lente aussi longtemps que vit cette session, ce qui peut représenter des semaines.

Une surveillance tourne donc à côté de la session vivante et ne fait que deux choses. Elle essaie les adresses classées au-dessus de l’actuelle, avec la même poignée de main complète, et elle remesure l’ensemble quand la dernière mesure est ancienne. Les nombres, et la raison de chacun :

  • Un intervalle de cinq minutes avec gigue complète, pour que les sondes d’un parc ne se synchronisent pas. Toutes les minutes, cela coûte cinq fois plus et ne gagne rien, car l’hystérésis ci-dessous impose de toute façon deux tours à tout retour.
  • Une série de deux avant de rebasculer. Une seule sonde réussie, c’est une bordure qui vient de finir de redémarrer.
  • Deux seuils pour la remesure périodique : un seuil absolu de 20 ms et un seuil relatif de 25 %. Les deux doivent être atteints. Le seuil absolu seul approuverait un passage de 100 ms à 79 ms ; le seuil relatif seul approuverait 4 ms à 3 ms. Deux bordures situées dans le même centre de données diffèrent de moins d’une milliseconde, et sans cela elles échangeraient leurs places un jour sur deux.

Le changement est une reconnexion propre avec sa propre raison, placée à côté des autres fins de session prévues plutôt que modélisée comme une erreur. Cela compte plus qu’il n’y paraît : une erreur alimenterait l’échelle de temporisation exponentielle, si bien qu’un agent qui revient vers sa bordure préférée serait puni d’un délai croissant pour avoir bien agi. La file de messages sortants rejoue, et le magasin de déduplication garantit que rien n’est exécuté deux fois.

L’observer depuis l’autre côté

La plateforme surveille la même chose de son côté, et deux détails y méritent d’être volés.

Résolvez le nom comme le fait l’agent, par le résolveur du système plutôt que par une requête DNS directe. Une requête directe a purement et simplement échoué sur une machine dont le résolveur pointait vers elle-même, et surtout, c’est le résolveur du système qu’emprunte le trafic réel : il renvoie donc la même liste.

Prenez deux nombres sur une même socket : la connexion TCP, qui est l’aller-retour vers la bordure elle-même, et la poignée de main TLS complète, qui doit traverser le tunnel jusqu’à l’origine et revenir. La santé dépend du second. Un relais dont le serveur dorsal est mort répond parfaitement au premier.

La dernière leçon concerne le pare-feu. Docker contourne entièrement le pare-feu de l’hôte. Les règles voulant que seules les bordures atteignent les ports de la passerelle doivent vivre dans la chaîne que Docker consulte lui-même, sans quoi elles sont décoratives tout en ayant l’air actives. Et cette chaîne voit le trafic dans les deux sens : nos propres sondes sortantes, de l’origine vers les bordures, correspondaient au même port de destination et étaient rejetées, ce qui faisait déclarer par la surveillance chaque bordure hors service alors que chaque agent allait bien.

Ce que cela ne résout pas

Les bordures ne protègent pas le contenu, et il serait erroné de commencer à les traiter comme une frontière de sécurité. Une adresse qui était hors service pendant une mesure complète n’est redécouverte que par la suivante, une lacune connue et acceptée. Et le TTL du DNS est un plancher sur la vitesse à laquelle une bordure quitte la rotation, ce qui est le prix à payer pour laisser l’ensemble A être la liste.

C’est ainsi que l’agent atteint la plateforme chez Provibr, où il ne se connecte que vers l’extérieur et garde les identifiants sur l’hôte du client.

Commencez avec un hôte et un agent

Créez un compte, installez l’agent avec une seule commande et connectez votre premier hôte Proxmox. La période d’essai dure 14 jours et ne demande aucune coordonnée de paiement.