3 points par GN⁺ 2024-10-27 | 1 commentaires | Partager sur WhatsApp
  • L’exploitant d’OpenFreeMap a regroupé plusieurs VPS situés dans différentes régions dans les enregistrements A d’un même sous-domaine afin de mettre en place un DNS round-robin, puis a expérimenté pour voir quel serveur le navigateur et Cloudflare choisissaient réellement
  • Sans load balancer dédié, on peut espérer une répartition de charge et un contournement des pannes, mais les résultats réels dépendent fortement de la manière dont le client trie les adresses et retente les connexions
  • Lors d’un test avec 3 VPS aux États-Unis, en Europe et à Singapour, Chrome et Firefox avaient tendance à figer un serveur choisi aléatoirement au démarrage, tandis que Safari et curl convergeaient vers le serveur EU le plus proche après des requêtes répétées
  • Si certains serveurs étaient hors ligne, les navigateurs et curl basculaient rapidement vers un serveur de remplacement, mais les requêtes passant par le proxy Cloudflare continuaient d’utiliser l’origin défini par IP client, ce qui pouvait provoquer une erreur 521
  • Si Cloudflare ne choisit pas correctement un origin hors ligne ou le serveur à la latence la plus faible, une répartition fondée sur le DNS round-robin peut connecter les utilisateurs à un serveur lent, indépendamment de leur emplacement

L’idée de base du DNS round-robin

  • Un site web classique hébergé sur des VPS ajoute généralement un seul enregistrement A chez son fournisseur DNS pour envoyer le trafic vers une IP précise
  • Le DNS round-robin consiste à définir plusieurs IP de serveurs pour un même sous-domaine
    • L’exemple montre une configuration avec plusieurs enregistrements A pour rr-direct.hyperknot.com et rr-cf.hyperknot.com
  • Avec cette configuration, on peut espérer répartir la charge entre plusieurs serveurs et éviter les serveurs hors ligne
  • La plupart des fournisseurs DNS permettent de le configurer sans load balancer distinct, ce qui en fait une approche simple et presque gratuite
  • Les fonctions de load balancing de services comme Cloudflare peuvent représenter un coût important

Sur quels critères le client peut-il choisir un serveur ?

  • Les standards pertinents incluent RFC 8305 Happy Eyeballs et RFC 6724
  • La section sur le tri des adresses de la RFC 8305 explique que si un client avec état dispose d’un historique du temps de trajet aller-retour (RTT) attendu pour chaque chemin d’adresse, il devrait ajouter des règles de sélection d’adresse de destination privilégiant les adresses au RTT plus faible
  • L’auteur de l’expérience l’interprète comme le comportement suivant
    • vérifier si un serveur est en ligne ou hors ligne
    • trier les serveurs en ligne selon le temps de ping

Configuration de l’expérience

  • Création de VPS dans 3 régions du monde
    • États-Unis
    • Europe
    • Singapour
  • Sur Cloudflare, 3 enregistrements A proxifiés et 3 enregistrements A non proxifiés ont été configurés
  • Chaque serveur renvoyait la même structure de réponse via nginx
    • toutes les requêtes de chemin étaient réécrites vers color.png
    • /server renvoyait /etc/hostname en text/plain
  • color.png est un fichier PNG de 1 px, avec une couleur différente selon le serveur
    • US : vert
    • EU : bleu
    • SG : rouge
  • Les noms d’hôte étaient distingués par test-eu, test-us, test-sg
  • Comme le test était effectué depuis l’Europe, le comportement attendu était la sélection du serveur EU le plus proche
  • La page de test HTML remplit une grille 10x10 avec des images aléatoires afin de visualiser le résultat de la sélection des serveurs

Comportement des clients quand tous les serveurs sont en ligne

  • Chrome avait tendance à choisir un emplacement de façon assez aléatoire parmi plusieurs, puis à rester sur ce serveur une fois le choix effectué
    • il réévalue son choix quelques heures plus tard
    • dans le test, il est même resté bloqué plusieurs heures sur le serveur de Singapour, le plus lent
    • sans HTTP/2, il pouvait aussi choisir arbitrairement entre deux serveurs et former un motif
  • Firefox se comportait de manière similaire à Chrome
    • il choisit un emplacement aléatoire au démarrage
    • après redémarrage du navigateur, il peut sélectionner un autre emplacement aléatoire
  • Safari choisissait toujours correctement le serveur le plus proche
    • même si un serveur revenait après une brève indisponibilité, quelques rafraîchissements suffisaient pour retrouver le serveur EU
  • curl se corrigeait lui aussi vers le serveur le plus proche
    • ce n’est pas forcément le cas à la première exécution, mais en lançant la commande deux fois, il basculait toujours vers le serveur le plus proche
    • dans l’exemple, la première requête allait vers test-us, puis la suivante vers test-eu

Comportement via le proxy Cloudflare

  • Cloudflare choisissait un emplacement arbitraire selon l’IP client, puis continuait à utiliser ce même emplacement
  • Le comportement observé ressemblait à client_ip_hash modulo server_num
  • Depuis l’IP du domicile, Cloudflare se connectait au serveur américain quoi qu’on fasse
  • Avec un hotspot mobile, la connexion allait toujours vers le serveur EU
  • En exécutant la même commande curl depuis plusieurs VPS, chaque VPS se connectait à un emplacement arbitraire dans le monde, mais utilisait toujours le même serveur
    • dans l’exemple, le résultat était test-sg

Différences quand certains serveurs sont hors ligne

  • Le comportement a été vérifié après avoir arrêté nginx sur le serveur américain avec service nginx stop
  • Chrome, Firefox, Safari et curl détectaient tous le serveur hors ligne et choisissaient un autre serveur
  • Même si le serveur était coupé pendant le chargement, la connexion de secours se faisait rapidement, avec correction en moins d’une seconde
  • Cloudflare, lui, ne détectait pas le serveur hors ligne
    • il continuait à accéder au serveur choisi une première fois pour une IP client, qu’il soit en ligne ou non
    • si ce serveur était hors ligne, l’utilisateur obtenait une erreur
    • le résultat avec curl était error code: 521

Questions et limites autour de Cloudflare

  • Le fait que Cloudflare ne détecte pas un origin hors ligne est considéré comme probablement un bug du réseau
  • En s’appuyant sur la documentation Cloudflare concernant le zero downtime failover, l’auteur estime que le service devrait se comporter comme les navigateurs et curl
  • À minima, les serveurs hors ligne devraient être détectés
  • Idéalement, le service devrait pouvoir choisir, comme Safari, le serveur à la latence la plus faible
  • Dans le fonctionnement actuel, avec un serveur aux États-Unis et un autre en Nouvelle-Zélande, 50 % des utilisateurs américains pourraient recevoir une réponse depuis le serveur néo-zélandais
  • Les utilisateurs de Safari pourraient être plus lents avec Cloudflare que sans Cloudflare
  • Dans la discussion HN associée, le CEO et le CTO de Cloudflare ont répondu
  • L’auteur demande aussi s’il existe une plateforme serverless prenant en charge HTTPS et le DNS round-robin sans coût de VPS dans 3 régions du monde afin de poursuivre les expérimentations

1 commentaires

 
GN⁺ 2024-10-27
Avis sur Hacker News
  • Hmm, j’ai demandé à l’équipe DNS faisant autorité d’expliquer ce qui se passe ici.
    Si j’obtiens une réponse claire, j’en informerai HN. Ça fait des années que je n’ai pas regardé le code, et depuis, beaucoup de gens l’ont continuellement modifié :-)
    À vue de nez, comme l’auteur le mentionne dans son billet, cela semble lié à un comportement visant à préserver une affinité entre l’IP du client et le serveur backend. La question clé est : « si le serveur backend tombe, faut-il casser cette affinité ? » ; si j’en apprends davantage, je répondrai à mon propre commentaire.

    • Beaucoup de péchés ont été commis au nom de l’affinité de session.
    • Mise à jour : un changement est en cours de déploiement pour permettre le basculement sans interruption même sur les comptes gratuits.
  • L’une des premières solutions à ce problème était les enregistrements DNS SRV. C’est similaire aux enregistrements MX, mais l’idée était de l’appliquer à tous les services, pas seulement au courrier électronique.
    Avec les enregistrements MX et SRV, on peut indiquer la liste des serveurs que le client doit essayer ainsi que leur priorité, et SRV disposait aussi d’un paramètre weight pour la répartition de charge. Mais SRV a été défini pour n’être utilisé que lorsque la norme du protocole concerné précisait son usage, afin d’éviter la bataille politique consistant à intercepter pratiquement tous les protocoles standard pour forcer tous les clients à consulter SRV. Résultat : techniquement, les clients HTTP ne peuvent pas utiliser SRV. Plus tard, lors de la création de HTTP/2 et des standards HTTP qui ont suivi, des arguments inappropriés venus notamment de Google ont empêché de spécifier SRV pour le nouveau protocole HTTP. SRV est de fait mort pour les nouveaux développements, et semble n’être utilisé que dans quelques anciens standards.
    La nouvelle solution de répartition de charge semble être les enregistrements DNS HTTPS et SVCB. D’après ce que je comprends, ils ont été standardisés par des personnes qui voulaient mettre des paramètres supplémentaires dans le DNS afin de démarrer plus rapidement la poignée de main TLS 1.3 et de réduire le nombre d’allers-retours. Le type d’enregistrement SVCB est identique à HTTPS, mais sous une forme généralisée comme SRV. HTTPS et SVCB ont le paramètre de priorité de SRV et MX, mais pas le paramètre weight de SRV. Le standard a été publié et certains navigateurs semblent le prendre en charge, mais tout le monde ne l’a pas activé. Il faudra voir ce que les navigateurs feront réellement dans un avenir proche.

    • Un autre gros avantage des enregistrements HTTPS est qu’ils permettent une délégation correcte de type CNAME à l’apex d’un domaine.
      Ils n’imposent pas les bidouilles de CNAME flattening, qui peuvent créer des problèmes de routage pour les CDN utilisant GeoDNS avec anycast ou à sa place. Si vous avez déjà vu une plateforme recommander d’utiliser le sous-domaine www plutôt que le domaine apex, c’est pour cette raison ; et c’est aussi l’une des raisons pour lesquelles Akamai, qui utilise GeoDNS, a poussé la standardisation des enregistrements HTTPS.
    • J’aimerais vraiment voir des enregistrements de style SRV ou MX utilisables avec HTTP être correctement adoptés.
      Le fait que les gens veuillent souvent héberger leur site web à l’apex du domaine rend l’absence de tels enregistrements particulièrement douloureuse. Cela dit, si l’on ne peut pas s’appuyer sur DNSSEC, utiliser des enregistrements de style MX en toute sécurité peut être délicat.
  • La répartition de charge par DNS comporte des cas limites vraiment sales. J’ai eu à gérer une situation où le client HTTP/2 de Go utilisait du DNS round-robin, et cela a posé problème.
    Le client HTTP/2 de Go continue de réutiliser le premier serveur auquel il parvient à se connecter et ne résout pas de nouveau le DNS. À cause de cela, même si l’on ajoute un nouveau serveur au pool, le client peut ne jamais le découvrir.
    Le cas particulièrement pathologique est le suivant : tous les backends tombent, puis le premier backend remonte ; tous les clients se retrouvent collés à ce serveur et ne bougent plus. Même lorsque les autres serveurs reviennent, ils sont déjà connectés au premier, donc très peu de clients ouvrent de nouvelles connexions.
    Un problème similaire se produit aussi avec grpc-go. Le résolveur DNS de gRPC ne résout de nouveau que lorsque la connexion au backend est coupée. Les clients gRPC peuvent donc se concentrer sur un seul hôte puis y rester. Certains suggèrent de définir MAX_CONNECTION_AGE côté serveur afin de couper périodiquement les clients après un certain temps et de les forcer à refaire une résolution DNS.
    J’aimerais qu’il existe une meilleure solution standard pour la découverte de services. Au final, le mieux que l’on puisse faire semble être d’implémenter un répartiteur de charge par requête basé sur une IP virtuelle, avec des vérifications d’état effectuées par ce répartiteur. Mais cela ne fait que déplacer le problème vers le système qui implémente l’IP virtuelle. On suppose apparemment que le système de routage est relativement plus statique que les backends, et que le bénéfice vient de là.
    Je me demande comment cela se fait sur bare metal. Je sais qu’AWS/GCP, etc. proposent des répartiteurs de charge internes, mais je suis curieux de connaître l’astuce qui permet de les implémenter. Des recommandations de billets de blog ou de livres blancs sur le sujet seraient les bienvenues.

    • Je ne suis pas spécialiste DNS, mais ne devrait-on pas résoudre de nouveau une fois le TTL expiré ?
  • Il est écrit : « Que se passe-t-il si un serveur est hors ligne ? Disons que l’on arrête le serveur américain : service nginx stop », mais ce n’est pas comme ça qu’il faut tester.
    Le client verra un refus de connexion et passera à l’IP suivante. En pratique, toutefois, le serveur peut ne pas répondre du tout, ou accepter la connexion puis rester silencieux.
    Dans ce cas, on dépend du timeout côté client, et le DNS round-robin, censé améliorer la fiabilité, devient soudain beaucoup moins séduisant.

    • Exact. On peut tester en éteignant la machine physique ou la VM qui possède cette IP, ou en débranchant le câble.
      Arrêter le service est une opération planifiée ; dans ce cas, on peut d’abord mettre à jour le DNS pour gérer la situation.
    • SIG_STOP ou un DROP via ip/nftables constituent des tests beaucoup plus réalistes.
  • « Comme vous pouvez le voir, tous les clients le détectent correctement et choisissent un serveur de remplacement » est le cœur du problème. La fiabilité est déterminée côté client
    Par exemple, systemd-resolved a un temps renvoyé systématiquement l’adresse IP la plus basse, au motif que c’était le comportement le plus techniquement correct possible. Le raisonnement était que le DNS round-robin n’étant pas bien défini, renvoyer toujours l’IP la plus basse n’était pas faux. Cela a changé après le remue-ménage, mais, à ma connaissance, Debian 11 était lié à ce comportement, ou l’est resté longtemps.
    J’ai aussi vu beaucoup d’applications avec un comportement de nouvelle tentative médiocre, voire inexistant. Elles se comportent du genre : « une connexion a été refusée, annulons tout, quittons et ne réessayons jamais ». Résultat : 20 à 30 % de l’ensemble des requêtes partent en fumée.
    Si l’on n’a pas d’autre option, c’est une solution acceptable. Comme le dit l’article, avec un client HTTP de bonne qualité, comme un navigateur, configuré pour effectuer quelques nouvelles tentatives, le DNS round-robin peut convenir pour trouver un vrai répartiteur de charge doté de health checks, etc., et peut offrir un taux de réussite de 100 %.
    Mais le DNS round-robin n’est pas un répartiteur de charge, et un répartiteur de charge est préférable.

    • À l’inverse, si vous contrôlez les clients et pouvez garantir leur comportement, la répartition de charge par DNS est très efficace.
      Dans un ancien poste, nous avions un serveur DNS interne avec des centaines de millions d’enregistrements et un TTL de 60 secondes, utilisé par un système de routage interne sur mesure pour diriger les connexions entrantes des clients vers les bonnes ressources à l’intérieur du réseau. En pratique, c’était excellent. Les changements de routage étaient aussi simples qu’une mise à jour DDNS, et NOTIFY poussait les changements vers tous les serveurs secondaires, avec un délai moyen de propagation complète inférieur à 60 secondes. Cela facilitait la création d’outils plus complexes, et nous avions même un panneau de contrôle permettant de retirer du service, d’un simple bouton, aussi bien un serveur isolé qu’un datacenter entier.
      Ce système avait clairement ses aspérités, mais pour ce type de système il était rapide, facile à inspecter, et relativement blindé.
    • Vous confiez en quelque sorte la fiabilité aux clients, ou aux résolveurs DNS cache arbitraires qui se trouvent derrière eux.
      Il en va de même pour le basculement. Si une région tombe, voulez-vous que le trafic se répartisse uniformément vers d’autres régions, ou qu’il se concentre sur la région voisine la plus proche ? Si ce comportement compte, il faut garder le contrôle de la gestion du trafic, pas le déléguer à quelqu’un d’autre.
    • J’ai aussi du mal à adhérer à l’idée que « si l’on n’a pas d’autre option, c’est une solution acceptable ».
      De nos jours, il existe d’autres solutions à choisir bien avant d’en arriver à ce « pas d’autre option ».
  • Pour pinailler prudemment sur la notion de détection automatique de mise hors ligne par DNS dans la phrase « répartir la charge sur plusieurs serveurs et détecter automatiquement quels serveurs sont hors ligne afin de choisir des serveurs en ligne » : le DNS round-robin, dans son état de base, ne sert vraiment qu’à la répartition de charge.
    À moins d’ajouter une logique intelligente côté client, rien ne se produit automatiquement en matière de détection de l’état de disponibilité. L’introduction de l’article dit plus ou moins cela, mais j’ai dû la relire plusieurs fois pour en saisir le sens. Pour être juste, c’est peut-être mon problème de compréhension. En lisant le reste de l’article ensuite, tout portait bien sur cette logique intelligente.
    Si l’enregistrement serveur 1/N choisi par le navigateur est indisponible, il n’y a pas de récupération automatique ni de nouvelle tentative au niveau du protocole.
    Au passage, dans la catégorie « amusement connexe », ne pas oublier le DNS TTL de Java [1] ni le comportement de .equals() [2].
    [1] https://stackoverflow.com/questions/1256556/how-to-make-java...
    [2] https://news.ycombinator.com/item?id=21765788 (il y a 5 ans, 168 commentaires)

    • Route53 gère cela en retirant des réponses DNS les serveurs qui ne sont pas en bonne santé, et en servant toutes les réponses avec un TTL très faible.
      Il existe bien des clients qui ignorent le TTL, mais ils sont assez rares.
    • Pour faire un peu de promotion : c’est un projet libre et open source qui fournit du basculement pour le DNS round-robin, avec le soutien de NLnet : https://codeberg.org/FedericoCeratto/rrdnsd
  • Si un serveur tombe, des adresses IP distribuées et mises en cache dans le monde entier restent en circulation, et on ne peut pas empêcher les gens d’y accéder
    https://www.cloudflare.com/learning/dns/glossary/round-robin...

    • Il vaut la peine d’envisager de se passer de couches intermédiaires inutiles
      La répartition de charge a elle aussi un coût, et il arrive que les répartiteurs de charge cassent les connexions, de façon subtile ou flagrante. Chez un fournisseur, la disponibilité du répartiteur de charge a même été pire que celle de nos hôtes
      Si vous contrôlez les clients, il est raisonnable d’appeler l’API DNS de la plateforme pour récupérer la liste des IP, puis de les mélanger et de les parcourir correctement. C’est encore mieux si vous pouvez intégrer dans le binaire client quelques IP attribuées de façon stable, au cas où le DNS serait en panne. Mais le DNS n’est généralement pas cassé, et il est pratique pour effectuer des changements d’exploitation sans déployer une nouvelle configuration ou un nouveau binaire à chaque mise à jour du cluster
      Si le client est un navigateur, le comportement par défaut est plutôt correct. En général, les IP sont utilisées dans l’ordre, ce qui peut poser problème [1], mais pour le reste le comportement de nouvelle tentative est bon. En cas de refus de connexion, il essaie immédiatement une autre IP ; en cas de timeout, il essaie au moins quelques autres IP. Ce n’est pas idéal, donc j’utiliserais un répartiteur de charge pour les navigateurs, et si possible au moins pour le chargement initial de la page. Pour WebSocket, etc., on peut aussi utiliser le round-robin DNS avec une logique client JS un peu intelligente. Malgré tout, utiliser le round-robin DNS pour tout le site reste possible
      Si le client n’est pas un navigateur et que vous ne le contrôlez pas non plus, il ne reste plus qu’à croiser les doigts
      Je reconnais totalement qu’il faut parfois partir du principe que quelqu’un, en écrivant un résolveur DNS avec cache, interprète le champ TTL en jours plutôt qu’en secondes. Les clients derrière ce résolveur auront des problèmes lors d’une mise à jour DNS. Mais si un répartiteur de charge se trouve derrière un nom DNS et que le jour vient où il faut changer cette adresse, vous rencontrerez alors ce problème, et sans expérience préalable
      [1] L’un des RFC suggère que l’API de l’OS trie les réponses selon la correspondance de préfixe. Cela pourrait avoir du sens si les préfixes IP étaient hiérarchiques et constituaient un indicateur indirect du serveur le plus proche en distance réseau. Mais dans la réalité, des /24 numériquement adjacents ne sont souvent pas adjacents sur le réseau. Si les adresses de serveurs sont très dispersées, on peut voir le trafic de certaines IP clientes se concentrer vers des IP de serveurs numériquement proches
    • Les clients testés dans l’article se sont comportés correctement et ont choisi l’un des serveurs joignables
      Bien sûr, il y aura toujours quelqu’un avec un DNS local mal configuré ou un mauvais client. Il faut soit accepter la panne pour les personnes ayant une configuration cassée, soit réattribuer l’IP à un autre serveur du même datacenter
    • La pratique standard aujourd’hui consiste à utiliser un TTL relativement bas, avec un serveur DNS qui effectue des vérifications d’état sur les membres du pool
  • Bonjour. Je suis le CTO de Cloudflare. Nous avons déployé un changement sur tous les comptes gratuits de Cloudflare afin qu’ils aient le même comportement que les comptes payants
    Le problème mentionné ici a été corrigé, et le Zero Downtime Failover devrait fonctionner pour tous les types de comptes. Pourriez-vous refaire un test ?
    Merci d’avoir documenté cela. Je suis ravi que nous ayons pu modifier ce comportement pour tout le monde

    • J’ai retesté et cela fonctionne très bien
      Je mettrai aussi l’article à jour en conséquence. Merci de l’avoir appliqué aux comptes gratuits également, c’est un excellent résultat
  • La version remix sombre de cela, c’est le fast flux hosting, une technique utilisée par de nombreux fournisseurs de bulletproof hosting
    https://unit42.paloaltonetworks.com/fast-flux-101/

  • Il peut être utile de mentionner que le basculement sans interruption est une fonctionnalité Pro et plus
    Je me souviens qu’à l’époque où la documentation de protection des serveurs d’origine était divisée par niveau d’offre, c’était déjà documenté ainsi. Le comportement ou les nouvelles tentatives peuvent donc sembler différents