· 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.

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 et cron.

  • 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 non tcp ou ssl), 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 et fullchain.pem dans un fichier combined.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 ! 😉

Back to Blog