Conocimiento de NGINX utilizado en OpenResty
API7.ai
September 17, 2022
A través del post anterior, ya tienes un conocimiento general de OpenResty. En los siguientes artículos, te guiaré a través de los dos pilares de OpenResty: NGINX y LuaJIT, y podrás aprender mejor OpenResty dominando estos conceptos básicos.
Hoy comenzaré con NGINX, y aquí solo introduciré algunos conceptos básicos de NGINX que pueden ser utilizados en OpenResty, que es solo un pequeño subconjunto de NGINX.
En cuanto a la configuración, en el desarrollo de OpenResty, debemos prestar atención a los siguientes puntos.
- Configurar
nginx.conf
lo menos posible. - Evitar el uso de combinaciones de múltiples directivas como
if
,set
,rewrite
, etc. - No usar configuraciones, variables y módulos de NGINX cuando se pueda resolver con código Lua.
Estos métodos maximizarán la legibilidad, mantenibilidad y extensibilidad. La siguiente configuración de NGINX es un ejemplo típico de mal uso de la configuración como código.
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;
}
Esto es lo que debemos evitar al desarrollar con OpenResty.
Configuración de NGINX
NGINX controla su comportamiento a través de archivos de configuración, que pueden considerarse como un DSL simple. NGINX lee la configuración cuando el proceso se inicia y la carga en memoria. Si modificas el archivo de configuración, necesitarás reiniciar o recargar NGINX y esperar a que NGINX lea nuevamente el archivo de configuración para que la nueva configuración surta efecto. Solo la versión comercial de NGINX proporciona algunas de estas capacidades dinámicas en tiempo de ejecución, en forma de APIs.
Comencemos con la siguiente configuración, que es muy 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;
}
}
Sin embargo, incluso estas configuraciones simples involucran algunos conceptos fundamentales.
Primero, cada directiva tiene su contexto, que es su alcance en el archivo de configuración de NGINX.
El nivel superior es main
, que contiene algunas instrucciones que no están relacionadas con el negocio específico, como worker_processes
, pid
y error_log
, que forman parte del contexto main
. Además, existe una relación jerárquica entre los contextos. Por ejemplo, el contexto de location
es server
, el contexto de server
es http
, y el contexto de http
es main
.
Las directivas no pueden ejecutarse en el contexto incorrecto. NGINX verificará si nginx.conf
es legal cuando se inicie. Por ejemplo, si cambiamos listen 80
; del contexto server
al contexto main
e iniciamos el servicio NGINX, veremos un error como este:
"listen" directive is not allowed here ......
En segundo lugar, NGINX puede manejar no solo solicitudes HTTP y tráfico HTTPS, sino también tráfico UDP y TCP. El L7 está en HTTP y el L4 está en Stream. En OpenResty, lua-nginx-module
y stream-lua-nginx-module
corresponden a estos dos, respectivamente.
Algo a tener en cuenta aquí es que OpenResty no admite todas las funciones de NGINX, y debes ver la versión de OpenResty. La versión de OpenResty es consistente con NGINX, lo que facilita su identificación.
Las directivas de configuración involucradas en nginx.conf
anteriormente están en los módulos centrales de NGINX ngx_core_module, ngx_http_core_module, y ngx_stream_core_module, a los que puedes hacer clic para ver la documentación específica.
Modo MASTER-WORKER
Después de entender el archivo de configuración, veamos el modo multiproceso de NGINX (como se muestra en la figura a continuación). Como puedes ver, cuando NGINX se inicia, habrá un proceso Master
y múltiples procesos Worker
(o solo un proceso Worker, dependiendo de cómo lo configures).
En primer lugar, el proceso Master
, como su nombre lo indica, desempeña el papel de "gerente" y no es responsable de manejar las solicitudes de los clientes. Gestiona el proceso Worker
, incluyendo recibir señales del administrador y monitorear el estado de los Worker
s. Cuando un proceso Worker
sale de manera anormal, el proceso Master
reiniciará un nuevo proceso Worker
.
Los procesos Worker
son los "empleados reales" que manejan las solicitudes de los clientes. Son bifurcados desde el proceso Master
y son independientes entre sí. Este modelo multiproceso es mucho más avanzado que el modelo multihilo de Apache, sin bloqueos entre hilos y fácil de depurar. Incluso si un proceso se bloquea y sale, generalmente no afecta el trabajo de los otros procesos Worker
.
OpenResty agrega un agente privilegiado único al modelo Master-Worker de NGINX. Este proceso no escucha en ningún puerto y tiene los mismos privilegios que el proceso Master
de NGINX, por lo que puede realizar algunas tareas que requieren altos privilegios, como algunas operaciones de escritura en archivos de disco local.
Si el proceso privilegiado funciona con el mecanismo de actualización en caliente binaria de NGINX, OpenResty puede implementar la actualización completa del binario sobre la marcha sin depender de programas externos.
Reducir la dependencia de programas externos y tratar de resolver problemas dentro del proceso de OpenResty facilita la implementación, reduce los costos de operación y mantenimiento, y disminuye la probabilidad de errores en el programa. El proceso privilegiado y ngx.pipe
en OpenResty están destinados a este propósito.
Fases de Ejecución
Las fases de ejecución también son una característica esencial de NGINX y están estrechamente relacionadas con la implementación específica de OpenResty. NGINX tiene 11 fases de ejecución, que podemos ver en el código fuente 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 deseas aprender más sobre el papel de estas 11 fases, puedes leer la documentación de NGINX, por lo que no entraré en detalles aquí.
Coincidentemente, OpenResty también tiene 11 directivas *_by_lua
relacionadas con la fase de NGINX, como se muestra en la figura a continuación (de la documentación de lua-nginx-module
).
init_by_lua
se ejecuta solo cuando se crea el proceso Master
, y init_worker_by_lua
se ejecuta solo cuando se crea cada proceso Worker
. Las otras directivas *_by_lua
son activadas por solicitudes de clientes y se ejecutan repetidamente.
Por lo tanto, durante la fase init_by_lua
, podemos precargar módulos Lua y datos de solo lectura públicos para aprovechar la función COW (copy on write) del sistema operativo y ahorrar memoria.
La mayoría de las operaciones se pueden realizar dentro de content_by_lua
, pero recomendaría dividirlas según diferentes funciones, como las siguientes.
set_by_lua
: establecer variables.rewrite_by_lua
: reenvío, redirección, etc.access_by_lua
: acceso, permisos, etc.content_by_lua
: generar contenido de respuesta.header_filter_by_lua
: procesamiento de filtrado de encabezados de respuesta.body_filter_by_lua
: procesamiento de filtrado del cuerpo de la respuesta.log_by_lua
: registro.
Permíteme darte un ejemplo para mostrar los beneficios de dividirlo de esta manera. Supongamos que se proporcionan muchas APIs en texto plano externamente, y ahora necesitamos agregar lógica personalizada de cifrado y descifrado. Entonces, ¿necesitamos cambiar el código de todas las APIs?
location /mixed {
content_by_lua '...';
}
Por supuesto que no. Usando la característica de fases, podemos descifrar en la fase access
y cifrar en la fase body filter
sin hacer ningún cambio en el código de la fase content
original.
location /mixed {
access_by_lua '...';
content_by_lua '...';
body_filter_by_lua '...';
}
Actualización en Caliente del Binario de NGINX
Finalmente, permíteme explicar brevemente la actualización en caliente del binario de NGINX. Sabemos que después de modificar el archivo de configuración de NGINX, necesitas recargarlo para que surta efecto. Pero cuando NGINX se actualiza a sí mismo, puede hacerlo en caliente. Esto puede parecer poner el carro delante del caballo, pero es comprensible dado que NGINX comenzó con balanceo de carga estático tradicional, proxy inverso y almacenamiento en caché de archivos.
La actualización en caliente se realiza enviando señales USR2
y WINCH
al proceso Master
antiguo. Para estos dos pasos, el primero inicia el nuevo proceso Master
; el segundo cierra gradualmente el proceso Worker
.
Después de estos dos pasos, el nuevo Master
y el nuevo Worker
se inician. En este punto, el Master
antiguo no se cierra. La razón para no cerrarlo es simple: si necesitas retroceder, aún puedes enviar señales HUP
al Master
antiguo. Por supuesto, si has determinado que no necesitas retroceder, puedes enviar una señal KILL
al Master
antiguo para que se cierre.
Eso es todo, y la actualización en caliente del binario de NGINX está completa.
Si deseas obtener información más detallada sobre esto, puedes consultar la documentación oficial para seguir aprendiendo.
Resumen
En general, lo que usas en OpenResty son los conceptos básicos de NGINX, principalmente relacionados con la configuración, procesos maestro-esclavo, fases de ejecución, etc. Las otras cosas que se pueden resolver con código Lua se resuelven con código tanto como sea posible, en lugar de usar módulos y configuraciones de NGINX, lo cual es un cambio de mentalidad al aprender OpenResty.
Finalmente, te dejo una pregunta abierta: Nginx oficialmente admite NJS, lo que significa que puedes escribir JS para controlar parte de la lógica de NGINX, similar a OpenResty. ¿Qué opinas sobre esto? Bienvenido a compartir este artículo.