Ajout de contenu

This commit is contained in:
Jérémy Lecour 2022-09-19 14:30:24 +02:00 committed by Jérémy Lecour
parent 6ff94a53d0
commit f0a225682b

174
README.md
View file

@ -1,6 +1,6 @@
# haproxyconf-2022
Le but de cette présentation ets de montrer comment on peut utiliser HAProxy t Varnish ensemblepour accélérer et fiabiliser des sites et applications web.
Le but de cette présentation est de montrer comment on peut utiliser HAProxy et Varnish ensemble pour accélérer et fiabiliser des sites et applications web.
Nous allons commencer par les principes généraux et plonger petit à petit dans les détails.
@ -8,10 +8,10 @@ Nous allons commencer par les principes généraux et plonger petit à petit dan
Il s'agit de 2 logiciels libres, performants et matures.
Ils ne sont pas les seuls dans leur catégorie, mais ils sont ceux que nous connaissons et apprécions le plus.
Ils sont facilement disponible sur de nombreuses plateformes. Ils sont très bien documenbtés et soutenus par des vastes communautés.
Ils sont facilement disponibles sur de nombreuses plateformes. Ils sont très bien documentés et soutenus par des vastes communautés.
Ils disposent aussi de possibilité de support professionnel pour les besoins les plus exigeants.
HAPoxy va servir de 1er serveur web pour le client HTTP. Il va faire la terminaison TLS, décider si la requête doit être acceptée ou rejetée, transmettre à Varnish s'il est disponible et si on veut faire du cache, et enfin il va transmettre la requête au serveur (singulier) ou serveurs (pluriel) web finaux.
HAProxy va servir de 1er serveur web pour le client HTTP. Il va faire la terminaison TLS, décider si la requête doit être acceptée ou rejetée, transmettre à Varnish s'il est disponible et si on veut faire du cache, et enfin il va transmettre la requête au serveur (singulier) ou serveurs (pluriel) web finaux.
Il sert donc de proxy, avec des capacités de répartiteur de charge et de tolérance de panne.
Varnish va servir à mettre en cache le résultat de certaines requêtes pour ne pas avoir à solliciter le serveur applicatif final. Il va appliquer des règles plus ou moins complexes pour décider s'il met en cache,s'il sert depuis le cache…
@ -33,26 +33,28 @@ Nous avons choisi de placer HAProxy et Varnish sur le même serveur, et de les f
Une fois la requête transmise à Varnish, il va également l'analyser pour décider s'il peut servir une réponse depuis le cache ou s'il doit la faire passer. Et s'il l'a fait passer, lors du retour il va décider s'il peut la mettre en cache.
Dans le cas le cplus courant d'utilisation de Varnish, lorsqu'il dit faire passer une requête, il le fait directement au serveur d'application. Nous avons choisi de faire un peu différemment et de ne pas gérer dans Varnish la partie load-balancing et tolérance de panne, car elle est gérée de manière plus complète par HAProxy, et c'est ce que nous maitrisons le mieux.
Dans le cas le plus courant d'utilisation de Varnish, lorsqu'il dit faire passer une requête, il le fait directement au serveur d'application. Nous avons choisi de faire un peu différemment et de ne pas gérer dans Varnish la partie load-balancing et tolérance de panne, car elle est gérée de manière plus complète par HAProxy, et c'est ce que nous maitrisons le mieux.
Varnish va donc renvoyer la requête à HAProxy, le même que celui qui a traité la requête entrante.
Mais pour ne pas tomber dans une boucle, et pour ne pas refaire certaines analyses déjà faites au début, nous faisons entrer la requête par un autre « frontend ». Celui-ci sera plus simple et aura commeprincipale responsabilité de choisir le « backend » final à utiliser pour la requête. Si vous gérer des sites ou applications qui sont réparties sur des groupes de serveurs différents, il faut avoir autant de « backen » que de groupes de serveur.
Mais pour ne pas tomber dans une boucle, et pour ne pas refaire certaines analyses déjà faites au début, nous faisons entrer la requête par un autre « frontend ». Celui-ci sera plus simple et aura comme principale responsabilité de choisir le « backend » final à utiliser pour la requête. Si vous gérer des sites ou applications qui sont réparties sur des groupes de serveurs différents, il faut avoir autant de « backen » que de groupes de serveur.
Au final on passe donc la requête aux serveurs web qui vont effectivement traiter la requête.
La réponse fera le chemin inverse,en revenant à HAProxy, puis Varnish (qui décidera q'il met la réponse en cache), puis HAProxy puis le client HTTP qui a fait initialement la requête.
La réponse fera le chemin inverse, en revenant à HAProxy, puis Varnish (qui décidera q'il met la réponse en cache), puis HAProxy puis le client HTTP qui a fait initialement la requête.
1. HAProxy frontend external
2. HAProxy backend varnish
Chaîne :
1. HAProxy frontend « external » (général)
2. HAProxy backend « varnish »
3. Varnish
4. HAProxy frontend internal
5. HAProxy backend internal
4. HAProxy frontend « internal » (général)
5. HAProxy backend « site X » (par site)
6. Web-server
## Fallback if Varnish is DOWN
Dans le configuration de HAProxy, nous avons créé un backend Varnish, avec un seul serveur final.
Dans la configuration de HAProxy, nous avons créé un backend Varnish, avec un seul serveur final.
```
backend varnish
@ -62,7 +64,7 @@ backend varnish
L'option `httpchk` permet à HAProxy de vérifier l'état de santé de Varnish, au niveau 7.
Dans la configuration de Varnish on retrouve l'URL `/vacnishcheck` :
Dans la configuration de Varnish on retrouve l'URL `/varnishcheck` :
```
sub vcl_recv {
@ -75,7 +77,7 @@ sub vcl_recv {
```
La réponse est extrêmement rapide et permet de faire une vérification chaque seconde.
Dans HAProxy, au niveau du frontend il y a une ACL qui permet de savoir si Varnish est disponible ou pas.Nous pouvons donc transmettre à Varnish s'il est dispo ou le contourner s'il est absent :
Dans HAProxy, au niveau du frontend il y a une ACL qui permet de savoir si Varnish est disponible ou pas. Nous pouvons donc transmettre à Varnish s'il est dispo ou le contourner s'il est absent :
```
frontend external
@ -103,11 +105,11 @@ backend example_com
### Pourquoi ?
L'utilisation du PROXY protocol n'est pas du tout indispensable mais elle ajoute un confort significatif dans la gestion de toute cette chaîne.
L'utilisation du PROXY protocol n'est pas du tout indispensable, mais elle ajoute un confort significatif dans la gestion de toute cette chaîne.
Dans leur rôle de proxy intermédiaire, HAProxy et Varnish sont vus comme des clients au niveau TCP ; entre eux, mais surtout vis-à-vis du serveur web final. Si on ne fait rien de particulier,le serveur final verra toujours l'IP d'HAProxy comme IP du client. Ça rend impossible l'application de autorisations ou restrictions par IP. Ça rend difficile le suivi des requêtes, les statistiques…
Dans leur rôle de proxy intermédiaire, HAProxy et Varnish sont vus comme des clients au niveau TCP ; entre eux, mais surtout vis-à-vis du serveur web final. Si on ne fait rien de particulier,le serveur final verra toujours l'IP d'HAProxy comme IP du client. Ça rend impossible l'application dautorisations ou restrictions par IP. Ça rend difficile le suivi des requêtes, les statistiques…
Vous me direz qu'on a pour ça l'en-tête HTTP `X-Forwarded-For`, mais elle ets invisible ua niveau TCP, et sa prise en compte au niveau applicatif n'est pas toujours possible.
Vous me direz qu'on a pour ça l'en-tête HTTP `X-Forwarded-For`, mais elle est invisible ua niveau TCP, et sa prise en compte au niveau applicatif n'est pas toujours possible.
Le PROXY protocol est une simple extension de TCP qui permet d'ajouter cette notion d'IP réelle du client.
Cela impose que les 2 parties de l'échange sachent gérer cette extension, mais à partir de là il n'est plus besoin de le gérer dans la couche applicative.
@ -163,7 +165,7 @@ Si la liaison finale entre HAProxy et le serveur final se fait aussi avec le PRO
### X-Forwarded-*
Bien que nous utilisons le PROXY protocol en interne (et éventuellement vers certains serveurs finaux), nous conservons le'en-tête HTTP `X-Forwarded-For`.
Bien que nous utilisions le PROXY protocol en interne (et éventuellement vers certains serveurs finaux), nous conservons le'en-tête HTTP `X-Forwarded-For`.
Nous ajoutons aussi `X-Forwarded-Port` pour indiquer à quel port de HAProxy le client s'est adressé.
Et enfin nous ajoutons `X-Forwarded-Proto` pour indiquer si la connexion initiale était chiffrée ou pas.
@ -193,15 +195,15 @@ frontend external
Il n'y a pas de réel consensus sur le nommage de l'en-tête. On trouve souvent `X-Unique-ID` ou `X-Request-ID`.
## X-Boost-*
### X-Boost-*
Boost est le nom que nous avons donné à notre système basé sur HAProxy et Varnish.
Nous ajoutons donc plusieurs en-têtes `X-Boost-*` pour ajouter des informations utiles.
Sur le frontend « external » nous marquons la requête comme étant passée en étape 1 par « haproxy-external ». Les autres étapes de la requêtes sauront alors que la requête est entrée par là.
Sur le frontend « external » nous marquons la requête comme étant passée en étape 1 par « haproxy-external ». Les autres étapes de la requête sauront alors que la requête est entrée par là.
Lorsque réponse resortira de ce backend pour aller au client, on la marque aussi pour indiquer que l'étape 1 était « haproxy-external » en précisant si la connexion était en http ou https.
On insique ausis le nom du serveur Boost qui a traité la requête.
On indique aussi le nom du serveur Boost qui a traité la requête.
```
frontend external
@ -223,7 +225,7 @@ frontend internal
http-response add-header X-Boost-Step3 "haproxy-internal; no SSL to backend" if !{ ssl_bc }
```
Dnas le backend final du site, on marque la réponse pour indiquer si la connexion avec le serveur final s'est faite en http ou https.
Dans le backend final du site, on marque la réponse pour indiquer si la connexion avec le serveur final s'est faite en http ou https.
```
backend example_com
@ -271,13 +273,127 @@ frontend internal
:warning: Il vaut mieux ne pas activer cela en production, mais ça peut être très utile pour permettre à un client en mode test/préprod de vérifier comment se comporte le proxy.
## Additional features
## Haute disponibilité
* reject request at TCP-level for maximum efficiency
* reject request at HTTP-level for maximum customizability
* per-site maintenance mode (with IP-whitelist bypass)
* customizable DEBUG level in HTTP headers
* per-site custom error response
* Let's Encrypt challenge pass-through
### Côté applicatif
Lorsque le site ou l'application web final dispose de plusieurs serveurs pour gérer les requêtes, on peut prendre en charge directement dans HAProxy la répartition de charge.
Lorsque c'est possible, on fait de préférence un `round-robin` sur les serveurs web.
Lorsque l'application gère mal les sessions ou des aspects bloquants, on met un serveur en actif et les autres en backup, pour basculer sur un secours si le primaire n'est plus disponible.
### Côté HAProxy
Pour éviter que HAProxy + Varnish ne soient un « Single Point Of Failure », nous avons mis en place 2 serveurs différents dans 2 réseaux différents et nous faisons un round-robin DNS en amont.
Cette approche n'est pas la plus avancée, car en cas de panne d'un serveur il faut intervenir au niveau DNS. Cependant elle a le mérite de la simplicité et les pannes complète de machines virtuelles tournant sur de la virtualisation redondante sont heureusement très rares. En revanche, ce système nous permet pas mal de souplesse.
Il serait aussi possible de faire du « actif-passif » en mettant une IP flottante (keepalived/vrrp) en amont de deux serveurs, pour avoir une bascule automatique sur le secondaire en cas de panne du primaire.
On peut aussi faire du « actif-actif » avec 2 HAProxy en « layer 4 » qui renvoient ensuite sur 2 HAproxy en « layer 7 »
Ces approches permettent une reprise d'activité quasi immédiate en cas de panne, mais elles impliquent plus de ressources et une topologie réseau particulière.
## Fonctionnalités complémentaires
### Filtrage TCP
Dans le frontend « external » qui gère le trafic entrant, nous pouvons vérifier si le client doit être immédiatement rejeté, par exemple selon son adresse IP :
```
frontend external
[…]
# Reject the request at the TCP level if source is in the denylist
tcp-request connection reject if { src -f /etc/haproxy/deny_ips }
```
Cela ne remplace pas un vrai firewall, mais ça permet de facilement exclure des client au niveau TCP (couche 4).
### Filtrage HTTP
En analysant la requête au niveau HTTP (couche 7), on peut filtrer de manière beaucoup plus fine.
Par exemple, si un site est passé en mode maintenance (détaillé plus loin), on peut contourner ce mode maintenance pour une liste d'IP particulière qui pourra tout de même consulter le site.
```
frontend external
[…]
# List of IP that will not go the maintenance backend
acl maintenance_ips src -f /etc/haproxy/maintenance_ips
# Go to maintenance backend, unless your IP is whitelisted
use_backend maintenance if !maintenance_ips
backend maintenance
http-request set-log-level silent
# Custom 503 error page
errorfile 503 /etc/haproxy/errors/maintenance.http
# If no server is defined, a 503 is returned for every request
```
Pour les outils locaux de monitoring (exemple: Munin) ou pour les challenges ACME, il peut être utile de renvoyer sur un serveur web local (exemple: Apache ou Nginx), au lieu de renvoyer sur Varnish ou les serveurs web appliatifs.
```
frontend external
[…]
# Is the request coming for the server itself (stats…)
acl server_hostname hdr(host) -i my-hostname
acl munin hdr(host) -i munin
# Detect Let's Encrypt challenge requests
acl letsencrypt path_dir -i /.well-known/acme-challenge
use_backend local if server_hostname
use_backend local if munin
use_backend letsencrypt if letsencrypt
backend letsencrypt
# Use this if the challenge is managed locally
server localhost 127.0.0.1:81 send-proxy-v2 maxconn 10
# Use this if the challenge is managed remotely
### server my-certbot-challenge-manager 192.168.2.1:80 maxconn 10
backend local
option httpchk HEAD /haproxy-check
server localhost 127.0.0.1:81 send-proxy-v2 maxconn 10
```
### Mode maintenance
Le mode maintenance est une sorte de coupe-circuit qui permet de basculer toute l'installation, ou juste un site web en mode maintenance.
Pour la coupure globale, on utilise un backend spécial qui ne définit pas de serveur web final et provoquera toujours une erreur 503.
On peut aussi définir une page d'erreur particulière pour cette situation.
Nous avons fait le choix de ne pas logguer les requêtes lorsque ce mode et activé.
```
frontend external
[…]
# List of IP that will not go the maintenance backend
acl maintenance_ips src -f /etc/haproxy/maintenance_ips
# Go to maintenance backend, unless your IP is whitelisted
use_backend maintenance if !maintenance_ips
backend maintenance
http-request set-log-level silent
# Custom 503 error page
errorfile 503 /etc/haproxy/errors/maintenance.http
# If no server is defined, a 503 is returned for every request
```
Pour une coupure par site, il faut définir un backend spécial pour ce site et activer l'utilisation de ce backend pour les requêtes.
```
frontend external
[…]
acl example_com_domains hdr(host) -i example.com
acl maintenance_ips src -f /etc/haproxy/maintenance_ips
acl example_com_maintenance_ips src -f /etc/haproxy/example_com/maintenance_ips
use_backend example_com_maintenance if example_com_domains !example_com_maintenance_ips !maintenance_ips
```
### Personnalisation par site
On peut définir des ACL pour chaque site et ainsi provoquer des comportements lorsqu'une requête est destinée à ce site.
Par exemple, les redirections http vers https, ou pour des sous-domaines, les en-têtes HSTS
## High-availability