Reverse Proxy Nginx
Reverse Proxy
Présentation des reverse-proxies
Les reverse-proxies servent d'intermédiaire entre les utilisateurs externes et le réseau local. Ils ont le rôle de passerelle entre les réseaux externes et le réseau local, et permettent de contrôler les flux à destination des serveurs web internes (au contraire des proxies qui contrôlent le flux depuis l'intérieur vers l'extérieur). Implémenter des reverse-proxies présente plusieurs avantages :
- Sécuriser les serveurs web en les protégeant des attaques directes depuis l'extérieur, et en masquant l'architecture
- Mettre en cache des données statiques
- Faire du Load-balancing
- Faire du Fail-Over
- Compresser les données
Paramétrage des clients
Les clients n'ont pas besoin de configuration particulière, le reverse-proxy est transparent pour eux. On en rencontre en réalité souvent sur Internet, sans en avoir conscience.
Côté serveur
Côté serveur, il est nécessaire d'avoir un cluster de serveurs web. La répartition de charge permet de répartir les requêtes, pour éviter les ralentissements ou plantages serveur. Le Failover quant à lui, se fait parce que le reverser-proxy communique en permanence avec les serveurs internes pour connaître leur disponibilité; le reverse-proxy s'adapte et relaie les requêtes vers les serveurs qui sont fonctionnels (pas de coupure de service !). Pour ce qui est du DNS, la résolution de nom renvoie vers le proxy (et pas un des serveurs web). Le proxy redistribue alors les requêtes.
L'exemple de NginX
Nginx est à la fois un serveur web, et un reverse-proxy. Nous allons ici voir comment le configurer en tant que reverse-proxy pour le web (http et https). Il est capable de renvoyer les requêtes vers un autre serveur web local (qui écoute sur un port autre que 80) ou vers d'autres serveurs de son réseau interne.
Installation et commandes de pilotage de Nginx
L'installation est basique :
sudo apt install nginx
Les commandes de pilotage sont les suivantes :
service nginx {configtest|force-reload|reload|restart|rotate|start|status|stop|upgrade}
On peut tester sa configuration avec :
nginx -t
Configuration de Nginx
À partir d'ici, on verra comment mettre en place le reverse-proxy pour deux sites hébergés sur deux serveurs différents, ainsi que la mise en place du cache et de la compression des données entre le reverse-proxy et le client distant.
Les fichiers de configuration sont placés par défaut dans /etc/nginx.
On va commencer par la configuration globale, dans nginx.conf :
user www-data; # user qui fait tourner nginx worker_processes 4; # nb d'instance nginx (à adapter, 1 par core CPU) # fichier dans lequel est enregistre le pid de nginx (celui du master process, qui lance les workers) pid /run/nginx.pid; events { worker_connections 1024; # nb de connexion max par worker (à adapter) } http { sendfile on; # cf complement pour plus d'infos tcp_nopush on; # cf complement pour plus d'infos tcp_nodelay on; # cf complement pour plus d'infos server_tokens off; # masque la version de nginx dans les messages # delai d'attente en seconde avant la fermeture d'une connexion keepalive_timeout 65; include /etc/nginx/mime.types; # liste des types MIME (type de fichier par extension) default_type application/octet-stream; # type par defaut des fichiers non repertories dans mime.type access_log /var/log/nginx/access.log; # fichier de log des acces error_log /var/log/nginx/error.log; # fichier de log des erreurs include /etc/nginx/conf.d/*.conf; # inclusion des fichiers de configuration (on trouvera notamment le fichier de conf pour le reverse proxy) include /etc/nginx/sites-enabled/*; # configuration des sites web actifs }
Pour plus d'explications sur les directives : http://nginx.org/en/docs/http/ngx_http_core_module.html
Quelques détails supplémentaires (from : https://thoughts.t37.net/optimisations-nginx-bien-comprendre-sendfile-tcp-nodelay-et-tcp-nopush-2ab3f33432ca):
- tcp_nodelay : Par défaut, un paquet TCP peut attendre jusqu'à 0.2 secondes avant d'être envoyé, si il est trop petit, par exemple. Cette option force nginx à envoyer directement les données, sans attendre de faire des paquets tcp assez gros. En effet, ce comportement par défaut cause une latence sur le dernier paquet : il est rare qu'un fichier fasse un nombre exact de paquets, et le dernier se retrouve en général à être un peu petit (genre, un octet de données pour 40 octets de headers TCP + IP).
- tcp_nopush : Cette directive fait un peu le contraire de tcp_nodelay : au lieu d'optimiser les délais d'envoi, elle optimise la quantité d'informations envoyées en une seule fois. tcp_nopush existe dans pile TCP de FreeBSD, mais sous Linux, elle correspond à TCP_CORK. CORK oblige tcp à envoyer des paquets qui font la taille de la MSS (Maximum Segment Size), soit la MTU moins les en-têtes TCP et IP.
- sendfile : Cette directive fait la force de Nginx avec les deux autres. Elle oblige l'utilisation de l'appel système sendfile pour tous les envois de fichiers. Je passe les détails incompréhensibles, mais en gros, c'est plus efficace.
Compression des données
Pour le moment, le cache et la compression des données ne sont pas configurés. On va donc configurer la compression des données (la configuration du cache se fera dans le fichier de configuration proxy.conf).
Pour gérer la compression des données, on rajoute les lignes suivantes dans la partie http du fichier de configuration :
gzip on; # active la compression gzip_comp_level 5; # niveau de compression (de 1 a 9, 9 pour la compression max) gzip_http_version 1.0; # version HTTP minimum pour que la compression soit active gzip_proxied any; # active la compression pour les requetes faites par les clients qui passent par un proxy gzip_types text/plain text/html text/css image/x-icon application/x-javascript; # types MIME pour lesquels la compression est active gzip_disable "MSIE [1-6]\.(?!.*SV1)"; # désactive la compression pour les navigateurs qui ne supportent pas
Plus de détails sur la compression : http://nginx.org/en/docs/http/ngx_http_gzip_module.html
Reverse-proxy
Pour les directives relatives au reverse-proxy, on va, pour plus de clarté, créer un fichier à part. On y définira aussi les options de cache. On va créer le fichier /etc/nginx/conf.d/proxy.conf et y mettre les lignes suivantes :
proxy_redirect off; # desactive la reecriture d'url (inutile ici, les sites sont heberges sur d'autres serveurs) proxy_set_header Host $host; # permet de modifier l'entete HTTP proxy_set_header X-Real-IP $remote_addr; # permet de modifier l'entete HTTP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # permet de modifier l'entete HTTP proxy_hide_header X-Powered-By; # cache le champ X-Powered-By de l'entête HTTP proxy_intercept_errors on; # permet d'intercepter les réponses avec un code de retour supérieur ou égal à 300 proxy_cache_path /var/cache/nginx/site1 levels=1:2 keys_zone=site1:10m inactive=7d max_size=700m; # definit le chemin et les parametres du cache pour le site 1 proxy_cache_path /var/cache/nginx/site2 levels=1:2 keys_zone=site2:10m inactive=7d max_size=700m; # definit le chemin et les parametres du cache pour le site 2 proxy_cache_path /var/cache/nginx/site3 levels=1:2 keys_zone=site3:10m inactive=7d max_size=700m; # definit le chemin et les parametres du cache pour le site 3
Ici, les directives proxy_set_header permettent de réécrire le header pour que les serveurs web voient les requêtes arriver du client, plutôt que du proxy.
Déclaration des serveurs arrières (backend)
On ne prendra ici pour exemple qu'un seul serveur arrière.
Deux dossiers importants :
- /etc/nginx/sites-available : Contient les configurations des sites (tous les sites, même inactifs)
- /etc/nginx/sites-enabled : contient des liens symboliques vers les fichiers de configuration des sites actifs (même principe qu'Apache).
On va ici voir l'exemple du fichier de site reverse-site1. C'est un fichier de configuration standard de Nginx. Il est à placer dans available avec un lien dans enable, comme pour Apache. Attention, ce fichier est bien sur le proxy, par sur les serveurs webs en eux-même !
server { listen 80; # port d'ecoute server_name www.site1.domain site1.domain; # nom du serveur arriere (ici repondra sites www.site1.domain et site1.domain if ($request_method !~ ^(GET|HEAD|POST)$ ) { # Si la requete n'est pas de type GET ou HEAD ou POST return 444; # on renvoie le code 444 (no data), permet de fermer la connexion } location / { proxy_cache site1; # nom de la zone de cache proxy_cache_valid 200 302 2d; # duree du cache pour les codes de retour 200 et 302 (ici 2 jours) proxy_cache_valid 404 1m; # duree du cache pour les code de retour 404 (ici 1 minute) proxy_pass http://www.site1.domain/; # url du serveur arriere } access_log /var/log/nginx/site1-access.log; # fichier de log des acces error_log /var/log/nginx/site1-error.log; # fichier de log des erreurs }
Load Balancing
Le load balancing se fait avec la directive upstream. Par défaut, Nginx fonctionne en mode round-robin (chaque serveur est interrogé à tour de rôle). Un exemple ici de la doc (http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream)
upstream '''backend''' { server backend1.example.com weight=5; server backend2.example.com:8080; server unix:/tmp/backend3; server backup1.example.com:8080 backup; server backup2.example.com:8080 backup; } server { location / { proxy_pass http://'''backend'''; } }
Quelques explications :
- server suivi d'une IP:un nom de domaine et d'un port (éventuel) permet d'indiquer un serveur backend;
- weight correspond au poids du serveur (par défaut, 1). Avec l'exemple du dessus, pour 7 requêtes, 5 vont au premier serveur, une au deuxième, et une au troisième.
- backup marque le serveur en question comme serveur de backup, utilisé seulement si els serveurs primaires sont indisponibles.
- Bien d'autres options sont disponibles.
Mise en place d'un reverse-proxy
Nous allons mettre en place un reverse-proxy, en nous basant sur cette architecture :
Le serveur proxy sera la même machine que j'ai utilisé pour Squid plus tôt; l'utilisateur externe sera en fait ma machine hôte, avec l'IP donnée par le DHCP de la box.
Je commence par mettre en place mes 4 serveurs web, des Debian avec Apache. Rien de compliqué, mais je m'assure que le site01 et le site02 aient une page d'acceuil différente (je ne crée pas d'hôte virtuel : les sites doivent répondre directement depuis leur IP). Une fois que c'est fait et que je les ai testés (avec un client...), je passe à la configuration du reverse-proxy.
C'est parti pour les différents fichiers !
/etc/nginx.conf
#/etc/nginx/nginx.conf user www-data; # user qui fait tourner nginx worker_processes 2; # nb d'instances nginx (ici, ma VM a deux coeurs CPU) # fichier dans lequel est enregistre le pid de nginx (celui du master process, qui lance les workers) pid /run/nginx.pid; events { worker_connections 4; # nb de connexion max par instance (j'en mets peu, pour l'exemple) } http { sendfile on; # Optimisation des performances tcp_nopush on; # Optimisation des performances tcp_nodelay on; # Optimisation des performances server_tokens off; # masque la version de nginx dans les messages keepalive_timeout 65; # delai d'attente en seconde avant la fermeture d'une connexion include /etc/nginx/mime.types; # liste des types MIME (type de fichier par extension) default_type application/octet-stream; # type par defaut des fichiers non repertories dans mime.type access_log /var/log/nginx/access.log; # fichier de log des acces error_log /var/log/nginx/error.log; # fichier de log des erreurs include /etc/nginx/conf.d/*.conf; # inclusion des fichiers de configuration (on trouvera notamment proxy.conf) include /etc/nginx/sites-enabled/*; # configuration des sites web actifs ###Compression gzip on; # active la compression gzip_comp_level 5; # niveau de compression (de 1 a 9, 9 pour la compression max) gzip_http_version 1.0; # version HTTP minimum pour que la compression soit active gzip_proxied any; # active la compression pour les requetes faites par les clients qui passent par un proxy gzip_types text/plain text/html text/css image/x-icon application/x-javascript; # types MIME pour lesquels la compression est active gzip_disable "MSIE [1-6]\.(?!.*SV1)"; # désactive la compression pour les navigateurs qui ne supportent pas }
/etc/nginx/conf.d/proxy.conf
#/etc/nginx/conf.d/proxy.conf #Ici se trouve la configuration générale du reverse-proxy ###Options générales proxy_redirect off; # desactive la reecriture d'url (inutile ici, les sites sont heberges sur d'autres serveurs) proxy_set_header Host $host; # permet de modifier l'entete HTTP proxy_set_header X-Real-IP $remote_addr; # permet de modifier l'entete HTTP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # permet de modifier l'entete HTTP proxy_hide_header X-Powered-By; # cache le champ X-Powered-By de l'entête HTTP proxy_intercept_errors on; # permet d'intercepter les réponses avec un code de retour supérieur ou égal à 300 ###Options de cache proxy_cache_path /var/cache/nginx/site01 levels=1:2 keys_zone=site1:10m inactive=7d max_size=700m; # definit le chemin et les parametres du cache pour le site 1 proxy_cache_path /var/cache/nginx/site02 levels=1:2 keys_zone=site2:10m inactive=7d max_size=700m; # definit le chemin et les parametres du cache pour le site 2
/etc/nginx/sites-available/site01.conf
#/etc/nginx/sites-available/site01.conf upstream site01 { #Ici, je configure mes deux serveurs backend server 192.168.56.101:80 weight=2; #Répartition de charge : ce serveur (srv1-backend) recoit 2/3 des requêtes server 192.168.56.102:80 weight=1; #Répartition de charge : ce serveur (srv2-backend) recoit 1/3 des requêtes } server { #Ici, je configure le site en lui même listen 80; #Le port sur lequel Nginx écoute server_name www.site01.mondomaine site01.mondomaine; #L'adresse de ce site pour le client location / { #Ce bloc contient les directives de fichiers... proxy_cache site1; #Le cache est "site1" défini dans proxy.conf proxy_cache_valid 200 302 2d; #Cache valide 1 jour pour les pages qui fonctionnent proxy_cache_valid 404 1m; #Cache valide 1 minute pour les erreurs 404 proxy_pass http://site01; #On renvoie vers le bloc "upstream site01", soit vers nos serveurs backend } }
/etc/nginx/sites-available/site02.conf
#/etc/nginx/sites-available/site02.conf upstream site02 { #Ici, je configure mes deux serveurs backend, à poids égal (par défaut, weight=1) server 192.168.56.103:80; #Répartition de charge : ce serveur (srv3-backend) reçoit la moitié des requêtes server 192.168.56.104:80; #Répartition de charge : ce serveur (srv3-backend) reçoit la moitié des requêtes } server { #Ici, je configure le site en lui même listen 80; #Le port sur lequel Nginx écoute server_name www.site02.mondomaine site02.mondomaine; #L'adresse de ce site pour le client location / { #Ce bloc contient les directives de fichiers... proxy_cache site2; #Le cache est "site1" défini dans proxy.conf proxy_cache_valid 200 302 2h; #Cache valide 1 jour pour les pages qui fonctionnent proxy_cache_valid 404 1m; #Cache valide 1 minute pour les erreurs 404 proxy_pass http://site02; #On renvoie nos deux serveurs backend } }
Création du dossier de cache
Il faut créer le dossier de cache et lui filer les droits :
mkdir /var/cache/nginx cd /var/cache/nginx mkdir site01 site 02 cd .. chown -R www-data:www-data nginx
Tests et activation
Notre site est enfin en place ! Il n'y a plus qu'à tester. D'abord les fichiers de configurations :
nginx -t #Doit dire que c'est bon
Ensuite, on redémarre nginx :
systemctl restart nginx systemctl status nginx #Très important : nginx -t ne renvoie pas toutes les erreurs !
Plus qu'à se rendre sur un navigateur client (côté WAN) pour tester.
Si le cache pose problème, il est possible de vider ses dossiers !