Add some content (in french)

This commit is contained in:
Jérémy Lecour 2022-09-18 22:38:33 +02:00 committed by Jérémy Lecour
parent d81d0a7307
commit e6906c6336

264
README.md
View file

@ -1,6 +1,46 @@
# haproxyconf-2022
## Request lifecycle
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.
Nous allons commencer par les principes généraux et plonger petit à petit dans les détails.
## Pourquoi HAProxy et Varnish ?
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 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.
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…
Je connais mieux HAProxy que Varnish, donc pour cette fois je vais donner plus de détails sur HAProxy.
## Comment combiner HAProxy et Varnish
HAProxy fonctionne selon le principe de « frontend/backend ».
Un « frontend » est un point d'entrée HTTP (ou TCP) sur un ou plusieurs couples « IP:PORT ».
En mode HTTP il permet d'analyser tous les éléments de la requête pour prendre des décisions.
Il passe ensuite la requête à un « backend » qui définit la façon dont on parle à l'élément suivant dans la chaîne. Le plus souvent il s'agit d'un ou plusieurs serveurs d'application. On y gère les éventuelles règles de répartition de charge ou de bascule en cas d'incident, les mécanismes de surveillance de disponibilité…
Dans notre cas, pour l'exemple typique, c'est à Varnish que HAProxy va passer la requête.
Nous avons choisi de placer HAProxy et Varnish sur le même serveur, et de les faire communiquer par socket, mais c'est un détail et rien n'empêche de les mettre sur des serveurs différents.
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.
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.
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.
1. HAProxy frontend external
2. HAProxy backend varnish
@ -9,8 +49,228 @@
5. HAProxy backend internal
6. Web-server
## Fallback if Varnish is DOWN
Dans le configuration de HAProxy, nous avons créé un backend Varnish, avec un seul serveur final.
```
backend varnish
option httpchk HEAD /varnishcheck
server varnish_sock /run/varnish.sock check observe layer7 maxconn 3000 inter 1s send-proxy-v2
```
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` :
```
sub vcl_recv {
# HAProxy check
if (req.url == "/varnishcheck") {
return(synth(200, "Hi HAProxy, I'm fine!"));
}
[…]
}
```
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 :
```
frontend external
# Is the request routable to Varnish ?
acl varnish_available nbsrv(varnish) gt 0
# Is the request comming for example.com ?
acl example_com_domains hdr(host) -i example.com
# Use Varnish if available
use_backend varnish if example_com_domains varnish_available
# … or use normal backend
use_backend example_com if example_com_domains
backend varnish
option httpchk HEAD /varnishcheck
server varnish_sock /run/varnish.sock check observe layer7 maxconn 3000 inter 1s send-proxy-v2
backend example_com
server example-hostname 1.2.3.4:443 check observe layer4 ssl
```
## PROXY protocol
### 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.
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…
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.
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.
### Comment?
Au niveau HAProxy, nous retrouvons celà dans le backend qui transmet à Varnish, dans le frontend de retour depuis Varnish et éventuellement dans le backend qui transmet au serveur web final.
```
backend varnish
option httpchk HEAD /varnishcheck
server varnish_sock /run/varnish.sock check observe layer7 maxconn 3000 inter 1s send-proxy-v2
frontend internal
bind /run/haproxy-frontend-default.sock user root mode 666 accept-proxy
backend example_com
server example-hostname 1.2.3.4:443 check observe layer4 ssl verify none send-proxy-v2
```
Lorsqu'HAproxy est client on ajoute `send-proxy-v2` et lorsqu'il est serveur on ajoute `accept-proxy`.
Pour Varnish, l'écoute se gère dans la ligne de commande de démarrage avec l'option `PROXY` :
```
/usr/sbin/varnishd […] -a /path/to/varnish.sock,PROXY […]
```
Et pour le retour vers HAProxy, c'est dans la configuration, avec l'option `proxy_header = 1` :
```
backend default {
.path = "/run/haproxy-frontend-default.sock";
.proxy_header = 1;
[…]
}
```
### Debug sans PROXY protocol
Bien que ça soit une optimisation très appréciable, elle n'est pas compatible avec de nombreux outils qui peuvent avoir besoin de se connecter à HAProxy ou Varnish de manière classique.
C'est particulièrement utile au niveau de Varnish pour faire du debug sans passer par HAProxy. On ajoute alors à la ligne de commande de démarrage un autre point d'entrée. Nous le faisons uniquement en local, sur un port inaccessible à l'extérieur :
```
/usr/sbin/varnishd […] -a 127.0.0.1:82 […]
```
Si la liaison finale entre HAProxy et le serveur final se fait aussi avec le PROXY protocol, alors on peut faire la même chose que dans Varnish et mettre une écoute protégée et sans PROXY protocol.
## HTTP tagging
### 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`.
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.
```
frontend external
bind 0.0.0.0:80,:::80
bind 0.0.0.0:443,:::443 ssl […]
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
http-request set-header X-Forwarded-Proto https if { ssl_fc }
```
### X-Unique-ID
Il est très utile de marquer une requête entrante d'un identifiant unique qui pourra être transmis d'étape en étape jusqu'au serveur final (et pourquoi pas au retour aussi).
```
frontend external
[…]
http-request set-header X-Unique-ID %[uuid()] unless { hdr(X-Unique-ID) -m found }
```
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-*
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à.
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.
```
frontend external
[…]
http-request add-header X-Boost-Step1 haproxy-external
http-response add-header X-Boost-Step1 "haproxy-external; client-https" if { ssl_fc }
http-response add-header X-Boost-Step1 "haproxy-external; client-http" if !{ ssl_fc }
http-response set-header X-Boost-Server my-hostname
```
Au niveau du frontend « internal » on applique le même principe, mais en indiquant que c'est pour l'étape 3.
frontend internal
[…]
http-request add-header X-Boost-Step3 haproxy-internal
http-response add-header X-Boost-Step3 "haproxy-internal; SSL to backend" if { ssl_bc }
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.
```
backend example_com
[…]
http-response set-header X-Boost-Proto https if { ssl_bc }
http-response set-header X-Boost-Proto http if !{ ssl_bc }
server example-hostname 1.2.3.4:443 check observe layer4 ssl verify none
```
Au niveau de Varnish, nous marquons la requête transmise pour indiquer qu'elle est passée par Varnish :
```
sub vcl_recv {
[…]
set req.http.X-Boost-Layer = "varnish";
}
```
Lorsque la réponse est renvoyée, elle est marquée sur l'étape 2 de détails à propos des modalités de cache
sub vcl_deliver {
[…]
if (resp.http.Set-Cookie && resp.http.Cache-Control) {
set resp.http.X-Boost-Step2 = "varnish WITH set-cookie AND cache-control on backend server";
} elseif (resp.http.Set-Cookie) {
set resp.http.X-Boost-Step2 = "varnish WITH set-cookie and NO cache-control on backend server";
} elseif (resp.http.Cache-Control) {
set resp.http.X-Boost-Step2 = "varnish with NO set-cookie and WITH cache-control on backend server";
} else {
set resp.http.X-Boost-Step2 = "varnish with NO set-cookie and NO cache-control on backend server";
}
```
### Log HAProxy complet
Dans des situations de debug avancées, nous pouvons aussi activer l'ajout dans un en-tête HTTP de la totalité de la ligne de log :
```
frontend external
http-response add-header X-Haproxy-Log-external "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
frontend internal
http-response add-header X-Haproxy-Log-Internal "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
```
: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
* reject request at TCP-level for maximum efficiency
@ -19,3 +279,5 @@
* customizable DEBUG level in HTTP headers
* per-site custom error response
* Let's Encrypt challenge pass-through
## High-availability