Aller au contenu

Fundamentaux de la répartition de charge

Équilibre de charge de paquets, réseau, serveur, L4 et L7

Ce document présente HAProxy à l’intention de tous ceux qui ne le connaissent pas, ainsi que de ceux qui souhaitent le redécouvrir après avoir utilisé des versions antérieures. Son objectif principal est de fournir à l’utilisateur toutes les informations nécessaires pour décider si HAProxy correspond ou non à ses besoins. Les utilisateurs avancés pourront y trouver certaines solutions à des idées qu’ils avaient eues, simplement parce qu’ils ignoraient une fonctionnalité récente. Des indications de dimensionnement sont également fournies, le cycle de vie du produit est expliqué, ainsi que des comparaisons avec des produits partiellement similaires.

Ce document ne fournit aucune aide ou indication de configuration, mais explique où trouver les documents pertinents. Le guide est présenté sous la forme d’une séquence plane de pages thématiques dans la barre latérale HAProxy.

La répartition de charge consiste à regrouper plusieurs composants afin d’obtenir une capacité de traitement totale supérieure à celle de chaque composant individuellement, sans intervention de l’utilisateur final et de manière évolutif. Cela permet d’exécuter simultanément un plus grand nombre d’opérations pendant le temps nécessaire à un composant pour exécuter une seule opération. Une opération unique reste toutefois traitée par un seul composant à la fois et n’est pas plus rapide qu’en l’absence de répartition de charge. Elle nécessite toujours au moins autant d’opérations que de composants disponibles, ainsi qu’un mécanisme de répartition de charge efficace pour tirer parti de tous les composants et bénéficier pleinement de la répartition de charge. Un bon exemple en est le nombre de voies sur une autoroute, qui permet à autant de véhicules de passer pendant le même intervalle de temps sans augmenter leur vitesse individuelle.

Exemples de répartition de charge :

  • Planification des processus dans les systèmes à plusieurs processeurs
  • Répartition de charge sur les liens (par exemple, EtherChannel, Bonding)
  • Répartition de charge par adresse IP (par exemple, ECMP, round-robin DNS)
  • Répartition de charge sur les serveurs (via des répartiteurs de charge)

Le mécanisme ou composant qui effectue l’opération de répartition de charge est appelé un répartiteur de charge. Dans les environnements web, ces composants sont désignés comme un « répartiteur de charge réseau », et plus couramment un « répartiteur de charge », étant donné que cette activité constitue de loin le cas le plus connu de répartition de charge.

Un répartiteur de charge peut agir :

  • au niveau du lien : il s’agit de la répartition de charge au niveau du lien, qui consiste à choisir le lien réseau par lequel envoyer un paquet ;

  • au niveau du réseau : cela s’appelle la répartition de charge réseau, et consiste à choisir le chemin suivi par une série de paquets ;

  • au niveau du serveur : il s’agit de la répartition de charge au niveau du serveur, qui consiste à déterminer quel serveur traitera une connexion ou une requête.

Deux technologies distinctes existent et répondent à des besoins différents, bien qu’elles se chevauchent parfois. Dans chaque cas, il est essentiel de garder à l’esprit que la répartition de charge consiste à détourner le trafic de son flux naturel, et qu’un minimum de précaution est toujours nécessaire pour maintenir le niveau requis de cohérence entre toutes les décisions de routage.

La première agit au niveau des paquets et les traite plus ou moins individuellement. La relation entre paquets d’entrée et de sortie est de 1 à 1 ; un analyseur réseau classique peut donc suivre le trafic de part et d’autre du répartiteur. Cette technologie peut être très peu coûteuse et extrêmement rapide. Elle est généralement implémentée dans le matériel (ASIC), ce qui permet d’atteindre le débit de ligne, par exemple avec des commutateurs ECMP. Habituellement sans état, elle peut aussi conserver un état en tenant compte de la session du paquet ; on parle alors de layer4-LB ou L4. Elle peut prendre en charge le DSR (retour direct du serveur sans repasser par le répartiteur) lorsque les paquets ne sont pas modifiés, mais n’offre presque aucune connaissance du contenu. Cette technologie convient particulièrement à la répartition de charge réseau, bien qu’elle serve parfois à une répartition serveur très simple à haut débit.

Le deuxième agit sur le contenu des sessions. Il nécessite que les flux d’entrée soient reconstitués et traités dans leur intégralité. Le contenu peut être modifié, et le flux de sortie est segmenté en nouveaux paquets. C’est pourquoi cette opération est généralement réalisée par des proxies, qui sont souvent appelés répartiteurs de charge au niveau 7 ou L7. Cela implique qu’il existe deux connexions distinctes de chaque côté, et qu’aucune relation n’existe entre les tailles ou les nombres de paquets d’entrée et de sortie. Les clients et les serveurs ne sont pas tenus d’utiliser le même protocole (par exemple IPv4 contre IPv6, clair contre SSL). Les opérations sont toujours étatiques, et le trafic de retour doit passer par le répartiteur de charge. Ce traitement supplémentaire comporte un coût, si bien qu’il n’est pas toujours possible d’atteindre un débit en ligne, notamment avec des paquets de petite taille. En revanche, il offre de nombreuses possibilités et est généralement réalisé par logiciel pur, même lorsqu’il est intégré à des appareils matériels. Cette technologie convient particulièrement bien à la répartition de charge des serveurs.

Les répartiteurs de charge basés sur les paquets sont généralement déployés en mode cut-through, ce qui signifie qu’ils sont installés sur le chemin normal du trafic et le redirigent selon la configuration. Le trafic de retour n’est pas nécessairement acheminé via le répartiteur de charge. Des modifications peuvent être apportées à l’adresse réseau de destination afin de diriger le trafic vers la destination appropriée. Dans ce cas, il est obligatoire que le trafic de retour passe par le répartiteur de charge. Si les routes ne permettent pas cette configuration, le répartiteur de charge peut également remplacer l’adresse source des paquets par la sienne afin de forcer le trafic de retour à passer par lui.

Les répartiteurs de charge basés sur un proxy sont déployés comme un serveur disposant de leurs propres adresses IP et ports, sans nécessiter de modification de l’architecture. Parfois, cela exige d’apporter certaines adaptations aux applications afin que les clients soient correctement redirigés vers l’adresse IP du répartiteur de charge et non directement vers celle du serveur. Certains répartiteurs de charge peuvent devoir ajuster certaines réponses des serveurs pour permettre cette redirection (par exemple, le champ d’en-tête HTTP Location utilisé dans les redirections HTTP). Certains répartiteurs de charge basés sur un proxy peuvent intercepter le trafic destiné à une adresse qu’ils ne possèdent pas, et masquer l’adresse du client lors de la connexion au serveur. Cela leur permet d’être déployés comme un routeur ou un pare-feu classique, en mode pass-through très similaire à celui des répartiteurs de charge basés sur les paquets. Cette caractéristique est particulièrement appréciée pour les produits qui combinent à la fois le mode paquet et le mode proxy. Dans ce cas, le DSR reste évidemment impossible, et le trafic de retour doit toujours être acheminé de retour vers le répartiteur de charge.

Une approche à plusieurs niveaux très évolutive consisterait à mettre en place un routeur frontal qui reçoit le trafic provenant de plusieurs liens répartis en charge, et qui utilise ECMP pour distribuer ce trafic à une première couche de plusieurs répartiteurs de charge basés sur les paquets et état ( L4 ). Ces répartiteurs de charge L4 transmettent ensuite le trafic à un nombre encore plus important de répartiteurs basés sur des proxies ( L7 ), qui doivent analyser le contenu pour déterminer quel serveur recevra finalement le trafic.

Le nombre de composants et de chemins possibles pour le trafic augmente le risque de défaillance ; dans des environnements très volumineux, il est même normal de voir en permanence quelques composants défaillants en cours de réparation ou de remplacement. La répartition de charge effectuée sans prise de conscience de l’état global de la pile dégrade significativement la disponibilité. Pour cette raison, tout répartiteur de charge raisonnable vérifiera que les composants auxquels il entend acheminer le trafic sont toujours actifs et accessibles, et il cesse de distribuer du trafic aux composants défaillants. Cela peut être réalisé à l’aide de diverses méthodes.

Le cas le plus courant consiste à envoyer périodiquement des sondes afin de vérifier que le composant reste opérationnel. Ces sondes sont appelées « contrôles d’état ». Elles doivent être représentatives du type de panne à détecter. Par exemple, un contrôle basé sur ping ne détectera pas qu’un serveur web a cessé de fonctionner et ne répond plus sur un port, tandis qu’une connexion au port permet de vérifier cela, et une requête plus avancée peut même valider que le serveur fonctionne toujours et que la base de données dont il dépend reste accessible. Les contrôles d’état comportent souvent quelques tentatives afin de compenser les erreurs de mesure occasionnelles. L’intervalle entre les contrôles doit être suffisamment court pour garantir que le composant défaillant n’est pas utilisé trop longtemps après la survenue d’une erreur.

D’autres méthodes échantillonnent le trafic de production envoyé à une destination pour vérifier son traitement et écarter les composants qui renvoient des réponses inappropriées. Cela sacrifie toutefois une partie du trafic de production, ce qui n’est pas toujours acceptable. Combiner ces deux mécanismes offre le meilleur de chaque approche : ils servent ensemble à détecter une panne, tandis que les contrôles d’état seuls en détectent la fin. Une dernière méthode repose sur un rapport centralisé : un agent de supervision met périodiquement à jour tous les répartiteurs avec l’état des composants. Chacun dispose ainsi d’une vue globale de l’infrastructure, parfois moins précise ou moins réactive. Cette approche convient surtout aux environnements comportant de nombreux répartiteurs et serveurs.

Les répartiteurs de charge au niveau 7 font également face à un autre défi connu sous le nom de persistance ou « sticky session ». Le principe consiste à orienter généralement plusieurs requêtes ou connexions successives provenant d’une même origine (par exemple, un utilisateur final) vers la même cible. L’exemple le plus connu est le panier d’achat sur un site marchand en ligne. Si chaque clic entraîne une nouvelle connexion, l’utilisateur doit toujours être acheminé vers le serveur qui détient son panier. La prise en compte du contenu facilite la détection d’éléments dans la requête afin d’identifier le serveur à qui la requête doit être acheminée, mais cela ne suffit pas toujours. Par exemple, si l’adresse source est utilisée comme clé pour sélectionner un serveur, on peut décider d’utiliser un algorithme basé sur un hachage, selon lequel une adresse IP donnée sera toujours acheminée vers le même serveur, en fonction d’une division de l’adresse par le nombre de serveurs disponibles. Mais si un serveur tombe en panne, le résultat change et tous les utilisateurs sont soudainement redirigés vers un autre serveur, ce qui fait perdre leur panier. La solution à ce problème consiste à mémoriser la cible choisie, de sorte que chaque fois qu’un même visiteur est détecté, il soit dirigé vers le même serveur, indépendamment du nombre de serveurs disponibles. Cette information peut être stockée en mémoire du répartiteur de charge, auquel cas elle doit éventuellement être répliquée sur d’autres répartiteurs de charge s’il n’est pas seul, ou stockée en mémoire du client à l’aide de diverses méthodes, à condition que le client soit capable de présenter cette information à chaque requête (insertion de cookie, redirection vers un sous-domaine, etc.). Ce mécanisme présente l’avantage supplémentaire de ne pas dépendre d’informations instables ou inégalement réparties (comme l’adresse IP source). C’est en réalité la raison la plus forte d’adopter un répartiteur de charge au niveau 7 plutôt qu’au niveau 4.

Afin d’extraire des informations telles qu’un cookie, un champ d’en-tête d’hôte, une URL ou tout autre élément, un répartiteur de charge peut devoir déchiffrer le trafic SSL/TLS, voire le réchiffrer lorsqu’il le transmet au serveur. Cette opération coûteuse explique pourquoi, dans certaines infrastructures à fort trafic, il peut y avoir un grand nombre de répartiteurs de charge.

Puisqu’un répartiteur de charge au niveau 7 peut effectuer un certain nombre d’opérations complexes sur le trafic (décrypter, analyser, modifier, comparer les cookies, décider quel serveur contacter, etc.), il peut effectivement causer des problèmes et est très fréquemment accusé à tort d’être à l’origine de nombreux dysfonctionnements qu’il n’a fait que révéler. Il arrive souvent que l’on découvre que les serveurs sont instables et passent périodiquement de l’état actif à l’état inactif, ou que, pour les serveurs web, ils renvoient des pages contenant des liens codés en dur qui obligent les clients à se connecter directement à un serveur spécifique sans passer par le répartiteur de charge, ou encore qu’ils mettent une éternité à répondre sous forte charge, provoquant des délais d’expiration. C’est pourquoi la journalisation constitue un aspect extrêmement important de la répartition de charge au niveau 7. Dès qu’un problème est signalé, il est essentiel de déterminer si le répartiteur de charge a pris une décision erronée et, le cas échéant, pourquoi, afin qu’il ne se reproduise plus.