Retour au cours

infra / nginx

Haute disponibilité et performance tuning

Leçon 141 exercice

Explication

Ce que vous allez apprendre

  • Dimensionner worker_processes et worker_connections par 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_cache pour absorber des pics de charge
  • Comprendre le rôle de proxy_cache_use_stale pour 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églageNiveauRôle
worker_connectionsNginx (nginx.conf)Connexions simultanées par worker
worker_rlimit_nofileNginx (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

nginx
# 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
bash
# 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"
nginx
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)
}
nginx
# 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
    }
}
nginx
# 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
    }
}
bash
# 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é + epoll définissent le plafond théorique de connexions simultanées.
  • worker_rlimit_nofile doit être aligné avec les limites système (/etc/security/limits.conf), sinon il est silencieusement plafonné.
  • proxy_cache avec proxy_cache_use_stale et proxy_cache_lock absorbe 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

1 disponible
1

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.

Résoudre l’exercice →