Conocimiento de NGINX utilizado en OpenResty

API7.ai

September 17, 2022

OpenResty (NGINX + Lua)

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).

Modo Worker de NGINX

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 Workers. 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).

Orden de las Directivas del Módulo Lua NGINX

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.

Share article link