· 14 min read
Découverte des Load Balancer avec HAProxy
Découvrons ensemble le concept de Load Balancer avec HAProxy. Nous allons mettre en place un petit labo avec Docker pour comprendre le fonctionnement de HAProxy, puis nous déploierons notre démo dans le cloud.
Introduction
Dans cet article, nous allons découvrir le concept de Load Balancer (avec HAProxy).
Nous allons mettre en place un petit labo avec Docker pour comprendre le fonctionnement de HAProxy, puis nous déploierons notre démo dans le cloud histoire de voir comment ça se passe en production !
Disclamer
La démonstration que nous allons mettre en place est très simple et n’intègre pas toutes les notions qu’il faut prendre en compte pour déployer une application en production.
Cette démo est là pour vous donner une idée de ce qu’est un Load Balancer et comment il fonctionne, de vous guider dans les premiers pas avec HAProxy et de vous donner quelques pistes pour aller plus loin.
Pour une utilisation en production, il faudra prendre en compte d’autres aspects comme la sécurité, la haute disponibilité, la scalabilité, etc.
Les sources
Vous pouvez retrouver les sources de cette démo sur mon GitHub : Simple Load Balancer.
Qu’est-ce qu’un Load Balancer ?
Un Load Balancer est un composant qui permet de répartir la charge entre plusieurs serveurs (ou plusieurs instances d’une même application).
Il permet de rendre une application plus performante et plus résiliente. Si un serveur tombe en panne, le Load Balancer va automatiquement rediriger les requêtes vers les autres serveurs.
Il est également un précieux allié pour la mise à jour d’une application. En effet, il est possible de mettre à jour les serveurs un par un, sans impacter les utilisateurs.
Bref, le Load Balancer est un composant essentiel pour les applications qui ont besoin de haute disponibilité !
HAProxy
HAProxy est un logiciel libre qui permet de faire du Load Balancing. Il est réputé pour être très performant et très stable.
Il est très utilisé dans le monde du web et est notamment utilisé par GitHub, Stack Overflow, Reddit, etc.
Parmi ses challengers, on peut citer NGINX, Traefik, Caddy, etc.
Le lab de démo
Pour cette démo, nous allons mettre en place un petit labo avec Docker pour comprendre le fonctionnement de HAProxy et des Load Balancer en général.
Il sera composé de 2 backends (des serveurs web) et d’un frontend (le Load Balancer) et orchestré avec Docker Compose.
Les Backends
Nous allons mettre en place 2 backends pour notre démo, ce qui nous permettra de voir comment HAProxy répartit la charge entre les différents serveurs.
Pour l’occasion, nous allons nous créer une petite image Docker qui va nous servir de backend, et nous permettre d’y indiquer un message personnalisé (exemple : “Hello from Backend: backend 1”, “Hello from Backend: backend 2”, etc.).
Le Dockerfile :
Ce Dockerfile est très simple, il se base sur l’image officielle d’Apache et copie un fichier index.html
dans le dossier /usr/local/apache2/htdocs/
.
On utilise également la commande RUN
pour installer le package gettext-base
qui nous permettra d’utiliser la commande envsubst
dans notre script d’entrypoint.
FROM httpd:latest
RUN apt-get update && \
apt-get install -y gettext-base && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY index.html /usr/local/apache2/htdocs/index.html
COPY entrypoint.sh /usr/local/bin/
# Set permissions
RUN chmod +x /usr/local/bin/entrypoint.sh
# Set environment variables
ENV BACKEND_NAME="backend"
# Set entrypoint
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# Expose port
EXPOSE 80
Le script d’entrypoint :
Ce script est également très simple, il permet de remplacer la variable BACKEND_NAME
dans le fichier index.html
par la valeur de la variable d’environnement BACKEND_NAME
.
#!/bin/sh
# Use envsubst to replace the environment variables in the index.html file
envsubst < /usr/local/apache2/htdocs/index.html > /usr/local/apache2/htdocs/index_temp.html
mv /usr/local/apache2/htdocs/index_temp.html /usr/local/apache2/htdocs/index.html
# Start Apache in the foreground
exec httpd-foreground
Le fichier index.html :
Ce fichier est également très simple, il affiche le message “Hello from Backend: backend 1” (ou “backend 2”, etc.) en fonction de la valeur de la variable BACKEND_NAME
.
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello from Backend: ${BACKEND_NAME}</h1>
</body>
</html>
Le compose.yml :
Ce fichier est également très simple, il permet de définir 2 services (backend1 et backend2) qui utilisent l’image que nous venons de créer.
version: '3.8'
services:
backend1:
image: yoanbernabeu/simple-load-balancer-demo:latest
environment:
- BACKEND_NAME=back1
networks:
- backend-network
backend2:
image: yoanbernabeu/simple-load-balancer-demo:latest
environment:
- BACKEND_NAME=back2
networks:
- backend-network
networks:
backend-network:
driver: bridge
Build et run :
Nous allons créer un fichier `Makefile“ pour nous simplifier la vie.
Ce fichier va nous permettre de builder l’image Docker, de la pousser sur Docker Hub, de la pull sur notre serveur et de lancer le labo avec Docker Compose.
# Makefile for the simple load balancer project
# Variables
TAG := latest
# Variables for demo
DEMO_IMAGE_NAME := yoanbernabeu/simple-load-balancer-demo
DEMO_PORT1 := 8081
DEMO_PORT2 := 8082
# Build the docker image
build:
@docker build -t $(DEMO_IMAGE_NAME) ./Demo
# Push the docker image
push:
@docker push $(DEMO_IMAGE_NAME):$(TAG)
# Pull the docker image
pull:
@docker pull $(DEMO_IMAGE_NAME):$(TAG)
# Run the demo (without https)
run-local:
@make build
@docker compose up -d
# Stop the demo
stop:
@docker compose down
# Default target
.PHONY: build push pull run-local stop
Nous pouvons maintenant builder notre image Docker et lancer notre labo avec la commande make run-local
.
Pour tester que les fichiers index.html
sont bien générés avec les bonnes valeurs, nous pouvons utiliser la commande suivante à l’intérieur d’un des conteneurs :
cat /usr/local/apache2/htdocs/index.html
Le Frontend (Load Balancer)
Pour le frontend, nous allons utiliser l’image officielle de HAProxy et construire notre propre image qui va nous permettre de configurer HAProxy à la volée en fonction des variables d’environnement.
Le Dockerfile :
Ce Dockerfile est également très simple, il se base sur l’image officielle de HAProxy :
-
On bascule sur l’utilisateur root pour pouvoir installer les packages nécessaires à la configuration de HAProxy, puis nous installons
certbot
,gettext-base
etcron
. -
Nous définissons les variables d’environnement qui vont nous servir à configurer HAProxy.
-
Nous copions ensuite les fichiers de configuration de HAProxy et le script d’entrypoint (nous voyons que nous avons 2 fichiers de configuration, un pour le mode HTTP et un pour le mode HTTPS, on en parlera plus tard), ainsi que le script qui va nous permettre de renouveler les certificats Let’s Encrypt et le fichier de cron.
-
Nous donnons les droits d’exécution aux scripts et le droit de lecture au fichier de cron.
-
Nous définissons le fichier d’entrypoint.
FROM haproxy:latest
# Switch to root user to install packages
USER root
# Install certbot and cron
RUN apt-get update && \
apt-get install -y certbot gettext-base cron && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Set environment variables
ENV DOMAIN_NAME=yourdomain.com
ENV EMAIL_ADDRESS=your@email.com
ENV BACKEND1_URL=back1.test.com:80
ENV BACKEND2_URL=back2.test.com:80
ENV ENABLE_HTTPS=false
# Copy files
COPY entrypoint.sh /usr/local/bin/
COPY haproxy.cfg.http.template /usr/local/etc/haproxy/haproxy.cfg.http.template
COPY haproxy.cfg.https.template /usr/local/etc/haproxy/haproxy.cfg.https.template
COPY renew-certs.sh /usr/local/bin/
COPY renew-cron /etc/cron.d/
# Set permissions
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/renew-certs.sh && \
chmod 0644 /etc/cron.d/renew-cron && \
crontab /etc/cron.d/renew-cron
# Set entrypoint
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
Les fichiers de configuration de HAProxy :
Nous avons 2 fichiers de configuration, un pour le mode HTTP et un pour le mode HTTPS.
Important : nous utilisons la commande envsubst
pour remplacer les variables d’environnement dans les fichiers de configuration (exemple : ${BACKEND1_URL}
).
Les deux fichiers sont très similaires, la seule différence est que le fichier de configuration pour le mode HTTPS contient une ligne supplémentaire pour activer le mode HTTPS (oui nous aurions pu faire un seul fichier de configuration et utiliser une variable d’environnement pour activer ou non le mode HTTPS, mais je trouvais ça plus simple de faire 2 fichiers pour cette démo).
A noter dans la configuration de HAProxy :
- Nous utilisons le mode
http
(et nontcp
oussl
), ce qui nous permet de faire du Load Balancing au niveau de la couche 7 du modèle OSI (couche application). - Nous utilisons le mode
roundrobin
pour répartir la charge entre les différents serveurs. - Nous utilisons le mode
cookie
pour que HAProxy puisse identifier les différents serveurs et rediriger les requêtes vers le même serveur (exemple : si un utilisateur se connecte à notre application, HAProxy va lui attribuer un cookie qui va lui permettre d’être redirigé vers le même serveur pour les requêtes suivantes). - Nous utilisons le mode
redispatch
pour que HAProxy puisse rediriger les requêtes vers un autre serveur si le serveur initial tombe en panne. - Pour le mode HTTPS, nous utilisons le mode
ssl
et nous indiquons le chemin vers le certificat Let’s Encrypt.
N’hésitez pas à consulter la documentation de HAProxy pour plus d’informations !
Le fichier haproxy.cfg.http.template :
Ce fichier est très simple, il permet de définir la configuration de HAProxy pour le mode HTTP (sans HTTPS).
global
log stdout format raw local0
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http_front
bind *:80
default_backend http_back
backend http_back
balance roundrobin
cookie SERVERID insert indirect nocache
option redispatch
server back1 ${BACKEND1_URL} check cookie back1
server back2 ${BACKEND2_URL} check cookie back2
Le fichier haproxy.cfg.https.template :
Ce fichier est également très simple, il permet de définir la configuration de HAProxy pour le mode HTTPS.
global
log stdout format raw local0
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http_front
bind *:80
bind *:443 ssl crt ${DOMAIN_CERT_PATH}
redirect scheme https code 301 if !{ ssl_fc }
default_backend http_back
backend http_back
balance roundrobin
cookie SERVERID insert indirect nocache
option redispatch
server back1 ${BACKEND1_URL} check cookie back1
server back2 ${BACKEND2_URL} check cookie back2
Le script de négociation des certificats Let’s Encrypt :
Ce script est également très simple, il permet de négocier les certificats Let’s Encrypt et de les renouveler automatiquement.
Pour cela, nous utilisons la commande certbot
avec le mode standalone
qui permet de générer les certificats sans avoir besoin d’un serveur web.
Nous utilisons également l’option --post-hook
pour recharger la configuration de HAProxy après le renouvellement des certificats.
La variable $DOMAIN_NAME
est définie dans le fichier docker-compose.yml
et correspond au nom de domaine que nous allons utiliser pour notre démo, elle sera “remplacée” par la valeur de la variable d’environnement DOMAIN_NAME
lors de l’exécution du script avec la commande envsubst
.
#!/bin/sh
# Generate the certificate with certbot and reload HAProxy
certbot certonly --standalone -d $DOMAIN_NAME --non-interactive --agree-tos --email $EMAIL_ADDRESS --expand --renew-by-default --post-hook "haproxy -f /usr/local/etc/haproxy/haproxy.cfg -c && systemctl reload haproxy"
Le fichier de cron :
Ce fichier est également très simple, il permet de définir la fréquence de renouvellement des certificats Let’s Encrypt.
Dans notre cas, nous allons renouveler les certificats tous les jours à minuit (et penser bien à laisser une ligne vide à la fin du fichier, c’est important pour le fonctionnement de cron, oui oui !).
0 0 * * * root /usr/local/bin/renew-certs.sh >> /var/log/cron.log 2>&1
Le script d’entrypoint :
Le script d’entrypoint est un peu plus complexe, il permet de :
- Detecter si le mode HTTPS est activé ou non.
- Si le mode HTTPS est activé, il va :
- Négocier les certificats Let’s Encrypt.
- Concaténer les fichiers
privkey.pem
etfullchain.pem
dans un fichiercombined.pem
pour que HAProxy puisse les utiliser. - Remplacer les variables d’environnement dans le fichier de configuration de HAProxy pour le mode HTTPS.
- Lancer le cron.
- Si le mode HTTPS n’est pas activé, il va :
- Remplacer les variables d’environnement dans le fichier de configuration de HAProxy pour le mode HTTP.
- Lancer HAProxy avec le fichier de configuration généré.
#!/bin/sh
# Check if the ENABLE_HTTPS variable is set to true
if [ "$ENABLE_HTTPS" = "true" ]; then
/usr/local/bin/renew-certs.sh
export DOMAIN_CERT_PATH="/etc/letsencrypt/live/$DOMAIN_NAME/combined.pem"
cat /etc/letsencrypt/live/$DOMAIN_NAME/privkey.pem /etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem > /etc/letsencrypt/live/$DOMAIN_NAME/combined.pem
envsubst < /usr/local/etc/haproxy/haproxy.cfg.https.template > /usr/local/etc/haproxy/haproxy.cfg
cron -f &
else
envsubst < /usr/local/etc/haproxy/haproxy.cfg.http.template > /usr/local/etc/haproxy/haproxy.cfg
fi
# Start HAProxy with the generated config file
exec haproxy -f /usr/local/etc/haproxy/haproxy.cfg
Le compose.yml :
Nous devons également modifier le fichier compose.yml
pour ajouter le frontend (le Load Balancer).
version: '3.8'
services:
loadbalancer:
image: yoanbernabeu/simple-load-balancer:latest
ports:
- "8080:80"
environment:
- DOMAIN_NAME=yourdomain.com
- EMAIL_ADDRESS=your@email.com
- BACKEND1_URL=backend1:80
- BACKEND2_URL=backend2:80
- ENABLE_HTTPS=false
depends_on:
- backend1
- backend2
networks:
- backend-network
backend1:
image: yoanbernabeu/simple-load-balancer-demo:latest
environment:
- BACKEND_NAME=back1
networks:
- backend-network
backend2:
image: yoanbernabeu/simple-load-balancer-demo:latest
environment:
- BACKEND_NAME=back2
networks:
- backend-network
networks:
backend-network:
driver: bridge
Modifiions le Makefile :
Nous devons également modifier le fichier Makefile
pour ajouter les commandes pour builder et pousser l’image du frontend.
# Makefile for the simple load balancer project
# Variables
IMAGE_NAME := yoanbernabeu/simple-load-balancer
TAG := latest
CONTAINER_NAME := haproxy-container
# Variables for demo
DEMO_IMAGE_NAME := yoanbernabeu/simple-load-balancer-demo
DEMO_PORT1 := 8081
DEMO_PORT2 := 8082
# Build the docker image
build:
@docker build -t $(IMAGE_NAME):$(TAG) ./LoadBalancer
@docker build -t $(DEMO_IMAGE_NAME) ./Demo
# Push the docker image
push:
@docker push $(IMAGE_NAME):$(TAG)
@docker push $(DEMO_IMAGE_NAME):$(TAG)
# Pull the docker image
pull:
@docker pull $(IMAGE_NAME):$(TAG)
@docker pull $(DEMO_IMAGE_NAME):$(TAG)
# Run the demo (without https)
run-local:
@make build
@docker compose up -d
# Stop the demo
stop:
@docker compose down
# Default target
.PHONY: build push pull run-local stop
Build et run :
Nous pouvons maintenant builder notre image Docker et lancer notre labo avec la commande make run-local
.
Nous n’avons plus qu’à tester notre labo avec la commande curl localhost:8080
et nous devrions voir apparaître le message “Hello from Backend: backend 1” ou “Hello from Backend: backend 2” en fonction du serveur qui a répondu à la requête.
- Stoppons l’un des backends (celui qui a répondu à la requête précédente) et relançons la commande
curl localhost:8080
, nous devrions voir apparaître le message “Hello from Backend: backend 1” ou “Hello from Backend: backend 2” en fonction du serveur qui a répondu à la requête.
Magique non ?! 🪄
Déploiement dans le cloud
Maintenant que nous avons compris le fonctionnement de HAProxy, nous allons déployer notre démo dans le cloud, histoire de voir comment ça se passe (presque) en production !
Création de l’infrastructure
Pour cette démo, nous allons utiliser Jelastic chez Hidora, une plateforme d’orchestration de conteneurs qui permet de déployer des applications dans le cloud.
Nous allons créer un environnement avec 3 conteneurs :
- Un conteneur pour le frontend (le Load Balancer).
- Un conteneur pour le backend1.
- Un conteneur pour le backend2.
Le manifeste JPS :
Pour créer notre environnement, nous allons utiliser un manifeste JPS (Jelastic Packaging Standard) qui va nous permettre de définir notre environnement et de le déployer en une seule commande.
Je ne rentre volontairement pas dans les détails de la création de l’environnement, si vous souhaitez en savoir plus, je vous invite à consulter la documentation de Jelastic.
type: install
name: SimpleLoadBalancer
id: SimpleLoadBalancer
description: |
Fichier d'Infrastructure As Code pour SimpleLoadBalancer
settings:
fields:
- caption: DomaineName
type: string
name: DOMAIN_NAME
required: true
description: DomaineName
- caption: EmailAddress
type: string
name: EMAIL_ADDRESS
required: true
description: EmailAddress
nodes:
- image: yoanbernabeu/simple-load-balancer:latest
count: 1
cloudlets: 8
fixedCloudlets: 1
nodeGroup: cp
displayName: Load Balancer
isSLBAccessEnabled: false
extip: true
env:
DOMAIN_NAME: "${settings.DOMAIN_NAME}"
EMAIL_ADDRESS: "${settings.EMAIL_ADDRESS}"
BACKEND1_URL: back1:80
BACKEND2_URL: back2:80
ENABLE_HTTPS: true
volumes:
- /etc/letsencrypt
- image: yoanbernabeu/simple-load-balancer-demo:latest
count: 1
cloudlets: 8
fixedCloudlets: 1
nodeGroup: back1
displayName: Back 1
isSLBAccessEnabled: false
env:
BACKEND_NAME: back1
- image: yoanbernabeu/simple-load-balancer-demo:latest
count: 1
cloudlets: 8
fixedCloudlets: 1
nodeGroup: back2
displayName: Back 2
isSLBAccessEnabled: false
env:
BACKEND_NAME: back2
success: |
SimpleLoadBalancer est installé sur votre environnement Jelastic !
Création de l’environnement :
Rendez-vous sur la console Jelastic et cliquez sur le bouton “Importer” dans le menu du haut.
Copiez collez le manifeste JPS dans le champ et cliquez sur le bouton “Importer”.
Remplissez les champs “DomaineName” et “EmailAddress” et cliquez sur le bouton “Installer”.
Une fois l’installation terminée, vous devriez voir apparaître un message de succès avec le nom de votre environnement.
C’est bon, notre environnement est créé !
Tests et démonstration :
Ouvrez votre navigateur et rendez-vous sur l’URL de votre environnement.
Vous devriez voir apparaître le message “Hello from Backend: back1” ou “Hello from Backend: back2” en fonction du serveur qui a répondu à la requête.
Stoppez l’un des backends (celui qui a répondu à la requête précédente) et rafraîchissez la page, vous devriez voir apparaître le message “Hello from Backend: back1” ou “Hello from Backend: back2” en fonction du serveur qui a répondu à la requête.
Pour stopper un conteneur, dans le shell du conteneur, tapez la commande
apachectl stop
.
Le Load Balancer a bien redirigé les requêtes vers le serveur qui était encore en ligne !
C’est bon, notre démo fonctionne même dans le cloud ! 🎉
Conclusions
Nous avons vu ensemble ce qu’est un Load Balancer et comment il fonctionne.
Nous avons également vu comment mettre en place un petit labo avec Docker pour comprendre le fonctionnement de HAProxy, puis nous avons déployé notre démo dans le cloud.
J’espère que cet article vous a plu et qu’il vous a permis de mieux comprendre le fonctionnement des Load Balancer et de HAProxy en particulier.
Encore une fois, cette démo est très simple et n’intègre pas toutes les notions qu’il faut prendre en compte pour déployer une application en production, mais vous avez maintenant les bases pour aller plus loin !
Amusez-vous bien ! 😉