Les coulisses d'un crash Nginx en haute disponibilité
Votre application s'effondre alors que l'utilisation du processeur de vos serveurs applicatifs dépasse à peine les dix pour cent. Les clients reçoivent des erreurs de connexion aléatoires et votre équipe d'astreinte pointe du doigt la base de données alors que le coupable dort tranquillement à l'entrée de votre réseau. Le rôle de reverse proxy endossé par Nginx est crucial, mais sa configuration d'origine est pensée pour la polyvalence, pas pour résister à des vagues de requêtes massives et simultanées.
La panne se produit souvent au niveau du système d'exploitation ou dans la mauvaise gestion de la mémoire temporaire allouée par Nginx pour chaque connexion réseau. Pour comprendre et corriger ce phénomène, nous allons travailler sur une architecture distribuée classique où Nginx distribue la charge vers plusieurs microservices.
Le cas d'usage réel et la topologie réseau
Dans un environnement moderne, Nginx sert de point d'entrée unique et distribue le trafic web vers une série de serveurs applicatifs en arrière-plan. Si le routeur ne dispose pas de suffisamment de ressources logiques pour maintenir les connexions ouvertes avec les clients d'un côté et les serveurs applicatifs de l'autre, la file d'attente sature immédiatement.
Le schéma ci-dessus illustre comment le flux de requêtes transite par notre répartiteur de charge Nginx. Lorsque l'un des serveurs de destination commence à ralentir ou à rejeter des requêtes (ici représenté par le composant défaillant), Nginx doit gérer activement la rétention des connexions sans épuiser son propre pool de ressources sous peine de pénaliser les autres serveurs en bonne santé.
Préparer le terrain de jeu et l'environnement
Pour reproduire ce comportement et le stabiliser, nous devons installer Nginx ainsi qu'un utilitaire de génération de charge réseau. Nous utiliserons une distribution Linux basée sur Debian ou Ubuntu pour installer les outils nécessaires au diagnostic.
sudo apt-get update && sudo apt-get install -y nginx wrk htop lsof
La commande ci-dessus met à jour la liste des paquets de notre système, puis installe le serveur web nginx, l'outil de test de charge wrk pour simuler le trafic, htop pour observer les ressources de notre processeur en temps réel et enfin lsof afin de lister les fichiers et les sockets ouverts.
Vérification des limites système
Avant de toucher à la configuration de Nginx, assurez-vous de connaître les limites actuelles de votre système concernant les fichiers ouverts en tapant la commande ulimit -n dans votre terminal.
Configuration par défaut et premier goulot d'étranglement
La configuration par défaut d'une installation standard de Nginx limite grandement le nombre maximal d'utilisateurs simultanés. Nginx utilise une architecture orientée événements où un processus parent gère des processus enfants appelés workers.
Le fichier de configuration initial naïf
Analysons la structure d'un fichier de configuration minimaliste qui provoque l'asphyxie du serveur lors d'une montée en charge. Ce fichier se situe généralement à l'emplacement /etc/nginx/nginx.conf.
user nginx;
worker_processes 1;
events {
worker_connections 768;
}
http {
upstream backend_serveurs {
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
server {
listen 80;
location / {
proxy_pass http://backend_serveurs;
}
}
}
Ce fichier contient les directives de base suivantes :
- worker_processes 1: Indique à Nginx de ne lancer qu'un seul processus de traitement, ce qui bride les performances si votre machine possède plusieurs cœurs de processeur.
- worker_connections 768: Limite le nombre de connexions simultanées que ce processus peut ouvrir à un instant donné.
- proxy_pass http://backend_serveurs: Redirige toutes les requêtes reçues sur le port 80 vers notre groupe de serveurs applicatifs déclarés dans le bloc de redirection amont.
Validation et crash test sous charge
Démarrons un test de charge intensif sur cette configuration d'origine pour observer la rupture du service. Nous allons simuler une charge soutenue avec notre outil de test dédié.
# Commande à exécuter pour lancer le test de charge
wrk -t12 -c1000 -d30s http://127.0.0.1/
Résultat:
Running 30s test @ http://127.0.0.1/
12 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 15.24ms 22.18ms 240.11ms 85.12%
Req/Sec 112.45 40.22 310.00 68.20%
4210 requêtes lues avec succès
Socket errors: connect 245, read 0, write 0, timeout 180
Non-2xx or 3xx responses: 1205
Les statistiques révèlent un effondrement des requêtes traitées avec un nombre élevé d'erreurs de connexion et des retours de requêtes non valides. Les file descriptors ou descripteurs de fichiers du système Linux limitent les connexions réseau physiques car chaque nouvelle communication avec un client ou un serveur applicatif équivaut à l'ouverture d'un fichier sur le disque.
L'implémentation de combat taillée pour la production
Pour concevoir une architecture capable de tenir la charge de millions d'utilisateurs sans sourciller, nous devons ajuster de manière chirurgicale les paramètres de la mémoire tampon, la répartition des processus sur les processeurs de notre machine et optimiser la communication avec le système d'exploitation.
Le fichier nginx.conf de production commenté
Voici la configuration optimisée pour un serveur disposant de plusieurs processeurs et nécessitant une gestion fluide de flux réseau importants. Remplacez le contenu de votre fichier de configuration par le bloc suivant.
user nginx;
worker_processes auto;
worker_cpu_affinity auto;
pid /var/run/nginx.pid;
# Configuration des limites d'ouverture de fichiers pour le processus parent
worker_rlimit_nofile 65535;
events {
worker_connections 10240;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Optimisations du protocole TCP et d'écriture disque
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Gestion des durées de vie des connexions inactives
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
# Dimensionnement fin des buffers mémoire pour contrer les écritures disque
client_body_buffer_size 16k;
client_header_buffer_size 1k;
client_max_body_size 16m;
large_client_header_buffers 4 8k;
upstream backend_serveurs {
server 10.0.1.10:8080 max_fails=3 fail_timeout=15s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=15s;
# Maintient des connexions persistantes avec les serveurs applicatifs
keepalive 64;
}
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://backend_serveurs;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Buffers de réponse pour le reverse proxy
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 64k;
proxy_busy_buffers_size 128k;
# Limites temporelles pour éviter de bloquer les workers
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
}
}
Voici l'analyse des paramètres clés introduits dans cette configuration :
- worker_processes auto: Indique à Nginx de détecter automatiquement le nombre de processeurs disponibles et de démarrer un processus enfant par processeur physique.
- worker_rlimit_nofile 65535: Augmente drastiquement la limite de fichiers ouverts autorisés pour les processus Nginx, empêchant l'apparition des erreurs de type "Too many open files".
- use epoll: Active le mécanisme d'E/S asynchrone ultra-performant propre au noyau Linux, agissant comme un guichet d'aiguillage automatique plutôt qu'un contrôle manuel de chaque connexion.
- keepalive 64: Permet de conserver des connexions persistantes ouvertes avec vos serveurs applicatifs pour éviter de recréer une liaison réseau complète à chaque requête entrante.
- proxy_buffering on: Active les buffers mémoire. Nginx stocke temporairement la réponse du serveur applicatif en mémoire vive au lieu de la lire octet par octet, libérant instantanément le microservice pour la requête suivante.
Optimisation de la couche système Linux
Configurer Nginx ne sert à rien si le système d'exploitation sous-jacent refuse d'accueillir les paquets réseau. Nous devons modifier les paramètres de la pile réseau du noyau Linux en éditant le fichier de configuration système /etc/sysctl.conf.
# Ajoutez ces lignes au fichier /etc/sysctl.conf
fs.file-max = 2097152
net.core.somaxconn = 32768
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_tw_reuse = 1
La commande sysctl -p permettra de recharger ces paramètres instantanément.
- fs.file-max: Définit la quantité maximale de descripteurs de fichiers autorisés sur l'intégralité du système d'exploitation.
- net.core.somaxconn: Augmente la taille de la socket d'écoute. C'est l'antichambre des connexions réseau en attente d'être acceptées par Nginx. Si elle est trop petite, les clients reçoivent des erreurs de refus de connexion instantanées lors des pics d'activité.
- net.ipv4.tcp_tw_reuse: Permet de réutiliser rapidement les sockets réseau récemment fermées qui stagnent dans l'état de sécurité réseau appelé "TIME_WAIT".
Résilience face aux pannes des microservices sous-jacents
Une configuration robuste doit prévoir la défaillance des services applicatifs. Dans notre configuration optimisée, nous avons intégré les paramètres max_fails=3 et fail_timeout=15s. Si un serveur applicatif échoue trois fois de suite, Nginx l'exclut temporairement du pool de serveurs actifs pendant une durée de 15 secondes, protégeant ainsi le trafic global du dysfonctionnement isolé d'un conteneur applicatif.
Conclusion et bilan de performance
En alignant finement les capacités du noyau Linux avec les mécanismes asynchrones de Nginx, nous avons transformé un serveur web fragile en un point d'entrée réseau hautement résilient. L'optimisation des structures de mémoire tampon évite l'utilisation abusive du disque dur et l'activation du maintien des connexions persistantes soulage l'ensemble de vos serveurs applicatifs en réduisant le travail de négociation réseau répétitif.
Pour assurer la pérennité de votre infrastructure de production, intégrez la surveillance systématique des descripteurs de fichiers ouverts et des erreurs de connexion HTTP au sein de vos outils de métriques. Un reverse proxy bien configuré est le garant silencieux d'une expérience utilisateur fluide et d'une infrastructure serveur préservée.
Espace commentaire
Écrire un commentaire
Rejoignez la discussion
Vous devez être connecté pour poster un message.
2 commentaires
C'est que ta limite système est toujours trop basse.
worker_rlimit_nofiledansnginx.confne suffit pas si l'OS bloque en amont.Vérifie
/etc/security/limits.confet ajoute :J'ai testé ta config sur une instance Debian et j'ai une erreur au démarrage :
[emerg] socket() AF_INET failed (24: Too many open files). Une idée ?Envie de progresser ?
Reçois les prochains articles gratuitement directement dans ta boîte mail.
S'abonner au blog