Je l'ai découvert il y'a de ça quelques années dans un groupe dans lequel je travaillais, et qui faisait une large place à l'open-source et à Linux de manière générale au sein de leur système informatique, mais sans avoir l'occasion de le pratiquer vraiment de manière intensive,
C'était juste une démonstration de l'outil, de ses capacités pour de répartition de charge pour des connexions web en mode round-robin.
Les capacités de l'outil vont au delà du simple équilibrage de charge sur du web : Reverse proxy http/s, failover et j'en passe, l'outil est vraiment complet dans sa version communautaire et permet déjà de faire énormément de choses.
HAProxy créé des frontend (des ports sur lesquels les clients vont pouvoir se connecter) et les rediriger vers un backend (un ou plusieurs serveurs qui fournissent un même service). Dans sa configuration, il est possible de procéder à des vérifications qui permettront de savoir pour chaque serveur d'un backend si le port qui est censé répondre répond, afin de pouvoir valider que celui-ci est en mesure de recevoir des connexions et de ne pas rediriger une connexion dessus si son état de santé n'est pas bon.
Dans un backend, le poids de chaque serveur permet de définir la manière dont seront équilibrées les connexions. Ce poids prend la mesure d'un nombre et fonctionne en prenant en compte l'ensemble des poids des machines d'un backend.
Par exemple, si mon backend est constitué de 2 machines qui ont toutes deux un poids de 100, HAProxy enverra une connexion sur le premier, la suivante sur le second, la troisième sur le premier, la quatrième sur le second, etc.
Le calcul est fait de la manière suivante :
Poids de la machine / Somme des poids du backend * 100
Dans notre exemple cela donne 100/200 * 100 = 50.
La machine 1 prendra 50% des connexions, la machine 2 50%.
Si notre backend comportait 4 machines au lieu de 2, le poids serait de :
100/400 * 100 = 25%
Chaque machine prendrait donc 1 connexion sur 4.
Dans la configuration d'HAProxy, un backend ressemble à ça :
backend Back_web
mode http
balance roundrobin
server serverapp1 192.168.251.99:80 check weight 100
server serverapp2 192.168.251.112:80 check weight 100
server serverapp3 192.168.251.113:80 check weight 100
server serverapp4 192.168.251.114:80 check weight 100
Et dans un monde parfait, la charge est parfaitement équilibrée et aucun serveur ne se retrouve en surcharge par rapport à un autre.
Notre monde est rempli d'inégalités.
Vous équilibrez vos connexions dans votre backend, mais sur un des serveurs, une requête SQL lancée par Martin, de la compta (c'est toujours la compta qui pose problème), est en train de mettre une de vos machines sur les genoux.
Votre CPU grimpe, la RAM également, votre machine souffre pendant que le restant du backend vit sa vie tranquillement.
Et dans ce monde imparfait, votre vérification d'état de santé venant d'HAProxy vers ce serveur mis à mal réponds toujours comme si de rien n'était. Le processeur brûle peut-être, mais votre service n'est pas KO pour autant et l'utilisateur qui sera connecté sur cette machine aura des temps de réponse infects et n'arrivera pas à travailler normalement.
Mais heureusement, il y a une solution : le check agent.
Un check agent permet d'aller interroger une machine sur un port TCP en écoute et d'avoir en guise de réponse de la machine un pourcentage correspondant au poids que HAProxy attribuera comme nouveau poids à cette machine.
Si l'on reprend la configuration du dessus, pour rajouter un check agent, c'est simple :
backend agent_test
mode http
balance roundrobin
server serverapp1 192.168.251.99:80 check weight 100 agent-check agent-addr 192.168.251.99 agent-port 9999 agent-inter 5s agent-send ping\n
server serverapp2 192.168.251.112:80 check weight 100 agent-check agent-addr 192.168.251.112 agent-port 9999 agent-inter 5s agent-send ping\n
server serverapp3 192.168.251.113:80 check weight 100 agent-check agent-addr 192.168.251.113 agent-port 9999 agent-inter 5s agent-send ping\n
server serverapp4 192.168.251.114:80 check weight 100 agent-check agent-addr 192.168.251.114 agent-port 9999 agent-inter 5s agent-send ping\n
Dans ce cas ci-dessus, notre HAProxy va interroger chaque machine toutes les 5 secondes sur le port 9999. L'agent va calculer un poids selon un pourcentage d'utilisation de CPU et RAM afin d'arriver au fonctionnement suivant : Plus la machine est sollicitée, moins son nouveau poids sera élevé et elle doit recevoir moins de nouvelles connexions.
Le poids de 100 est un poids de départ mais changera automatiquement une fois un retour obtenu d'un check-agent.
Et un agent est maintenu pour Linux. C'est cool.
Et bien entendu pour Windows...
Si ce fonctionnement apparaît comme sympa, ce qui l'est moins c'est que les agents disponibles pour Windows sont délaissés.
Le site Loadbalancer.org propose un agent qui n'a pas de mise à jour depuis 2022 et mes tests sur des machines Windows récentes montrent qu'il ne marche pas sous tous les OS.
Les autres solutions existantes récentes demandent à avoir un interpréteur go sur la machine, ce qui n'est pas forcément faisable dans tous les environnements et en plus rajoute une couche de problèmes potentiels avec les mises à jour du runtime.
Mais comme HAProxy fournit les informations sur la manière de créer son agent il était temps pour moi de pallier à ce manque.
Loadbalancer.org m'aura servi de base pour adapter leur mode de fonctionnement à un nouvel agent fonctionnant avec ... (roulements de tambour, enfin pas trop parce que vous vous doutez de la réponse) : Powershell
Si vous souhaitez passer l'explication de fonctionnement, le script est disponible sur mon git.
L'idée est simple :
Une chose aussi simple que créer un port d'écoute n'a jamais été quelque chose que j'ai du programmer. J'en ai configuré dans quasi tous les produits que j'héberge ou installe, mais lorsqu'il a été question de scripter ça, j'étais incapable de savoir par où commencer. Ce que je connais de Powershell et les premières recherches que j'ai pu faire m'ont montré que ça n'était pas possible via des commandes natives. Il était nécessaire d'aller chercher du côté de C# pour trouver les outils nécessaires.
Pour créer un point d'écoute, une classe existe dans C# : System.Net.Sockets.TcpListener. Pour pouvoir être instancié, cet objet attends d'avoir des informations de endpoint (respectivement l'adresse d'écoute et un port) avant d'être démarré.
Cela se traduit comme ceci :
$endpoint = New-Object System.Net.IPEndPoint([ipaddress]::Any,8000)
$listener = $null
$listener = New-Object System.Net.Sockets.TcpListener $endpoint
$listener.Start()
Une fois créé, il va falloir informer l'objet $listener d'accepter les clients en TCP, et d'enregistrer les connexions pour pouvoir y répondre. Comme ce processus doit se faire en boucle, il sera dans une boucle while() sans fin. Lorsqu'une connexion est faite, il procédera au calcul (via la fonction Get-ServerHAProxyLoad décrite un peu plus loin) et l'a renverra sous format de tableau de bytes au client avant de fermer la connexion.
while ($true)
{
# AcceptTcpClient() va bloquer le script en attente de connexion
$client = $listener.AcceptTcpClient()
# GetStream() va permettre d'écrire et lire. On ne vérifie pas ce qui est envoyé, on renvoie les infos
$tcpnetworkstream = $client.GetStream()
# Récupération de la stat
$value = Get-ServerHAProxyLoad
$data = [text.Encoding]::Ascii.GetBytes("$value%`n")
Write-Verbose "Envoi $value sous format : $data"
$tcpnetworkstream.Write($data,0,$data.Length)
$tcpnetworkstream.close()
}
Le calcul de poids est défini dans la fonction écrite plus bas.
$Usage correspond au pourcentage de charge qui sera renvoyé. Si le CPU et la RAM sont à 0% d'utilisation ou la vérification désactivée, la fonction renverra toujours 100%.
L'information de mémoire utilisée est calculée en utilisant les infos de l'objet wmi Win32_Operatingsystem, qui contient deux propriétés : FreePhysicalMemory et TotalVisibleMemory.
Ces 2 valeurs vont nous permettre de calculer le pourcentage de mémoire utilisée, que l'on va ensuite utiliser pour calculer un autre pourcentage : celui du score pour HAProxy.
La RAM utilisée divisée par la valeur RAM_VALUE et multipliée par RAM_IMPORTANCEFACTOR afin d'obtenir un niveau de charge.
Cette valeur sera ensuite soustraite à $Usage pour définir la nouvelle valeur de $Usage.
Le même calcul est ensuite fait pour le CPU (en utilisant la propriété LoadPercentage de l'objet WMI Win32_Processor) et la valeur également soustraite à $Usage pour en définir la nouvelle valeur.
Si jamais une des limites (RAM_THRESHOLD ou CPU_THRESHOLD) est dépassée, alors les calculs sont oubliés et la valeur renvoyée sera forcément "drain 0%", ce qui obligera HAProxy à refuser de nouvelles connexions sur cette machine.
function Get-ServerHAProxyLoad()
{
# Usage : état initial, 100% de poids
$usage = 100
# Threshold : Si une valeur dépasse, on passe à true et force l'usage à 0 après les calculs (drain mode)
$threshold = $false
$ram = 0
$cpuusage = 0
if ($config.RAM_ENABLED -eq 1)
{
$raminfos = Get-WmiObject -Class WIN32_OperatingSystem
$ram = [math]::Floor( 100 - ($raminfos.FreePhysicalMemory / $raminfos.TotalVisibleMemorySize) * 100)
Write-verbose "Ram used : $ram"
$ramscore = [math]::Floor( ( $ram / $Config.RAM_VALUE ) * $config.RAM_IMPORTANCEFACTOR )
Write-Verbose "Ram score : $ramscore"
if ($ram -ge $config.RAM_THRESHOLD)
{
$threshold = $true
Write-verbose "Limit THRESHOLD on RAM"
}
}
if ($config.CPU_ENABLED -eq 1)
{
$cpuusage = $(Get-WmiObject -Class Win32_Processor).loadpercentage
Write-verbose "CPU used : $cpuusage"
$cpuscore = [math]::Floor( ( $cpuusage / $config.CPU_VALUE ) * $config.CPU_IMPORTANCEFACTOR )
Write-verbose "CPU score : $cpuscore"
if ($cpuusage -ge $config.CPU_THRESHOLD)
{
$threshold = $true
Write-verbose "Limit THRESHOLD on CPU"
}
}
## Calcul des scores
## RAM
#
$usage = [math]::Floor( $usage - $ramscore )
Write-Verbose "Score post RAM : $usage"
## CPU
$usage = [math]::Floor( $usage - $cpuscore )
Write-Verbose "Score post CPU : $usage"
if ($threshold -or $usage -le 0)
{
return "drain 0"
}
else
{
return "ready $usage"
}
}
Et une fois que tout le script powershell est lancé, la console en mode verbeux nous renvoie les informations ainsi que le score du serveur. En mettant un peu de charge dessus, il diminue selon les critères choisis et l'information est renvoyée à HAProxy qui adapte sa configuration dynamiquement.


N'hésitez pas à me dire si cela vous aura été utile !