infra / nginx
Haute disponibilité et performance tuning
Explication
Ce que vous allez apprendre
- Dimensionner
worker_processesetworker_connectionspar rapport au matériel réel - Aligner les limites système (
ulimit) avec la configuration Nginx pour éviter un plafonnement silencieux - Mettre en cache des réponses de backend avec
proxy_cachepour absorber des pics de charge - Comprendre le rôle de
proxy_cache_use_stalepour amortir une panne backend temporaire - Mettre en place une haute disponibilité active/passive avec Keepalived
Dans quel contexte ?
Un site e-commerce s'apprête à affronter un pic de trafic majeur (soldes, lancement produit) qui va multiplier son trafic habituel par dix pendant quelques heures. L'équipe infra doit s'assurer, à l'avance et avec des mesures réelles, que Nginx et les serveurs qui l'entourent peuvent absorber cette charge sans tomber, plutôt que de le découvrir en pleine opération commerciale.
D'abord, le plafond théorique de connexions se calcule simplement
worker_processes (généralement égal au nombre de cœurs CPU) multiplié par worker_connections (le nombre de connexions simultanées gérables par worker) donne le nombre maximal de connexions simultanées théoriques. Augmenter worker_connections au-delà de sa valeur par défaut (souvent 512 ou 1024) est souvent la première optimisation nécessaire sous forte charge.
Une fois ce calcul fait, un piège invisible peut tout bloquer
Relever worker_rlimit_nofile dans la configuration Nginx ne suffit pas si les limites système du système d'exploitation (/etc/security/limits.conf) restent plus basses : le système d'exploitation plafonne silencieusement, sans erreur explicite dans les logs Nginx, ce qui rend ce genre de problème particulièrement difficile à diagnostiquer sans le savoir à l'avance.
| Réglage | Niveau | Rôle |
|---|---|---|
worker_connections | Nginx (nginx.conf) | Connexions simultanées par worker |
worker_rlimit_nofile | Nginx (nginx.conf) | Limite de fichiers ouverts par worker |
nofile (soft/hard) | Système (limits.conf) | Plafond réel imposé par l'OS, prioritaire sur Nginx |
Ensuite, une technique différente permet de réduire la charge sur les backends eux-mêmes
proxy_cache met en cache les réponses d'un backend directement dans Nginx, évitant de le solliciter pour du contenu peu volatile (un catalogue produit qui ne change pas à la seconde près). Combiné à proxy_cache_lock, cela évite aussi le "thundering herd" : sans lui, un cache qui expire pendant un pic de trafic laisserait des centaines de requêtes simultanées frapper le backend en même temps pour régénérer la même donnée.
Il reste un mécanisme de résilience particulièrement précieux en cas de panne
proxy_cache_use_stale error timeout updating http_500 http_502 http_503; autorise Nginx à servir une version en cache légèrement périmée plutôt qu'une erreur, si le backend est temporairement indisponible ou trop lent. C'est un compromis délibéré entre fraîcheur parfaite et disponibilité continue, souvent préférable pour l'expérience utilisateur.
Prérequis
Cette leçon suppose une bonne maîtrise de proxy_pass et des upstream (leçons 4 et 5) : le tuning de performance s'applique par-dessus une architecture reverse proxy déjà fonctionnelle.
Piège fréquent
Augmenter agressivement worker_connections sans jamais valider le résultat avec un vrai outil de charge (comme wrk ou k6) revient à deviner : un réglage qui semble raisonnable sur le papier peut se heurter à une autre limite invisible (bande passante réseau, capacité du backend, mémoire disponible) qui n'apparaît qu'en conditions réelles.
Bonne pratique
Pour une haute disponibilité au niveau infrastructure, deux instances Nginx actif/passif avec Keepalived (protocole VRRP) et une IP virtuelle flottante permettent une bascule automatique en cas de panne d'un nœud, sans intervention manuelle ni changement de configuration DNS en urgence.
Maintenant que la performance est optimisée, la dernière leçon de ce cours rassemble tous les outils de debugging et une checklist complète à appliquer systématiquement avant toute mise en production.
Commandes & code
Haute disponibilité et performance tuning
# Niveau "main" : dimensionner Nginx par rapport au matériel réel
user www-data;
worker_processes auto; # = nombre de coeurs CPU ; "auto" laisse Nginx détecter
worker_rlimit_nofile 65535; # relève la limite de descripteurs de fichiers par worker
events {
worker_connections 4096; # connexions simultanées par worker (défaut souvent trop bas : 512-1024)
use epoll; # mécanisme I/O le plus efficace sous Linux (epoll >> select/poll)
multi_accept on; # accepte plusieurs connexions en une seule syscall
}
# Connexions max théoriques = worker_processes * worker_connections# Aligner les limites système avec la config Nginx (sinon worker_rlimit_nofile est plafonné en silence)
# /etc/security/limits.conf
www-data soft nofile 65535
www-data hard nofile 65535
# Vérifier les limites effectives d'un worker en cours d'exécution
cat /proc/$(pgrep -f 'nginx: worker' | head -1)/limits | grep "Max open files"http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65; # durée pendant laquelle une connexion client reste ouverte (réutilisation)
keepalive_requests 1000; # nombre max de requêtes sur une même connexion keepalive avant fermeture
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
reset_timedout_connection on; # libère immédiatement la mémoire des connexions timeout (au lieu d'attendre)
}# Cache de réponses proxy (proxy_cache) : évite de re-solliciter le backend pour du contenu peu volatile
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:100m max_size=1g inactive=60m use_temp_path=off;
server {
location /api/catalog/ {
proxy_pass http://backend_app;
proxy_cache api_cache;
proxy_cache_valid 200 5m; # cache les 200 pendant 5 minutes
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503; # sert du cache périmé si le backend est down
proxy_cache_lock on; # évite le "thundering herd" : une seule requête regénère le cache, les autres attendent
add_header X-Cache-Status $upstream_cache_status; # debug : HIT / MISS / STALE / BYPASS
}
}# Haute disponibilité : deux Nginx actif/passif devant les mêmes backends, avec keepalived (VRRP)
# /etc/keepalived/keepalived.conf (résumé conceptuel, sur chaque noeud Nginx)
vrrp_instance VI_1 {
state MASTER # ou BACKUP sur le second noeud
interface eth0
virtual_router_id 51
priority 150 # plus haute priorité = devient MASTER
virtual_ipaddress {
192.168.1.100/24 # IP virtuelle flottante, bascule automatiquement vers le noeud vivant
}
}# Bench de charge pour valider le tuning (à faire AVANT la mise en prod, jamais deviner)
wrk -t4 -c200 -d30s https://example.com/api/health
# -t4 : 4 threads, -c200 : 200 connexions simultanées, -d30s : durée du test
# Surveiller pendant le test
watch -n1 'ss -s; nginx -V 2>&1 | head -1'
tail -f /var/log/nginx/error.log | grep -i "worker_connections\|accept()"Résumé
worker_processes auto+worker_connectionsélevé +epolldéfinissent le plafond théorique de connexions simultanées.worker_rlimit_nofiledoit être aligné avec les limites système (/etc/security/limits.conf), sinon il est silencieusement plafonné.proxy_cacheavecproxy_cache_use_staleetproxy_cache_lockabsorbe les pics de charge et amortit les pannes backend.- Keepalived + IP virtuelle = haute disponibilité niveau infra (bascule automatique entre deux noeuds Nginx).
- Toujours valider un tuning avec un outil de charge réel (
wrk,k6) plutôt que de se fier à l'intuition.
Exercices pratiques
Mission : préparer Nginx à un pic de trafic x10 sans tout deviner
Objectif : Aligner les limites système avec la configuration Nginx, mettre en cache un endpoint catalogue avec repli sur du contenu périmé, et valider le tout avec un test de charge.
Contexte
Un site e-commerce anticipe un pic de trafic x10 pendant des soldes. worker_rlimit_nofile 65535; a été ajouté dans nginx.conf, mais personne n'a vérifié /etc/security/limits.conf. L'équipe veut aussi mettre en cache /api/catalog/ pour absorber la charge, avec un mécanisme qui tolère une brève panne backend pendant le pic.