Connaissances sur NGINX utilisées dans OpenResty
API7.ai
September 17, 2022
À travers l'article précédent, vous avez acquis une connaissance générale d'OpenResty. Dans les quelques articles suivants, je vais vous guider à travers les deux piliers d'OpenResty : NGINX et LuaJIT, et vous pourrez mieux apprendre OpenResty en maîtrisant ces bases.
Aujourd'hui, je vais commencer par NGINX, et ici, je ne présenterai que quelques bases de NGINX qui pourraient être utilisées dans OpenResty, ce qui ne représente qu'une infime partie de NGINX.
En ce qui concerne la configuration, dans le développement OpenResty, nous devons prêter attention aux points suivants.
- Configurer
nginx.conf
le moins possible. - Éviter d'utiliser la combinaison de plusieurs directives telles que
if
,set
,rewrite
, etc. - Ne pas utiliser la configuration, les variables et les modules de NGINX lorsque vous pouvez résoudre le problème avec du code Lua.
Ces méthodes maximiseront la lisibilité, la maintenabilité et l'extensibilité. La configuration NGINX suivante est un exemple typique de mauvaise pratique consistant à utiliser la configuration comme du code.
location ~ ^/mobile/(web/app.htm) {
set $type $1;
set $orig_args $args;
if ( $http_user_Agent ~ "(iPhone|iPad|Android)" ) {
rewrite ^/mobile/(.*) http://touch.foo.com/mobile/$1 last;
}
proxy_pass http://foo.com/$type?$orig_args;
}
C'est ce que nous devons éviter lors du développement avec OpenResty.
Configuration NGINX
NGINX contrôle son comportement via des fichiers de configuration, qui peuvent être considérés comme un simple DSL. NGINX lit la configuration au démarrage du processus et la charge en mémoire. Si vous modifiez le fichier de configuration, vous devrez redémarrer ou recharger NGINX et attendre que NGINX relise le fichier de configuration pour que la nouvelle configuration prenne effet. Seule la version commerciale de NGINX offre une partie de cette capacité dynamique au moment de l'exécution, sous forme d'API.
Commençons par la configuration suivante, qui est très simple.
worker_processes auto;
pid logs/nginx.pid;
error_log logs/error.log notice;
worker_rlimit_nofile 65535;
events {
worker_connections 16384;
}
http {
server {
listen 80;
listen 443 ssl;
location / {
proxy_pass https://foo.com;
}
}
}
stream {
server {
listen 53 udp;
}
}
Cependant, même ces configurations simples impliquent des concepts fondamentaux.
Premièrement, chaque directive a son contexte, qui est sa portée dans le fichier de configuration NGINX.
Le niveau supérieur est main
, qui contient des instructions sans rapport avec une activité spécifique, comme worker_processes
, pid
, et error_log
, qui font toutes partie du contexte main
. En outre, il existe une relation hiérarchique entre les contextes. Par exemple, le contexte de location
est server
, le contexte de server
est http
, et le contexte de http
est main
.
Les directives ne peuvent pas être exécutées dans le mauvais contexte. NGINX vérifie si nginx.conf
est légal au démarrage. Par exemple, si nous déplaçons listen 80
; du contexte server
au contexte main
et démarrons le service NGINX, nous verrons une erreur comme celle-ci :
"listen" directive is not allowed here ......
Deuxièmement, NGINX peut gérer non seulement les requêtes HTTP et le trafic HTTPS, mais aussi le trafic UDP et TCP. Le L7 est dans HTTP et le L4 est dans Stream. Dans OpenResty, lua-nginx-module
et stream-lua-nginx-module
correspondent respectivement à ces deux aspects.
Une chose à noter ici est qu'OpenResty ne prend pas en charge toutes les fonctionnalités de NGINX, et vous devez vérifier la version d'OpenResty. La version d'OpenResty est cohérente avec celle de NGINX, ce qui facilite l'identification.
Les directives de configuration impliquées dans nginx.conf
ci-dessus se trouvent dans les modules de base de NGINX ngx_core_module, ngx_http_core_module, et ngx_stream_core_module, que vous pouvez consulter pour voir la documentation spécifique.
Mode MASTER-WORKER
Après avoir compris le fichier de configuration, examinons le mode multi-processus de NGINX (comme illustré ci-dessous). Comme vous pouvez le voir, lorsque NGINX démarre, il y aura un processus Master
et plusieurs processus Worker
(ou un seul processus Worker, selon la configuration).
Tout d'abord, le processus Master
, comme son nom l'indique, joue le rôle de "manager" et n'est pas responsable de la gestion des requêtes des clients. Il gère le processus Worker
, y compris la réception des signaux de l'administrateur et la surveillance de l'état des Worker
. Lorsqu'un processus Worker
se termine anormalement, le processus Master
redémarre un nouveau processus Worker
.
Les processus Worker
sont les "véritables employés" qui gèrent les requêtes des clients. Ils sont créés à partir du processus Master
et sont indépendants les uns des autres. Ce modèle multi-processus est bien plus avancé que le modèle multi-thread d'Apache, sans verrouillage inter-thread et facile à déboguer. Même si un processus plante et se termine, cela n'affecte généralement pas le travail des autres processus Worker
.
OpenResty ajoute un agent privilégié unique au modèle Master-Worker de NGINX. Ce processus n'écoute aucun port et a les mêmes privilèges que le processus Master
de NGINX, il peut donc effectuer des tâches nécessitant des privilèges élevés, comme certaines opérations d'écriture sur des fichiers locaux.
Si le processus privilégié fonctionne avec le mécanisme de mise à jour à chaud binaire de NGINX, OpenResty peut implémenter la mise à jour complète du binaire en cours d'exécution sans dépendre de programmes externes.
Réduire la dépendance aux programmes externes et essayer de résoudre les problèmes au sein du processus OpenResty facilite le déploiement, réduit les coûts d'exploitation et diminue la probabilité d'erreurs du programme. Le processus privilégié et ngx.pipe
dans OpenResty sont tous deux destinés à cet effet.
Phase d'exécution
Les phases d'exécution sont également une caractéristique essentielle de NGINX et sont étroitement liées à l'implémentation spécifique d'OpenResty. NGINX a 11 phases d'exécution, que nous pouvons voir dans le code source de ngx_http_core_module.h
:
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_PRECONTENT_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
Si vous souhaitez en savoir plus sur le rôle de ces 11 phases, vous pouvez lire la documentation de NGINX, donc je ne vais pas m'étendre ici.
Par coïncidence, OpenResty a également 11 directives *_by_lua
liées à la phase NGINX, comme illustré ci-dessous (extrait de la documentation de lua-nginx-module
).
init_by_lua
est exécuté uniquement lorsque le processus Master
est créé, et init_worker_by_lua
est exécuté uniquement lorsque chaque processus Worker
est créé. Les autres commandes *_by_lua
sont déclenchées par les requêtes des clients et sont exécutées de manière répétée.
Ainsi, pendant la phase init_by_lua
, nous pouvons précharger les modules Lua et les données publiques en lecture seule pour tirer parti de la fonctionnalité COW (copy on write) du système d'exploitation et économiser de la mémoire.
La plupart des opérations peuvent être effectuées dans content_by_lua
, mais je recommande de les diviser selon différentes fonctions, comme suit.
set_by_lua
: définition des variables.rewrite_by_lua
: redirection, réécriture, etc.access_by_lua
: accès, permissions, etc.content_by_lua
: génération du contenu de retour.header_filter_by_lua
: traitement du filtrage des en-têtes de réponse.body_filter_by_lua
: traitement du filtrage du corps de la réponse.log_by_lua
: journalisation.
Prenons un exemple pour montrer les avantages de cette division. Supposons que de nombreuses API en texte clair soient fournies à l'extérieur, et que nous devions maintenant ajouter une logique de chiffrement et de déchiffrement personnalisée. Devons-nous modifier le code de toutes les API ?
location /mixed {
content_by_lua '...';
}
Bien sûr que non. En utilisant la fonctionnalité de phase, nous pouvons déchiffrer dans la phase access
et chiffrer dans la phase body filter
sans apporter de modifications au code dans la phase content
d'origine.
location /mixed {
access_by_lua '...';
content_by_lua '...';
body_filter_by_lua '...';
}
Mise à jour à chaud du binaire NGINX
Enfin, permettez-moi d'expliquer brièvement la mise à jour à chaud du binaire NGINX. Nous savons qu'après avoir modifié le fichier de configuration NGINX, vous devez le recharger pour qu'il prenne effet. Mais lorsque NGINX se met à jour lui-même, il peut le faire à chaud. Cela peut sembler mettre la charrue avant les bœufs, mais c'est compréhensible étant donné que NGINX a commencé avec l'équilibrage de charge statique traditionnel, le proxy inverse et la mise en cache de fichiers.
La mise à jour à chaud est effectuée en envoyant les signaux USR2
et WINCH
à l'ancien processus Master
. Pour ces deux étapes, la première démarre le nouveau processus Master
; la seconde arrête progressivement le processus Worker
.
Après ces deux étapes, le nouveau Master
et le nouveau Worker
sont démarrés. À ce stade, l'ancien Master
ne se termine pas. La raison de ne pas quitter est simple : si vous avez besoin de revenir en arrière, vous pouvez toujours envoyer des signaux HUP
à l'ancien Master
. Bien sûr, si vous avez déterminé que vous n'avez pas besoin de revenir en arrière, vous pouvez envoyer un signal KILL
à l'ancien Master
pour qu'il se termine.
C'est tout, et la mise à jour à chaud du binaire NGINX est terminée.
Si vous souhaitez en savoir plus sur ce sujet, vous pouvez consulter la documentation officielle pour continuer à apprendre.
Résumé
En général, ce que vous utilisez dans OpenResty sont les bases de NGINX, principalement liées à la configuration, aux processus maître-esclave, aux phases d'exécution, etc. Les autres choses qui peuvent être résolues avec du code Lua sont résolues avec du code autant que possible, plutôt que d'utiliser des modules et des configurations NGINX, ce qui représente un changement de mentalité lors de l'apprentissage d'OpenResty.
Enfin, je vous laisse avec une question ouverte : Nginx prend officiellement en charge NJS, ce qui signifie que vous pouvez écrire du JS pour contrôler une partie de la logique NGINX, similaire à OpenResty. Que pensez-vous de cela ? N'hésitez pas à partager cet article.