Créer votre propre Load Balancer avec .NET et YARP

Avant de foncer dans le vif du sujet, prenons d'abord le temps de voir ce que signifie YARP.

YARP (Yet Another Reverse Proxy) signifie littéralement : "Encore un autre Reverse Proxy", naming très drôle de la part des équipes de Microsoft. Mais alors qu'est-ce qu'un Reverse Proxy ?

Un reverse proxy est un peu comme un intermédiaire entre vous et un site web que vous visitez. Imaginez que vous voulez visiter un site web, mais au lieu d'y accéder directement, vous passez par un autre serveur (le reverse proxy) qui se charge d'aller chercher les informations pour vous et de vous les renvoyer.

Le reverse proxy peut également fournir d'autres services comme la mise en cache pour accélérer les temps de chargement, la sécurité en filtrant les requêtes malveillantes, ou encore la répartition de charge pour diriger le trafic vers plusieurs serveurs pour une meilleure performance, ce qu'on appelle plus communément le Load Balancing.

Sooo Let's Load The Balance...

Pour explorer ce concept avec YARP, j'ai créé trois projets différents, 2 serveurs web qui contienne chacune une api, on va considérer qu'on va faire le dispatching sur ces deux serveurs, et un projet web vide qui sera notre Load Balancer

ServerA sur localhost:7106 et ServerB sur localhost:7281 dispose tous les deux d'une API qui retourne des données aléatoires sur la météo, ainsi que un champ pour définir si la requête vient du serveur A ou du serveur B, on peut faire l'appel d'API directement sur Insomnia ou Postman pour tester

Serveur A sur https://localhost:7106

Serveur B sur https://localhost:7281

Maintenant il faudrait que notre Load Balancer se place au milieu et distribue les requêtes de l'utilisateur entre ces deux serveurs

Retournons sur notre projet Web ''LoadBalancer", installons YARP en utilisant le Gestionnaire de Packages Nuget

PM >NuGet\Install-Package Yarp.ReverseProxy -Version 2.1.0

Une fois YARP installé, il faudra qu'on fasse quelques configs au niveau du fichier Program.cs et AppSettings

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

app.MapReverseProxy();

app.Run();

Là on ajoute juste le service de YARP dans notre pipeline et on lui dit de prendre la configuration nécessaire à partir de l'AppSettings plus précisément dans la section ReverseProxy

Sans s'attarder allons insérer notre configuration dans le fichier AppSettings

AppSettings

Nous allons créer la configuration nécessaire pour diriger le trafic vers nos deux destinations différentes (ServerA et ServerB) en utilisant une répartition de charge en mode Round Robin let's go

"AllowedHosts": "*",
"ReverseProxy": {
  "Routes": {
    "route1": {
      "ClusterId": "cluster1",
      "Match": {
        "Path": "{**catch-all}"
      }
    }
  },
  "Clusters": {
    "cluster1": {
      "LoadBalancingPolicy": "RoundRobin",
      "Destinations": {
        "destination1": {
          "Address": "https://localhost:7106/WeatherForecast"
        },
        "destination2": {
          "Address": "https://localhost:7281/WeatherForecast"
        }
      }
    }
  }
}

En détails:

  1. "ReverseProxy": { : Cela indique que nous commençons à définir les paramètres pour le reverse proxy.

  2. "Routes": { : Dans cette section, nous définissons les routes que le reverse proxy va gérer. Une route est une URL spécifique ou un ensemble d'URLs qui sont dirigées vers un certain ensemble de destinations.

  3. "route1": { : C'est le nom de la route. Dans ce cas, la route est nommée "route1".

  4. "ClusterId": "cluster1", : Cette ligne indique que les requêtes entrantes pour cette route seront dirigées vers le cluster avec l'ID "cluster1".

  5. "Match": { "Path": "{**catch-all}" } : Cette ligne spécifie la condition pour cette route. Dans ce cas, la route correspond à tous les chemins d'URL (c'est-à-dire un "catch-all").

  6. "Clusters": { : Cette section définit les clusters, qui sont des groupes de serveurs pouvant traiter les requêtes dirigées par le reverse proxy.

  7. "cluster1": { : C'est le nom du cluster. Dans ce cas, il est nommé "cluster1".

  8. "LoadBalancingPolicy": "RoundRobin", : Cette ligne spécifie la politique d'équilibrage de charge utilisée pour distribuer les requêtes entre les serveurs du cluster. Dans ce cas, c'est une répartition en mode Round Robin, où chaque serveur reçoit une requête à tour de rôle.

  9. "Destinations": { ... } : Cette section définit les destinations disponibles dans ce cluster.

  10. "destination1": { "Address": "https://localhost:7106/WeatherForecast" }, : C'est la première destination dans le cluster (ServerA). Dans ce cas, elle pointe vers "https://localhost:7106/WeatherForecast".

  11. "destination2": { "Address": "https://localhost:7281/WeatherForecast" } : C'est la deuxième destination dans le cluster (ServerB), pointant vers "https://localhost:7281/WeatherForecast".

Comme annoncé plus haut, ce bloc de code configure un reverse proxy pour diriger le trafic vers deux destinations différentes en utilisant une répartition de charge en mode Round Robin.

Et c'est tout, on peut des a present lancer notre serveur Load Balancer et a chaque fois qu'on raffraichira la page on constatera que le Load Balancer distribue la requête entre le Serveur A et le Serveur B.

Mais nous sommes conscients que dans la vie réelle, nos serveurs ne seront pas aussi fiables que nous le souhaiterions. Même si vous pensez qu'ils le sont, il est toujours prudent d'ajouter une dose de résilience. Ainsi, nous allons mettre en place des vérifications de l'état de santé (health checks) sur notre Load Balancer. Ces vérifications permettront de surveiller régulièrement l'état de santé de nos serveurs. Si un serveur est jugé opérationnel, nous continuerons à diriger nos requêtes vers lui. En revanche, si un serveur présente un dysfonctionnement, nous l'éliminerons de la rotation des serveurs disponibles pour ne rediriger nos requêtes que vers ceux qui sont en bon état de fonctionnement. De plus, si un serveur défaillant récupère et redevient opérationnel, nous réintégrerons progressivement ses capacités de traitement des requêtes. Voyons maintenant comment mettre tout cela en œuvre.

En supposant que les HealthChecks sont déjà paramétrer sur nos deux serveurs, on peut rajouter la modification suivante dans notre AppSettings

"Clusters": {
  "cluster1": {
    "HealthCheck": {
      "Active": {
        "Enabled": "true",
        "Interval": "00:00:10",
        "Timeout": "00:00:10",
        "Policy": "ConsecutiveFailures",
        "Path": "/api/health"
      }
    },
    "Metadata": {
      "ConsecutiveFailuresHealthPolicy.Threshold": "3"
    },
    "LoadBalancingPolicy": "RoundRobin",
    "Destinations": {
      "destination1": {
        "Address": "https://localhost:7106/WeatherForecast"
      },
      "destination2": {
        "Address": "https://localhost:7281/WeatherForecast"
      }
    }
  }
}
  1. "HealthCheck": { ... } : Cette partie configure les vérifications de santé pour le cluster. Cela signifie que le reverse proxy vérifiera régulièrement la santé des serveurs dans ce cluster.

  2. "Active": { ... } : Ceci est une vérification de santé active, ce qui signifie que le reverse proxy enverra activement des requêtes aux serveurs pour vérifier leur santé.

  3. "Enabled": "true", : Cela indique que la vérification de santé active est activée.

  4. "Interval": "00:00:10", : Cela indique la fréquence à laquelle les vérifications de santé seront effectuées, dans ce cas toutes les 10 secondes.

  5. "Timeout": "00:00:10", : Cela indique le délai d'attente pour chaque vérification de santé, dans ce cas également de 10 secondes.

  6. "Policy": "ConsecutiveFailures", : Cela spécifie la politique de gestion des échecs consécutifs.

  7. "Path": "/api/health" : C'est le chemin de l'URL auquel le reverse proxy enverra les requêtes de vérification de santé, celui que vous aurez configurer sur votre serveur.

  8. "Metadata": { "ConsecutiveFailuresHealthPolicy.Threshold": "3" } : Cela spécifie le seuil d'échecs consécutifs au-delà duquel un serveur est considéré comme non sain.

Et on est bon, le Load Balancer n'enverra plus de requêtes à un serveur si celui-ci est un-healthy.

C'est tout pour cet aperçu rapide de YARP avec .NET. Je vous recommande vivement de consulter la documentation pour des implémentations bien plus avancées. Il est même possible de baser la configuration sur un fichier de code plutôt que sur les AppSettings, ce qui permet d'ajouter et de supprimer des serveurs de manière dynamique sans avoir besoin de recompiler et de redéployer votre projet.

Sur ce, je vous remercie ! ;-)

2
Subscribe to my newsletter

Read articles from Abdoul Aziz SALL directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abdoul Aziz SALL
Abdoul Aziz SALL