Il y a 1 année · 9 minutes · Back, Cloud

REX AWS : Nginx n’aime pas les IPs dynamiques

Lors de la mise en place d’une architecture, le besoin d’un Nginx frontal officiant comme reverse proxy peut se faire sentir. Ceci est d’autant plus vrai dans le cas d’une migration d’application nécessitant de conserver les briques historiques en place afin de faciliter la transition.

Nous vous proposons un retour d’expérience sur la mise en place d’un serveur Nginx dans AWS. Nous verrons que l’introduction d’un tel serveur en frontal d’un service Elastic Load Balancing (ELB) peut provoquer de forts ralentissements imprévisibles allant jusqu’à paralyser la production.
Après avoir présenté et expliqué le problème, une correction sera proposée afin de retrouver une QoS nominale.

Contexte

Notre projet consiste en la migration d’un site web grand public vers une nouvelle plateforme applicative hébergée sur AWS. Comme souvent dans ce type de migration, les développements sont effectués par lots et la première partie de la plateforme a été livrée en production avant la fin de l’ensemble des développements.

Pour des raisons propres au projet, le nom de domaine pointe vers Route53 d’AWS. L’ensemble du trafic circulant à travers ce nom de domaine arrive donc sur la plateforme et doit, pour les parties non migrées, être redirigé vers les anciens serveurs. Pour gérer cet aiguillage, un Nginx elastic (à travers un autoscaling group) a été mis en place dans l’infrastructure dont voici un schéma simplifié :

contexte-ngnix-aws

En résumé :

  • Un CloudFront (le CDN d’AWS) permet de servir le contenu au plus prêt de l’utilisateur.
  • Toutes les requêtes transitent par un autoscaling group Nginx servant de poste d’aiguillage. Ce Nginx devant être transparent pour l’utilisateur, il a été configuré comme un reverse proxy pointant vers la nouvelle plateforme, et pour certaines URL définies, vers les anciens serveurs (Legacy).
  • Un Elastic Beanstalk sur lequel est déployé l’application.

Sur le papier, tout semble fonctionner correctement, malheureusement, un problème inattendu est arrivé.

Problème

De manière aléatoire, l’accès au site était perturbé par de forts ralentissements. Ceux-ci survenaient pour environ la moitié des requêtes pointant vers nos applications Elastic Beanstalk et ce peu importe le type (GET, POST, HEAD) et le client (navigateur, curl, backends). La partie Legacy n’était pas impactée.

Malgré un pull de c4.large, un sizing de machine trop faible fut envisagé, les points suivants ont été analysés :

  • Le CPU est-il surchargé ?
  • La mémoire est-elle suffisante ?
  • Les I/O disque sont-ils importants ?
  • Les interfaces réseaux sont-elles saturées ?

Après analyse via un utilitaire d’APM et les outils AWS (CloudWatch), il s’avère que toutes les métriques étaient normales ce qui écartait les machines comme source du problème.

La deuxième piste explorée fut les constantes Nginx. A l’instar des machines, rien d’alarmant ne fut relevé. Le nombre de requêtes était certes élevé, mais rien que Nginx ne puisse traiter. Les connexions actives ne semblaient pas non plus exploser, le taux de drop de connexion était nul et pour finir, le fichier d’erreur de Nginx ne contenait aucune entrée.

A priori Nginx était hors de cause et pourtant son redémarrage corrigeait le problème de façon temporaire et permettait de retrouver une qualité de service nominale.

Fort de cette constatation, la piste Nginx fut privilégiée et approfondie.

Résolution du problème

Afin d’avoir plus d’informations sur les requêtes entrantes des Nginx, le format de log suivant a été appliqué :

log_format  main '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" '
    '$request_time $upstream_response_time $connection_requests $pipe';

Pour information, un upstream dans Nginx désigne une liste de serveurs vers laquelle il est notamment possible de proxyfier des requêtes.

Les informations les plus importantes apportées par ce format sont :

  • $request_time : temps d’exécution de la requête incluant le $upstream_response_time et le temps de processing Nginx
  • $upstream_response_time : temps d’exécution de la requête dans les backends

Lors des requêtes lentes, les logs ressemblent à celui-ci :

127.0.0.1 - - [06/Oct/2015:13:00:08 +0000] "HEAD / HTTP/1.1" 404 0 "-" "curl/7.40.0" "-" 60.255 60.004, 0.251 1 .


Deux choses sont remarquables :

  • Le $upstream_response_time contient deux valeurs (ici 60.004, 0.251). Cela signifie que Nginx a attendu 60 secondes que l’application réponde (timeout), puis a essayé une seconde fois et la requête a répondu instantanément
  • Le $request_time (ici 60.255 ) est égal à la somme des deux $upstream_response_time

L’analyse des access-log permet d’affirmer que le ralentissement ne provient pas d’un problème interne de processing de Nginx mais d’un autre élément en aval de l’infrastructure.

Une démarche bottom-up a été mise en place en partant de l’application pour trouver ce point de contention.

Le résultat fut le suivant :

  • Un curl directement sur la machine de l’application est normal (1)
  • Un curl sur le nom de domaine de l’ELB est également normal (2)
  • Un curl sur le Nginx est lent (3)

Les logs des ELB permettent d’établir que même lors d’une requête lente depuis le Nginx, la réponse entre ces deux composants est instantanée dès que l’ELB reçoit la requête. Il s’agit donc d’un problème réseau entre ces éléments.

Enfin, nous avons remarqué que la résolution du nom de domaine d’un ELB retourne plusieurs adresses IP.

Un nslookup donne les informations suivantes :

$> nslookup awseb-e-c-AWSEBLoa-xxxx.eu-west-1.elb.amazonaws.com
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: awseb-e-c-AWSEBLoa-xxxx.eu-west-1.elb.amazonaws.com
Address: 54.72.xxx.21
Name: awseb-e-c-AWSEBLoa-xxxx.eu-west-1.elb.amazonaws.com
Address: 54.246.xxx.217

Un autre point important, ces adresses IP changent de manière dynamique. C’est pourquoi AWS conseille de toujours utiliser le nom de domaine fourni avec chaque ELB dont le TTL est 60 secondes.

Les ELB étant élastiques, AWS gère automatiquement le nombre d’instances d’ELB associé à une stack. Ce nombre évolue dynamiquement en fonction du trafic sur l’ELB. Lors de cette évolution, les adresses IP sont réalouées au besoin par AWS avec au minimum deux adresses IPs pour assurer le fail over.

Concernant le problème Nginx, une seule IP change à la fois, ce qui explique pourquoi l’application est toujours joignable. Nginx tente sur la première IP puis effectue un fail over sur la deuxième.

Le problème Nginx se réduit à la question suivante :

Pourquoi Nginx ne prend pas en compte le changement d’IP des ELB.

Il faut savoir que Nginx résout les noms de domaines au démarrage puis stocke les adresses IP en mémoire sans réitérer ultérieurement la résolution.

Afin de résoudre ce problème, il faut forcer Nginx à résoudre dynamiquement les noms de domaines. La version payante de Nginx (Nginx plus) fait de la résolution dynamique de manière native.

Néanmoins, si l’on souhaite utiliser la version gratuite, il faut appliquer la configuration suivante :

http {
    ...
    resolver 172.31.0.2 valid=5s;
    ...
    server {
        ...
        set $elb "awseb-e-c-XXX-YYY.eu-west-1.elb.amazonaws.com";
        location / {
            ...
            proxy_pass  http://$elb;
            ...
        }
        ...
   }
}

En résumé :

  • Dans la section http , un serveur DNS est défini et force la résolution des noms de domaine toutes les cinq secondes. Pour connaître l’adresse IP du serveur DNS d’une machine EC2, il s’agit de la deuxième adresse IP privée du range de la machine. AWS nomme cette stratégie le « plus two ». Cela signifie que sur le range d’IP 10.0.0.0/16, l’adresse 10.0.0.1 est la gateway tandis que 10.0.0.2 est celle du serveur DNS
  • Dans la section server, la variable $elb est configurée avec le nom de domaine de l’ELB. Sans cette variable, Nginx ne forcera pas la re-résolution du DNS.
  • Enfin, dans la section location où sont proxyfiées les requêtes, la directive proxy_pass utilise la variable $elb définie précédemment.

À noter qu’il est impossible d’utiliser des variables dans la directive upstream permettant de définir des listes de backends.

Grâce à cette configuration, un Nginx peut être placé en reverse proxy devant un ELB ou n’importe quel autre système changeant d’adresse IP dynamiquement.

Conclusion

Dans une infrastructure AWS, il est capital de prendre en compte la dimension « elastic » des load balancers. Comme ces derniers se mettent à l’échelle pour épouser au mieux le trafic, chaque résolution DNS peut indiquer des IPs en plus, en moins ou différentes. D’où la nécessité du TTL minime – de 60 secondes – configuré pour les ELB afin de prendre en compte au plus tôt ces changements.

Lorsqu’un équipement est placé en amont d’un ELB, il est impératif que ce dernier tire parti du TTL réduit de l’entrée DNS sous peine d’expérimenter de désagréables déconvenues. Deux cas sont recensés avec un Nginx en frontal :

  • suppression / changement d’une ressource ELB : Nginx en reverse proxy aiguille toujours des requêtes sur un ancien noeud en dépit de la mise à jour DNS. Cela provoque de gros ralentissements en raison du timeout et va jusqu’à paralyser la plate-forme.
  •   ajout d’une ressource ELB : Nginx n’a pas connaissance de la nouvelle topologie et devient donc un goulot d’étranglement car incapable de tirer partie de la mise à l’échelle. Les nouveaux load balancers sont inutiles du fait de l’absence de trafic reçu.

Dans sa configuration par défaut Nginx résout les IP uniquement au démarrage et non pas dynamiquement ce qui conduit aux écueils précédents. Heureusement, en quelques lignes de configuration exposées dans cet article ou via l’achat d’une licence Nginx plus, il est possible de s’affranchir de cette limitation.

En conclusion, lorsque vous interagissez directement avec un ELB que ce soit à travers un reverse proxy ou via un logiciel de tests de performances, pensez à la résolution du DNS. Il serait en effet mal venu de ralentir le trafic ou d’obtenir des résultats de charge faussés en raison d’une utilisation obsolète ou partielle des ELB à votre disposition.

Source :

Best Practices in Evaluating Elastic Load Balancing

Nginx, AWS ELB, DNS resolution

AWS: Working with Private Hosted Zones

2 réflexions au sujet de « REX AWS : Nginx n’aime pas les IPs dynamiques »

  1. Publié par Romain Niveau, Il y a 1 année

    Bonjour,

    Oui nous utilisons notre Nginx en configuration keep alive avec nos backends.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *