Augmenter les performances de Nginx
Le 5/16/2021

# Préambule

  • Ce tutoriel s’adresse aux gens sous GNU/Linux, il a Ă©tĂ© fait sous Debian 10 (avec un compte root).
  • Il est possible que certaines commandes changent suivant votre distribution (et surtout de votre package manager) ou qu’il y ai besoin d’utiliser sudo.

# Installation des prérequis

apt update && apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
bash

# Installation de Nginx

On va ici utiliser directement les repositories de Nginx plutĂŽt que ceux de Debian:

# On va dans /tmp pour ne pas polluer notre ~home
cd /tmp
# On télécharge la clef
wget https://nginx.org/keys/nginx_signing.key
# On ajoute la clef précedement téléchargée
apt-key add nginx_signing.key
# OK
# On ajoute le repository de Nginx dans nos sources
nano /etc/apt/sources.list.d/nginx.list
bash

Ă  ajouter dans /etc/apt/sources.list.d/nginx.list

deb http://nginx.org/packages/mainline/debian buster nginx
deb-src https://nginx.org/packages/mainline/debian/ buster nginx
/etc/apt/sources.list.d/nginx.list

Et enfin

apt update && apt install nginx
bash

# Tuning de la configuration

Le point d’entrĂ©e de la configuration de Nginx est /etc/nginx/nginx.conf.

# block racine

# worker_processes

Par dĂ©faut cette directive est Ă  1, celĂ  veux dire qu’il n’y a qu’un seul processus Nginx qui tourne, or de nos jours il est frĂ©quent d’avoir des serveurs avec plus d’un core CPU.
Malheureusement un processus Nginx n’est pas capable d’utiliser plus d’un core à la fois 

Heureusement il est possible de faire tourner plusieurs processus de Nginx, en plus c’est super simple avec la directive worker_processes.

Le plus simple est de dĂ©finir worker_processes Ă  auto, cela va crĂ©er autant de processus Nginx qu’il y a de core.

Mais attends ?!
Comment c’est possible que plusieurs processus Ă©coutent sur le mĂȘme port ?

  1. Nginx se lance une 1Ăšre fois en root (bien obligĂ© pour Ă©couter sur les ports infĂ©rieurs Ă  1024 sans utiliser CAP_NET_BIND_SERVICE) et bind les sockets (Ă©coute sur le port 80 par exemple pour faire simple), on l’appelle master.
  2. Puis il se dĂ©double en autant d’exemplaire que dĂ©fini par la directive worker_processes, se sont des workers.
  3. Les workers changent d’utilisateur conformĂ©ment Ă  la directive user.

L’astuce rĂ©side dans le fork (le dĂ©doublage), le processus pĂšre partage toutes les ressources qu’il a Ă  son nouveau fils, y compris fichiers et ports ouverts.
en rĂ©alitĂ© fichier et ports sont la mĂȘme chose sous Unix, se sont des files descriptor 
 mais ça c’est un autre sujet.

Illustration nginx master / workers

# block events

# multi_accept

si multi_accept est Ă  on un processus worker va accepter toutes les nouvelles connexions en mĂȘme temps, au contraire si si la directive est Ă  off il ne pourra en accepter qu’une Ă  la fois.
Par défaut cette directive est sur off.

# worker_connections

rĂšgle le nombre maximal de connexions simultanĂ©es qu’un worker peut ouvrir en mĂȘme temps.
Il faut garder Ă  l’esprit que ça inclue toutes les connexions (connexion avec un backend proxiĂ© par exemple).

On peut augmenter ce paramĂštre Ă  1024 sans trop de soucis.
Par dĂ©faut ce paramĂštre est Ă  512 requĂȘtes simultanĂ©es par workers.

# block http

# sendfile

Utilise l’appel kernel sendfile dans le cas oĂč nginx doit servir des fichiers statiques, plutĂŽt qu’une combinaison de write et read. Par dĂ©faut ce paramĂštre est Ă  off.

# aio

Permet de ne pas bloquer la boucle d’évĂ©nement du processus worker dans le cas oĂč une opĂ©ration bloquante est en cours (lecture de fichier par exemple)
Par défaut cette directive est à off.

# tcp_nopush

Permet d’envoyer les entĂȘtes HTTP et le dĂ©but (ou la totalitĂ©) du fichier en un seul packet.
Par défaut cette directive est à off.

# gzip

Permet de compresser la réponse renvoyée par Nginx. Par défaut cette directive est à off.

# gzip_proxied

Permet de compresser les rĂ©ponses pour des requĂȘtes proxiĂ©es suivant les entĂȘtes HTTP
Je conseille d’utiliser any pour tout le temps activer la compression. Par dĂ©faut cette directive est Ă  off.

# gzip_comp_level

Permet de plus ou moins compresser la réponse, une valeur élevée entrainera une consommation CPU plus importante
Le juste milieu entre usage CPU et usage réseau se situe au alentour de 5/6.
Par défaut le niveau est à 1.

# Mise en cache

Info
Ce cache ne s'applique que dans le cadre de ressources proxiées.
Attention
Mettre en cache des données venant de votre API peux avoir des effets indésirables.

dans etc/nginx/nginx.conf

proxy_cache_path /tmp/cache levels=1:2 keys_zone=my_cache:10m inactive=10m;
etc/nginx/nginx.conf

Cette directive permet de crĂ©er une zone de cache nommĂ©e my_cache qui a un index de 10mb (ce qui doit ĂȘtre suffisant pour le commun des mortels)
/tmp/cache dĂ©fini oĂč le cache sera Ă©crit sur le disque, dans le cas de forte activitĂ© le kernel Linux devrait ĂȘtre assez malin pour mettre les fichiers dans un cache LRU, pas besoin donc d’un montage de type tmpfs
inactive permet de stipuler au bout de combien de temps les fichiers de cache devraient ĂȘtre purger si le cache n’est pas utilisĂ©.

proxy_cache my_cache;
proxy_cache_methods GET HEAD;
proxy_cache_valid 200 302 10s;
add_header X-Cache-Status $upstream_cache_status;
/etc/nginx/sites-enabled/monsite.fr.conf
  • proxy_cache permet de dire qu’on va utiliser le cache nommĂ© my_cache, dĂ©fini dans le block http juste au dessus.
  • proxy_cache_methods permet de dire sur quelles methodes on va mettre en place le cache. par dĂ©faut se sont les mĂ©thodes GET et HEAD qui sont misent en cache.
  • proxy_cache_valid permet de dĂ©finir quels status code seront mis en cache et pour combien de temps. Par example proxy_cache_valid 200 302 10s; met en cache toutes les rĂ©ponses 200 et 302 de l’api proxiĂ©e avec une durĂ©e de validitĂ© de 10 secondes.
  • add_header n’est pas une directive propre Ă  la mise en cache, cependant la variable upstream_cache_status indique si la requĂȘte a Ă©tĂ© ou non tirĂ©e du cache, cela nous permet donc de savoir si les donnĂ©es viennent de l’api ou du cache.

# ratelimit

dans etc/nginx/nginx.conf on va définir tout comme pour le cache une zone de ratelimit

limit_req_zone $binary_remote_addr zone=1reqPer1Second:10m rate=1r/s;
etc/nginx/nginx.conf

On crĂ©e donc une zone qui s’appelle 1reqPer1Second avec comme clef de dictionnaire $binary_remote_addr et comme taille maximale du dictionnaire 10mb.

dans /etc/nginx/sites-enabled/monsite.fr.conf

limit_req zone=1reqPer1Second burst=50 nodelay;
limit_req_status 429;
/etc/nginx/sites-enabled/monsite.fr.conf
  • limit_req zone=1reqPer1Second burst=50 nodelay; permet de dire qu’on va utiliser la zone prĂ©cĂ©dement crĂ©Ă©e, pour burst il va falloir utiliser l’analogie du seau percĂ©
    • On prends un seau qui peut contenir 50 unitĂ©es (des requĂȘtes ici)
    • toutes les secondes une unitĂ© s’écoule du seau
    • quand le seau arrive Ă  50 il dĂ©borde, la requĂȘte n’est pas traitĂ©e
  • limit_req_status dĂ©fini le status de la rĂ©ponse renvoyĂ© par Nginx dans le cas d’un ratelimit

# Bonus: redirection en HTTPS sur tous vos vhosts

On va rediriger toutes les requĂȘtes qui sont sur le port 80 (http) vers le port 443 (https)

server {
    listen 80;
    listen [::]:80;
    server_name _;
    return 301 https://$host$request_uri;
}
/etc/nginx/sites-enabled/redirect-https.conf
Info
Il est possible de modifier ce comportement pour un vhost en mettant le bon server_name.

# RĂ©capitulatif

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
	multi_accept on;
	worker_connections  1024;
}

http {
	limit_req_zone $binary_remote_addr zone=1reqPer1Second:10m rate=1r/s;
	proxy_cache_path /tmp/cache levels=1:2 keys_zone=my_cache:10m inactive=10m;
	##
	# Basic Settings
	##

	sendfile on;
	aio on;
	tcp_nopush on;
	types_hash_max_size 2048;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	# ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
	ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_proxied any;
	gzip_comp_level 6;
	gzip_http_version 1.1;
	gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##
	server {
		listen 80;
		listen [::]:80;
		server_name _;
		return 301 https://$host$request_uri;
	}
	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*.conf;
}
/etc/nginx/nginx.conf
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name monsite.fr www.monsite.fr;

    location / {

        limit_req zone=1reqPer1Second burst=50 nodelay;
        limit_req_status 429;

        proxy_cache my_cache;
        proxy_cache_methods GET HEAD;
        proxy_cache_valid 200 302 10s;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_pass http://unix:/tmp/monsite.fr.sock;
        include /etc/nginx/proxy_params;
    }
    ssl_certificate /etc/letsencrypt/live/flapili.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/flapili.fr/privkey.pem;
}
/etc/nginx/sites-enabled/monsite.fr.conf