Comprendre les algorithmes de sélection du serveur Nginx et du bloc d'emplacement

De Get Docs
Aller à :navigation, rechercher

Introduction

Nginx est l'un des serveurs Web les plus populaires au monde. Il peut gérer avec succès des charges élevées avec de nombreuses connexions client simultanées et peut fonctionner comme un serveur Web, un serveur de messagerie ou un serveur proxy inverse.

Dans ce guide, nous aborderons certains des détails en coulisse qui déterminent la manière dont Nginx traite les demandes des clients. La compréhension de ces idées peut aider à éliminer les approximations lors de la conception des blocs de serveurs et d'emplacements et peut rendre la gestion des demandes moins imprévisible.

Configurations de bloc Nginx

Nginx divise logiquement les configurations destinées à servir différents contenus en blocs, qui vivent dans une structure hiérarchique. Chaque fois qu'une demande client est faite, Nginx commence un processus de détermination des blocs de configuration à utiliser pour gérer la demande. Ce processus de décision est ce dont nous discuterons dans ce guide.

Les principaux blocs dont nous parlerons sont le bloc server et le bloc location.

Un bloc serveur est un sous-ensemble de la configuration de Nginx qui définit un serveur virtuel utilisé pour gérer les requêtes d'un type défini. Les administrateurs configurent souvent plusieurs blocs de serveur et décident quel bloc doit gérer quelle connexion en fonction du nom de domaine, du port et de l'adresse IP demandés.

Un bloc d'emplacement réside dans un bloc de serveur et est utilisé pour définir comment Nginx doit gérer les demandes de différentes ressources et URI pour le serveur parent. L'espace URI peut être subdivisé de la manière que l'administrateur aime utiliser ces blocs. C'est un modèle extrêmement flexible.

Comment Nginx décide quel bloc de serveur traitera une requête

Étant donné que Nginx permet à l'administrateur de définir plusieurs blocs de serveur qui fonctionnent comme des instances de serveur Web virtuel distinctes, il a besoin d'une procédure pour déterminer lequel de ces blocs de serveur sera utilisé pour satisfaire une demande.

Pour ce faire, il utilise un système défini de vérifications qui sont utilisées pour trouver la meilleure correspondance possible. Les principales directives de blocage de serveur concernées par Nginx au cours de ce processus sont la directive listen et la directive server_name.

Analyse de la directive listen pour trouver des correspondances possibles

Tout d'abord, Nginx regarde l'adresse IP et le port de la requête. Il compare cela à la directive listen de chaque serveur pour créer une liste des blocs de serveur qui peuvent éventuellement résoudre la requête.

La directive listen définit généralement l'adresse IP et le port auxquels le bloc serveur répondra. Par défaut, tout bloc de serveur qui n'inclut pas de directive listen reçoit les paramètres d'écoute de 0.0.0.0:80 (ou 0.0.0.0:8080 si Nginx est exécuté par un normal, non-[ X170X]utilisateur racine). Cela permet à ces blocs de répondre aux demandes sur n'importe quelle interface sur le port 80, mais cette valeur par défaut n'a pas beaucoup de poids dans le processus de sélection du serveur.

La directive listen peut être définie sur :

  • Un combo adresse IP/port.
  • Une adresse IP unique qui écoutera alors sur le port 80 par défaut.
  • Un port unique qui écoutera chaque interface sur ce port.
  • Chemin d'accès à un socket Unix.

La dernière option n'aura généralement des implications que lors du passage des requêtes entre différents serveurs.

En essayant de déterminer à quel bloc de serveur envoyer une requête, Nginx essaiera d'abord de décider en fonction de la spécificité de la directive listen en utilisant les règles suivantes :

  • Nginx traduit toutes les directives « incomplètes » listen en remplaçant les valeurs manquantes par leurs valeurs par défaut afin que chaque bloc puisse être évalué par son adresse IP et son port. Voici quelques exemples de ces traductions : Un bloc sans directive listen utilise la valeur 0.0.0.0:80. Un bloc défini sur une adresse IP 111.111.111.111 sans port devient 111.111.111.111:80 Un bloc défini sur le port 8888 sans adresse IP devient 0.0.0.0:8888
  • Nginx tente ensuite de collecter une liste des blocs de serveur qui correspondent à la demande plus spécifiquement en fonction de l'adresse IP et du port. Cela signifie que tout bloc qui utilise fonctionnellement 0.0.0.0 comme adresse IP (pour correspondre à n'importe quelle interface), ne sera pas sélectionné s'il existe des blocs correspondants qui répertorient une adresse IP spécifique. Dans tous les cas, le port doit correspondre exactement.
  • S'il n'y a qu'une seule correspondance la plus spécifique, ce bloc de serveur sera utilisé pour répondre à la demande. S'il existe plusieurs blocs de serveur avec le même niveau de correspondance de spécificité, Nginx commence alors à évaluer la directive server_name de chaque bloc de serveur.

Il est important de comprendre que Nginx n'évaluera la directive server_name que lorsqu'il doit faire la distinction entre les blocs de serveur qui correspondent au même niveau de spécificité dans la directive listen. Par exemple, si example.com est hébergé sur le port 80 de 192.168.1.10, une requête pour example.com sera toujours servie par le premier bloc dans cet exemple, malgré la directive server_name dans le deuxième bloc.

server {
    listen 192.168.1.10;

    . . .

}

server {
    listen 80;
    server_name example.com;

    . . .

}

Dans le cas où plusieurs blocs de serveur correspondent avec une spécificité égale, l'étape suivante consiste à vérifier la directive server_name.

Analyse de la directive server_name pour choisir une correspondance

Ensuite, pour évaluer plus en détail les demandes qui ont des directives listen tout aussi spécifiques, Nginx vérifie l'en-tête Host de la demande. Cette valeur contient le domaine ou l'adresse IP que le client essayait réellement d'atteindre.

Nginx tente de trouver la meilleure correspondance pour la valeur qu'il trouve en examinant la directive server_name dans chacun des blocs de serveur qui sont encore candidats à la sélection. Nginx les évalue en utilisant la formule suivante :

  • Nginx essaiera d'abord de trouver un bloc de serveur avec un server_name qui correspond à la valeur dans l'en-tête Host de la requête exactement. Si cela est trouvé, le bloc associé sera utilisé pour servir la demande. Si plusieurs correspondances exactes sont trouvées, la première est utilisée.
  • Si aucune correspondance exacte n'est trouvée, Nginx essaiera alors de trouver un bloc de serveur avec un server_name qui correspond en utilisant un caractère générique de début (indiqué par un * au début du nom dans la configuration) . S'il en trouve un, ce bloc sera utilisé pour répondre à la requête. Si plusieurs correspondances sont trouvées, la correspondance la plus longue sera utilisée pour répondre à la demande.
  • Si aucune correspondance n'est trouvée à l'aide d'un caractère générique de début, Nginx recherche alors un bloc de serveur avec un server_name qui correspond à l'aide d'un caractère générique de fin (indiqué par un nom de serveur se terminant par un * dans la configuration) . S'il en trouve un, ce bloc est utilisé pour répondre à la requête. Si plusieurs correspondances sont trouvées, la correspondance la plus longue sera utilisée pour répondre à la demande.
  • Si aucune correspondance n'est trouvée à l'aide d'un caractère générique de fin, Nginx évalue alors les blocs de serveur qui définissent le server_name à l'aide d'expressions régulières (indiquées par un ~ avant le nom). Le first server_name avec une expression régulière qui correspond à l'en-tête "Host" sera utilisé pour servir la requête.
  • Si aucune correspondance d'expression régulière n'est trouvée, Nginx sélectionne alors le bloc de serveur par défaut pour cette adresse IP et ce port.

Chaque combo adresse IP/port a un bloc de serveur par défaut qui sera utilisé lorsqu'un plan d'action ne peut pas être déterminé avec les méthodes ci-dessus. Pour une combinaison adresse IP/port, il s'agira soit du premier bloc de la configuration, soit du bloc contenant l'option default_server dans le cadre de la directive listen (qui remplacerait le premier bloc trouvé algorithme). Il ne peut y avoir qu'une seule déclaration default_server par combinaison adresse IP/port.

Exemples

S'il existe un server_name défini qui correspond exactement à la valeur d'en-tête Host, ce bloc de serveur est sélectionné pour traiter la demande.

Dans cet exemple, si l'en-tête Host de la requête était défini sur host1.example.com, le deuxième serveur serait sélectionné :

server {
    listen 80;
    server_name *.example.com;

    . . .

}

server {
    listen 80;
    server_name host1.example.com;

    . . .

}

Si aucune correspondance exacte n'est trouvée, Nginx vérifie alors s'il existe un server_name avec un caractère générique de départ qui convient. La correspondance la plus longue commençant par un caractère générique sera sélectionnée pour répondre à la demande.

Dans cet exemple, si la requête avait un en-tête Host de www.example.org, le deuxième bloc de serveur serait sélectionné :

server {
    listen 80;
    server_name www.example.*;

    . . .

}

server {
    listen 80;
    server_name *.example.org;

    . . .

}

server {
    listen 80;
    server_name *.org;

    . . .

}

Si aucune correspondance n'est trouvée avec un caractère générique de départ, Nginx verra alors s'il existe une correspondance en utilisant un caractère générique à la fin de l'expression. À ce stade, la correspondance la plus longue se terminant par un caractère générique sera sélectionnée pour répondre à la demande.

Par exemple, si la requête a un en-tête Host défini sur www.example.com, le troisième bloc serveur sera sélectionné :

server {
    listen 80;
    server_name host1.example.com;

    . . .

}

server {
    listen 80;
    server_name example.com;

    . . .

}

server {
    listen 80;
    server_name www.example.*;

    . . .

}

Si aucune correspondance générique ne peut être trouvée, Nginx tentera alors de faire correspondre les directives server_name qui utilisent des expressions régulières. L'expression régulière correspondante first sera sélectionnée pour répondre à la requête.

Par exemple, si l'en-tête Host de la requête est défini sur www.example.com, alors le deuxième bloc serveur sera sélectionné pour satisfaire la requête :

server {
    listen 80;
    server_name example.com;

    . . .

}

server {
    listen 80;
    server_name ~^(www|host1).*\.example\.com$;

    . . .

}

server {
    listen 80;
    server_name ~^(subdomain|set|www|host1).*\.example\.com$;

    . . .

}

Si aucune des étapes ci-dessus n'est en mesure de satisfaire la demande, la demande sera transmise au serveur default pour l'adresse IP et le port correspondants.

Blocs d'emplacement correspondants

Semblable au processus utilisé par Nginx pour sélectionner le bloc de serveur qui traitera une demande, Nginx dispose également d'un algorithme établi pour décider quel bloc d'emplacement au sein du serveur utiliser pour traiter les demandes.

Syntaxe du bloc d'emplacement

Avant d'expliquer comment Nginx décide du bloc d'emplacement à utiliser pour gérer les requêtes, passons en revue certaines des syntaxes que vous pourriez voir dans les définitions de bloc d'emplacement. Les blocs d'emplacement vivent dans des blocs de serveur (ou d'autres blocs d'emplacement) et sont utilisés pour décider comment traiter l'URI de la demande (la partie de la demande qui vient après le nom de domaine ou l'adresse IP/le port).

Les blocs de localisation prennent généralement la forme suivante :

location optional_modifier location_match {

    . . .

}

Le location_match ci-dessus définit ce que Nginx doit vérifier par rapport à l'URI de la demande. L'existence ou la non-existence du modificateur dans l'exemple ci-dessus affecte la manière dont Nginx tente de faire correspondre le bloc d'emplacement. Les modificateurs ci-dessous entraîneront l'interprétation du bloc d'emplacement associé comme suit :

  • (aucun) : si aucun modificateur n'est présent, l'emplacement est interprété comme une correspondance préfixe. Cela signifie que l'emplacement donné sera mis en correspondance avec le début de l'URI de la demande pour déterminer une correspondance.
  • = : si un signe égal est utilisé, ce bloc sera considéré comme une correspondance si l'URI de la requête correspond exactement à l'emplacement indiqué.
  • ~ : si un modificateur tilde est présent, cet emplacement sera interprété comme une correspondance d'expression régulière sensible à la casse.
  • ~* : si un modificateur tilde et astérisque est utilisé, le bloc d'emplacement sera interprété comme une correspondance d'expression régulière insensible à la casse.
  • ^~ : si un modificateur carat et tilde est présent, et si ce bloc est sélectionné comme la meilleure correspondance d'expression non régulière, la correspondance d'expression régulière n'aura pas lieu.

Exemples illustrant la syntaxe du bloc d'emplacement

Comme exemple de correspondance de préfixe, le bloc d'emplacement suivant peut être sélectionné pour répondre aux URI de requête qui ressemblent à /site, /site/page1/index.html ou /site/index.html :

location /site {

    . . .

}

Pour une démonstration de la correspondance exacte d'URI de demande, ce bloc sera toujours utilisé pour répondre à un URI de demande qui ressemble à /page1. Il ne sera pas utilisé pour répondre à une requête URI /page1/index.html. Gardez à l'esprit que si ce bloc est sélectionné et que la demande est satisfaite à l'aide d'une page d'index, une redirection interne aura lieu vers un autre emplacement qui sera le véritable gestionnaire de la demande :

location = /page1 {

    . . .

}

Comme exemple d'emplacement qui doit être interprété comme une expression régulière sensible à la casse, ce bloc pourrait être utilisé pour gérer les requêtes pour /tortoise.jpg, mais pas pour /FLOWER.PNG :

location ~ \.(jpe?g|png|gif|ico)$ {

    . . .

}

Un bloc qui permettrait une correspondance insensible à la casse similaire à ce qui précède est illustré ci-dessous. Ici, /tortoise.jpg et /FLOWER.PNG peuvent être gérés par ce bloc :

location ~* \.(jpe?g|png|gif|ico)$ {

    . . .

}

Enfin, ce bloc empêcherait la correspondance d'expression régulière s'il est déterminé qu'il s'agit de la meilleure correspondance d'expression non régulière. Il pourrait gérer les requêtes pour /costumes/ninja.html :

location ^~ /costumes {

    . . .

}

Comme vous le voyez, les modificateurs indiquent comment le bloc de localisation doit être interprété. Cependant, cela ne pas nous indique l'algorithme que Nginx utilise pour décider à quel bloc d'emplacement envoyer la requête. Nous reviendrons sur cela ensuite.

Comment Nginx choisit l'emplacement à utiliser pour traiter les demandes

Nginx choisit l'emplacement qui sera utilisé pour répondre à une demande de la même manière qu'il sélectionne un bloc de serveur. Il passe par un processus qui détermine le meilleur bloc d'emplacement pour une demande donnée. Comprendre ce processus est une exigence cruciale pour pouvoir configurer Nginx de manière fiable et précise.

En gardant à l'esprit les types de déclarations d'emplacement que nous avons décrits ci-dessus, Nginx évalue les contextes d'emplacement possibles en comparant l'URI de la requête à chacun des emplacements. Il le fait en utilisant l'algorithme suivant :

  • Nginx commence par vérifier toutes les correspondances d'emplacement basées sur le préfixe (tous les types d'emplacement n'impliquant pas d'expression régulière). Il vérifie chaque emplacement par rapport à l'URI complet de la demande.
  • Tout d'abord, Nginx recherche une correspondance exacte. Si un bloc de localisation utilisant le modificateur = correspond exactement à l'URI de la requête, ce bloc de localisation est immédiatement sélectionné pour servir la requête.
  • Si aucune correspondance de bloc d'emplacement exacte (avec le modificateur =) n'est trouvée, Nginx passe ensuite à l'évaluation des préfixes non exacts. Il découvre l'emplacement de préfixe correspondant le plus long pour l'URI de requête donné, qu'il évalue ensuite comme suit : Si l'emplacement de préfixe correspondant le plus long a le modificateur ^~, alors Nginx mettra immédiatement fin à sa recherche et sélectionnera cet emplacement pour répondre à la demande. Si l'emplacement de préfixe correspondant le plus long n'utilise pas le modificateur ^~, la correspondance est stockée par Nginx pour le moment afin que le focus de la recherche puisse être déplacé.
  • Une fois que l'emplacement du préfixe correspondant le plus long est déterminé et stocké, Nginx passe à l'évaluation des emplacements des expressions régulières (sensibles à la casse et insensibles). S'il existe des emplacements d'expressions régulières dans l'emplacement de préfixe correspondant le plus long, Nginx les déplacera en haut de sa liste d'emplacements de regex à vérifier. Nginx essaie ensuite de faire correspondre les emplacements des expressions régulières de manière séquentielle. L'emplacement de l'expression régulière first qui correspond à l'URI de la demande est immédiatement sélectionné pour servir la demande.
  • Si aucun emplacement d'expression régulière correspondant à l'URI de la demande n'est trouvé, l'emplacement du préfixe précédemment stocké est sélectionné pour servir la demande.

Il est important de comprendre que, par défaut, Nginx servira les correspondances d'expression régulière de préférence aux correspondances de préfixe. Cependant, il évalue d'abord les emplacements de préfixe , permettant à l'administrateur d'ignorer cette tendance en spécifiant les emplacements à l'aide des modificateurs = et ^~.

Il est également important de noter que, alors que les emplacements de préfixe sont généralement sélectionnés en fonction de la correspondance la plus longue et la plus spécifique, l'évaluation des expressions régulières est arrêtée lorsque le premier emplacement correspondant est trouvé. Cela signifie que le positionnement dans la configuration a de vastes implications pour les emplacements des expressions régulières.

Enfin, il est important de comprendre que les correspondances d'expression régulière dans la correspondance de préfixe la plus longue "sauteront la ligne" lorsque Nginx évaluera les emplacements de regex. Celles-ci seront évaluées, dans l'ordre, avant que les autres correspondances d'expressions régulières ne soient prises en compte. Maxim Dounin, un développeur Nginx incroyablement serviable, explique dans ce post cette partie de l'algorithme de sélection.

Quand l'évaluation du bloc d'emplacement passe-t-elle à d'autres emplacements ?

D'une manière générale, lorsqu'un bloc d'emplacement est sélectionné pour servir une demande, la demande est entièrement traitée dans ce contexte à partir de ce point. Seuls l'emplacement sélectionné et les directives héritées déterminent la manière dont la demande est traitée, sans interférence des blocs d'emplacement frères.

Bien qu'il s'agisse d'une règle générale qui vous permettra de concevoir vos blocs d'emplacement de manière prévisible, il est important de réaliser qu'il y a des moments où une nouvelle recherche d'emplacement est déclenchée par certaines directives dans l'emplacement sélectionné. Les exceptions à la règle "un seul bloc d'emplacement" peuvent avoir des implications sur la manière dont la demande est réellement servie et peuvent ne pas correspondre aux attentes que vous aviez lors de la conception de vos blocs d'emplacement.

Certaines directives pouvant conduire à ce type de redirection interne sont :

  • indice
  • try_files
  • récrire
  • page_erreur

Passons brièvement en revue ces dernières.

La directive index conduit toujours à une redirection interne si elle est utilisée pour traiter la requête. Les correspondances de localisation exactes sont souvent utilisées pour accélérer le processus de sélection en mettant immédiatement fin à l'exécution de l'algorithme. Cependant, si vous faites une correspondance exacte d'emplacement qui est un répertoire ', il y a de fortes chances que la demande soit redirigée vers un emplacement différent pour un traitement réel.

Dans cet exemple, le premier emplacement correspond à un URI de requête de /exact, mais afin de gérer la requête, la directive index héritée par le bloc initie une redirection interne vers le deuxième bloc :

index index.html;

location = /exact {

    . . .

}

location / {

    . . .

}

Dans le cas ci-dessus, si vous avez vraiment besoin que l'exécution reste dans le premier bloc, vous devrez trouver une méthode différente pour satisfaire la demande au répertoire. Par exemple, vous pouvez définir un index non valide pour ce bloc et activer autoindex :

location = /exact {
    index nothing_will_match;
    autoindex on;
}

location  / {

    . . .

}

C'est un moyen d'empêcher un index de changer de contexte, mais ce n'est probablement pas utile pour la plupart des configurations. Généralement, une correspondance exacte sur les répertoires peut être utile pour des choses comme la réécriture de la demande (ce qui entraîne également une nouvelle recherche d'emplacement).

Un autre cas où l'emplacement de traitement peut être réévalué est la directive try_files. Cette directive indique à Nginx de vérifier l'existence d'un ensemble nommé de fichiers ou de répertoires. Le dernier paramètre peut être un URI vers lequel Nginx effectuera une redirection interne.

Considérez la configuration suivante :

root /var/www/main;
location / {
    try_files $uri $uri.html $uri/ /fallback/index.html;
}

location /fallback {
    root /var/www/another;
}

Dans l'exemple ci-dessus, si une demande est faite pour /blahblah, le premier emplacement recevra initialement la demande. Il essaiera de trouver un fichier nommé blahblah dans le répertoire /var/www/main. S'il n'en trouve pas, il poursuit en recherchant un fichier appelé blahblah.html. Il essaiera ensuite de voir s'il existe un répertoire appelé blahblah/ dans le répertoire /var/www/main. À défaut de toutes ces tentatives, il sera redirigé vers /fallback/index.html. Cela déclenchera une autre recherche d'emplacement qui sera capturée par le deuxième bloc d'emplacement. Cela servira le fichier /var/www/another/fallback/index.html.

Une autre directive qui peut conduire à un blocage de localisation est la directive rewrite. Lorsque vous utilisez le paramètre last avec la directive rewrite, ou lorsque vous n'utilisez aucun paramètre, Nginx recherchera un nouvel emplacement correspondant en fonction des résultats de la réécriture.

Par exemple, si nous modifions le dernier exemple pour inclure une réécriture, nous pouvons voir que la requête est parfois passée directement au deuxième emplacement sans s'appuyer sur la directive try_files :

root /var/www/main;
location / {
    rewrite ^/rewriteme/(.*)$ /$1 last;
    try_files $uri $uri.html $uri/ /fallback/index.html;
}

location /fallback {
    root /var/www/another;
}

Dans l'exemple ci-dessus, une demande pour /rewriteme/hello sera initialement traitée par le premier bloc de localisation. Il sera réécrit en /hello et un emplacement sera recherché. Dans ce cas, il correspondra à nouveau au premier emplacement et sera traité par le try_files comme d'habitude, peut-être revenir à /fallback/index.html si rien n'est trouvé (en utilisant la redirection interne try_files nous en avons parlé plus haut).

Cependant, si une demande est faite pour /rewriteme/fallback/hello, le premier bloc correspondra à nouveau. La réécriture sera appliquée à nouveau, cette fois résultant en /fallback/hello. La demande sera ensuite servie à partir du deuxième bloc d'emplacement.

Une situation similaire se produit avec la directive return lors de l'envoi des codes d'état 301 ou 302. La différence dans ce cas est qu'il en résulte une requête entièrement nouvelle sous la forme d'une redirection visible de l'extérieur. Cette même situation peut se produire avec la directive rewrite lors de l'utilisation des drapeaux redirect ou permanent. Cependant, ces recherches d'emplacement ne devraient pas être inattendues, car les redirections visibles de l'extérieur entraînent toujours une nouvelle demande.

La directive error_page peut conduire à une redirection interne similaire à celle créée par try_files. Cette directive est utilisée pour définir ce qui doit se passer lorsque certains codes d'état sont rencontrés. Cela ne sera probablement jamais exécuté si try_files est défini, car cette directive gère le cycle de vie complet d'une requête.

Considérez cet exemple :

root /var/www/main;

location / {
    error_page 404 /another/whoops.html;
}

location /another {
    root /var/www;
}

Chaque requête (autre que celles commençant par /another) sera traitée par le premier bloc, qui servira les fichiers à partir de /var/www/main. Cependant, si un fichier n'est pas trouvé (un statut 404), une redirection interne vers /another/whoops.html se produira, conduisant à une nouvelle recherche d'emplacement qui finira par atterrir sur le deuxième bloc. Ce fichier sera servi sur /var/www/another/whoops.html.

Comme vous pouvez le voir, comprendre les circonstances dans lesquelles Nginx déclenche une nouvelle recherche de localisation peut aider à prédire le comportement que vous verrez lors des requêtes.

Conclusion

Comprendre la manière dont Nginx traite les demandes des clients peut rendre votre travail d'administrateur beaucoup plus facile. Vous pourrez savoir quel bloc de serveur Nginx sélectionnera en fonction de chaque demande client. Vous pourrez également indiquer comment le bloc de localisation sera sélectionné en fonction de l'URI de la requête. Dans l'ensemble, connaître la manière dont Nginx sélectionne différents blocs vous donnera la possibilité de tracer les contextes que Nginx appliquera afin de répondre à chaque demande.