388 lines
13 KiB
Markdown
388 lines
13 KiB
Markdown
---
|
|
title: Howto HAProxy
|
|
category: web HA
|
|
---
|
|
|
|
* Documentation (txt) : <http://haproxy.1wt.eu/download/1.5/doc/configuration.txt>
|
|
* Documentation (html) : <https://www.haproxy.com/doc/hapee/1.5/onepage/>
|
|
|
|
[HAProxy](http://www.haproxy.org/) 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](http://1wt.eu/#wami), 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
|
|
~~~
|
|
|
|
On vérifie qu'il n'y a pas d'erreur de syntaxes :
|
|
|
|
~~~
|
|
haproxy -c -V -f /etc/haproxy/haproxy.cfg
|
|
~~~
|
|
|
|
## 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
|
|
~~~
|
|
|
|
La visualisation des statistiques peut aussi se faire via la console :
|
|
~~~
|
|
hatop -s /var/run/haproxy/admin.sock
|
|
~~~
|
|
|
|
|
|
### 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 »
|
|
|
|
Tous 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>
|
|
|
|
Dans le cas où HAProxy gère plusieurs domaines dont certains seulement ont un certificat SSL, HAProxy enverra par défaut le certificat défini par la directive `crt`. Si il pointe sur un répertoire, un des certificats du répertoire (ça ne semble pas être le premier/dernier par ordre alphabétique) sera envoyé. Pour éviter ce comportement, on peut rajouter la directive `strict-sni`. Dans ce cas, si HAProxy ne trouve pas le certificat qui correspond au domaine demandé par le client, il retournera l'erreur SSL *SSL_ERROR_BAD_CERT_DOMAIN*.
|
|
|
|
Il est aussi possible de désigner un fichier contenant la liste de tous les PEM :
|
|
|
|
~~~
|
|
frontend fe_https
|
|
bind 0.0.0.0:443 ssl crt-list /etc/ssl/crt-list
|
|
~~~
|
|
|
|
Le fichier crt-list contient le chemin des fichiers PEM, ligne par ligne, comme ceci :
|
|
|
|
~~~
|
|
/chemin/vers/fichier1.pem
|
|
/chemin/vers/fichier2.pem
|
|
/chemin/vers/fichier3.pem
|
|
~~~
|
|
|
|
|
|
#### 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é.
|
|
|
|
Attention, HAProxy ne supporte pas le SNI pour la communication avec le backend, du moins en version 1.5. L'erreur n'est pas explicite, il se contente d'indiquer une erreur de négociation SSL, cela peut se voir en détail avec wireshark par exemple.
|
|
Il faut donc s'assurer que le backend délivre le bon certificat par défaut.
|
|
|
|
### 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érifier 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 post-41
|
|
server sql00 192.0.2.1:3306 check
|
|
~~~
|
|
|
|
Il faut penser à créer l'utilisateur "haproxy_check" (sans mot de passe mais sans droits et restreint à une IP source) sur les serveurs ciblés
|
|
|
|
~~~{.sql}
|
|
CREATE USER haproxy_check@IP_OF_HAPROXY;
|
|
~~~
|
|
|
|
#### 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 HEAD
|
|
http-check disable-on-404
|
|
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é de ce [vieux blog post](http://sysbible.org/2008/12/04/having-haproxy-check-mysql-status-through-a-xinetd-script/)) est de créer un script qui sera déclenché par **xinetd**.
|
|
|
|
~~~
|
|
# apt install xinetd
|
|
~~~
|
|
|
|
On ajoute un service à xinetd, dans `/etc/xinetd.d/mysqlchk` (droits: root:root 0644) :
|
|
|
|
~~~
|
|
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
|
|
per_source = UNLIMITED
|
|
}
|
|
~~~
|
|
|
|
Il faut penser à ajuster la liste d'adresses IP autorisées dans `only_from`.
|
|
|
|
On ajoute la ligne suivante dans `/etc/services` :
|
|
|
|
~~~
|
|
mysqlchk 8306/tcp # mysqlchk
|
|
~~~
|
|
|
|
On crée le script à exécuter dans `/root/mysqlchk` (droits: root:root 0750) :
|
|
|
|
~~~{.bash}
|
|
#!/bin/sh
|
|
|
|
# Mysql is down, return a 503
|
|
return="503 Service Unavailable"
|
|
|
|
# Mysql is fine, return a 200
|
|
/usr/lib/nagios/plugins/check_mysql -f /etc/mysql/debian.cnf >/dev/null && return="200 OK"
|
|
|
|
# Mysql is up but replication is not ok, return a 404
|
|
# You may want to comment this line in master/master mode
|
|
# It disable server (NOLB status) when replication is down or lagging
|
|
/usr/lib/nagios/plugins/check_mysql -f /etc/mysql/debian.cnf --check-slave -c 60 >/dev/null || return="404 Not Found"
|
|
|
|
cat <<EOF
|
|
HTTP/1.0 ${return}
|
|
Content-Type: Content-Type: text/plain
|
|
Content-Length: 0
|
|
|
|
EOF
|
|
~~~
|
|
|
|
On redémarre xinetd (surveiller `/var/log/syslog` pour d'éventuelles erreurs) et on pense à autoriser le port 8306 au niveau firewall depuis les IP concernées.
|
|
|
|
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
|
|
~~~
|
|
|
|
### Redirection d'un domaine
|
|
|
|
~~~
|
|
acl domaine hdr(host) -i domaine1.com
|
|
redirect prefix http://www.domaine2.com code 301 if domaine
|
|
~~~
|
|
|
|
Ainsi, il fait la redirection si l'acl domaine correspond au domaine spécifié.
|
|
|
|
## Optimisations TCP/kernel
|
|
|
|
Sur un serveur servant principalement de proxy/load-balancer, nous conseillons plusieurs optimisations TCP/kernel, à appliquer au niveau "sysctl" :
|
|
|
|
~~~{.ini}
|
|
# Augmentation du nombre de connexions simultanées possibles
|
|
net.netfilter.nf_conntrack_max=1000000
|
|
# Réduction du délai de timeout FIN
|
|
net.ipv4.tcp_fin_timeout=20
|
|
# Elargissement de la plage de ports locaux utilisables
|
|
net.ipv4.ip_local_port_range=1025 65534
|
|
# augmentation du nombre maximum d'orphelins
|
|
net.ipv4.tcp_max_orphans=65536
|
|
~~~
|
|
|
|
## 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
|
|
~~~
|