Login Logout

Howto HAProxy

HAProxy est un puissant load balancer pour les protocoles TCP/HTTP/HTTPS. Il gère la répartition de charge et la tolérance de panne. Son principal auteur est Willy Tarreau, un développeur actif du noyau Linux. HAProxy est écrit en langage C, il est optimisé pour Linux, mais tourne également sous BSD. Des sites web importants l’utilisent comme Twitter, Github, Reddit, Airbnb, etc.

Installation

# apt install haproxy

Configuration

La configuration se passe dans le fichier /etc/haproxy/haproxy.cfg :

global
      log 127.0.0.1 local5 debug
defaults
      mode     http
listen www
      bind *:80
      balance roundrobin
      option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www.example.com
      stats uri /haproxy-stats
      stats auth foo:bar
      server www00 192.0.2.1:80 maxconn 50 check inter 10s
      server www01 192.0.2.2:80 maxconn 50 check inter 10s

On note l’activation des logs en debug ce qui permet de voir toutes les requêtes. Attention, il faut donc que le démon syslog ait un paramétrage sur la facilité local5 (ou autre selon votre configuration). Pour rsyslog cela se fait ainsi dans rsyslog.conf :

local5.*  -/var/log/haproxy.log

Configuration avancée

Exemple d’une configuration avec frontend/backend HTTP

global
    log /dev/log    local5
    log /dev/log    local5 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5000
    timeout client  50000
    timeout server  50000
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http
    default-server port 80 maxconn 250 on-error fail-check slowstart 60s inter 1m fastinter 5s downinter 10s weight 100


listen stats
    bind *:8080
    stats enable
    stats uri /haproxy
    stats show-legends
    stats show-node
    stats realm Auth\ required
    stats auth foo:bar
    stats admin if TRUE

frontend myfront
    option  forwardfor
    maxconn 800
    bind 0.0.0.0:80
    default_backend myback

backend myback
    balance roundrobin
    server web01 192.0.2.1:80 check observe layer4 weight 100
    server web02 192.0.2.2:80 check observe layer4 weight 100
    server web03 192.0.2.3:80 check observe layer4 weight 100

SSL

global
    # Default SSL material locations
    ca-base /etc/ssl/certs
    crt-base /etc/ssl/private

    # Default ciphers to use on SSL-enabled listening sockets.
    # For more information, see ciphers(1SSL). This list is from:
    #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
    ssl-default-bind-options no-sslv3 no-tls-tickets
    ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
    ssl-default-server-options no-sslv3 no-tls-tickets

Dans un frontend il faut ensuite faire un “binding” avec des arguments pour le SSL :

frontend fe_https
    bind 0.0.0.0:443 ssl crt /etc/ssl/haproxy/example_com.pem

    http-request set-header X-Forwarded-Proto: https

    default_backend myback

Le fichier example_com.pem doit contenir le certificat ainsi que la clé privée et éventuellement des paramètres Diffie-Hellman (tout au format PEM).

Il est possible d’indiquer plusieurs fois crt /chemin/vers/fichier.pem pour avoir plusieurs certificats possibles. HAProxy utilisera alors le mécanisme de SNI. Si on indique plutôt un dossier (par exemple /etc/ssl/haproxy/) tous les fichiers trouvés seront chargé par ordre alphabétique.

Pour chaque fichier PEM trouvé, HAProxy cherchera un fichier .ocsp du même nom. Il peut être vide ou contenir une réponse OCSP valide (au format DER). Cela active le mécanisme de « OCSP stapling »

Touts les détails de configuration pour l’attribut crt sont consultables sur http://cbonte.github.io/haproxy-dconv/1.5/configuration.html#5.1-crt

Terminaison SSL

Si HAProxy doit faire la terminaison SSL et dialoguer en clair avec le backend, on se contente de transmettre la requête.

backend myback
    balance roundrobin
    server web01 192.0.2.1:80 check observe layer4 weight 100
    server web02 192.0.2.2:80 check observe layer4 weight 100

Si HAProxy doit faire la terminaison SSL et maintenir une communication chiffrée avec le backend, on doit le spécifier dans le backend (avec l’argument ssl verify [none|optional|required], car le port 443 ne suffit pas à forcer le ssl).

backend myback
    balance roundrobin
    server web01 192.0.2.1:443 ssl verify none check observe layer4 weight 100
    server web02 192.0.2.2:443 ssl verify none check observe layer4 weight 100

La vérification du SSL sur le backend est à voir en fonction des besoins de sécurité.

Exemple avec plusieurs backends et du « sticky session »

global
   [...]

defaults
   [...]

frontend http-in
   bind *:8080

   # On définit des ACL qui associe un Host: HTTP à un backend
   acl is_domain1 hdr_end(host) -i domain1.example.com
   acl is_domain2 hdr_end(host) -i domain2.example.com
   use_backend domain1 if is_domain1
   use_backend domain2 if is_domain2
   default_backend domain1

backend domain1
   # Avec cette directive, HAProxy ajoute automatiquement un cookie SERVERID aux réponses HTTP,
   # et l'utilise pour sélectionner le bon serveur lors de la prochaine requête
   cookie SERVERID insert indirect
   balance roundrobin

   # Pour ce serveur, la valeur du cookie SERVERID sera "web01" (directive "cookie")
   server web01 192.0.2.1:80 cookie web01 check
   # Pour ce serveur, la valeur du cookie SERVERID sera "web02"
   server web02 192.0.2.2:80 cookie web02 check

backend domain2
   cookie SERVERID insert indirect
   balance roundrobin

   server web01 192.0.2.1:80 cookie web01 check
   server web02 192.0.2.2:80 cookie web02 check

Exemple en mode TCP

listen memcached 127.0.0.1:11211
    option tcp-check
    server nosql00 192.0.2.3:11211 check
    server nosql01 192.0.2.4:11211 check backup

Exemple pour MySQL

Il existe 2 modes principaux pour un proxy MySQL :

  • le mode simple, qui effectue un test de connexion au serveur MySQL ;
  • le mode avancé, qui exécute des tests poussés (et personnalisés) pour valider le bon fonctionnement du serveur.

Mode simple

HAProxy fourni une option “mysql-check”. Il va alors faire une connexion identifiée au serveur MySQL, puis la fermer et vérifiée dans les infos renvoyées que tout semble correct.

Ce mode ne nécessite pas d’outillage supplémentaire et nous le recommandons lorsqu’HAProxy agit seulement comme un proxy et pas comme un load-balancer ou pour de la tolérance de panne.

listen mysql 127.0.0.1:3306
    mode tcp
    option mysql-check user haproxy_check
    server sql00 192.0.2.1:3306 check

Mode avancé

La version avancée consiste à utiliser un check http pour déterminer l’état du serveur.

listen mysql 127.0.0.1:3306
    option httpchk
    server sql00 192.0.2.1:3306 check port 8306
    server sql01 192.0.2.2:3306 check port 8306 backup

On note l’option httpchk qui va permettre de faire un check en HTTP et vérifier des conditions avancées (réplication OK, etc.).

Un moyen simple (inspirés de ce vieux blog post) est de créer un script qui sera déclenché par xinetd.

On ajoute un service à xinetd, dans /etc/xinetd.d/mysqlchk :

service mysqlchk {
        flags           = REUSE
        socket_type     = stream
        port            = 8306
        wait            = no
        user            = root
        server          = /root/mysqlchk
        log_on_failure  += USERID
        disable         = no
        only_from       = 192.0.2.0/27
}

On crée le script a exécuter dans /root/mysqlchk :

MYSQL_HOST="127.0.0.1"
MYSQL_PORT="3306"
MYSQL_USERNAME="mysqlchk"
MYSQL_PASSWORD="PASSWORD"
TMP_FILE="/tmp/mysqlchk.out"
ERR_FILE="/tmp/mysqlchk.err"

/usr/bin/mysql --host=$MYSQL_HOST --port=$MYSQL_PORT --user=$MYSQL_USERNAME \
        --password=$MYSQL_PASSWORD -e"show databases;" > $TMP_FILE 2> $ERR_FILE

uptime=`cat /proc/uptime | cut -f 1 -d .`

if [ "$(/bin/cat $TMP_FILE)" != "" ] && ( /root/nrpe-check-mysql-slave.sh >/dev/null || [ $uptime -gt 3600 ] ); then
        # mysql is fine, return http 200
        /bin/echo -e "HTTP/1.1 200 OK\r\n"
        /bin/echo -e "Content-Type: Content-Type: text/plain\r\n"
        /bin/echo -e "\r\n"
        /bin/echo -e "MySQL is running.\r\n"
        /bin/echo -e "\r\n"
else
        # mysql is fine, return http 503
        /bin/echo -e "HTTP/1.1 503 Service Unavailable\r\n"
        /bin/echo -e "Content-Type: Content-Type: text/plain\r\n"
        /bin/echo -e "\r\n"
        /bin/echo -e "MySQL is *down*.\r\n"
        /bin/echo -e "\r\n"
fi

/root/nrpe-check-mysql-slave.sh :

#!/bin/sh

exec /usr/lib/nagios/plugins/check_mysql --check-slave -u debian-sys-maint -p PASSWORD -w 5 -c 60

Il est également possible d’utiliser tout programme ou script, pourvu qu’au final il puisse être accessible en HTTP.

HTTP basic authentication

Pour mettre en place une authentification HTTP basique au niveau d’HAProxy, définir dans la section globale une liste d’utilisateur, soit avec un mot de passe en clair soit avec un mot de passe chiffré :

userlist NomDeMaUserList
user user1 insecure-password passwordEnClair
user user2 password $6$passwordSHA512
[…]

Dans le backend concerné rajouter :

auth AuthOkayPourMonSite http_auth(NomDeMaUserList)
http-request auth realm Texte if !AuthOkayPourMonSite

Debug

# echo "show info" | socat stdio unix-connect:/run/haproxy/admin.sock
# echo "show acl" | socat stdio unix-connect:/run/haproxy/admin.sock
# echo "show acl #<ID>" | socat stdio unix-connect:/run/haproxy/admin.sock