FAQ de OpenResty | Permisos de Proceso Privilegiado, Fase de Ejecución y más
API7.ai
November 11, 2022
Este artículo contiene seis preguntas frecuentes:
1. Permisos de Procesos Privilegiados
P: ¿Qué es un proceso privilegiado? ¿Cómo podría un usuario no privilegiado obtener permisos de root
? ¿Puedes presentar algunos escenarios de procesos privilegiados?
R: Los permisos del proceso privilegiado son los mismos que los del proceso master
. Si inicias OpenResty como un usuario no privilegiado, entonces el proceso master
hereda los privilegios del usuario, lo que significa que el "proceso privilegiado" no tiene derechos en este momento.
Es fácil entender que no hay privilegios de root cuando un usuario normal inicia un proceso.
En cuanto a los escenarios de uso del proceso privilegiado, generalmente lo utilizamos para tareas que requieren altos privilegios, como la limpieza de registros y el reinicio de OpenResty. Sin embargo, ten cuidado de no usar el proceso privilegiado para ejecutar tareas del proceso worker debido a los riesgos de seguridad.
Un desarrollador ejecuta todas las tareas de timer
en el proceso privilegiado. ¿Por qué lo hace? Porque solo hay un proceso privilegiado, de esta manera, el timer
no se inicia repetidamente.
El desarrollador es "inteligente" ya que logró el objetivo sin usar worker.id
. Sin embargo, no olvides que es muy peligroso si la tarea de timer
depende de la entrada del cliente.
2. Fases y Depuración
P: Después de ejecutar ngx.say('hello')
, ¿OpenResty responderá directamente al cliente después de ejecutar el resto de la lógica en la fase actual? Es decir, ¿no continuará ejecutando las fases posteriores?
R: No es así. Podemos observar su fase de ejecución:
Puedes probar ngx.say
en la fase content
primero, luego usar ngx.log
en la fase log
o body filter
para imprimir el registro.
En artículos anteriores, no mencioné específicamente el problema de depurar código en OpenResty, lo cual puede confundir a los desarrolladores.
No hay características avanzadas para depurar código en OpenResty, como puntos de interrupción (hay algunos complementos de pago, pero no los he usado), y solo puedes usar ngx.say
y ngx.log
para ver la salida. Así es como todos los desarrolladores que conozco hacen su depuración, incluidos los autores y colaboradores de OpenResty. Por lo tanto, necesitas casos de prueba robustos y registros de depuración como garantía.
3. La Práctica de ngx.exit
P: En uno de los artículos anteriores, hay una descripción: el código de estado HTTP de OpenResty tiene una constante especial ngx.OK
. Después de ejecutar ngx.exit(ngx.OK)
, la solicitud sale de la fase actual y pasa a la siguiente fase en lugar de regresar directamente al cliente.
Recuerdo que ngx.OK
no debería considerarse como un código de estado HTTP, su valor es 0
. Mi entendimiento es:
- Después de ejecutar
ngx.exit(ngx.OK)
,ngx.exit(ngx.ERROR)
ongx.exit(ngx.DECLINED)
, la solicitud sale de la fase actual y pasa a la siguiente fase. - Cuando
ngx.exit(ngx.HTTP_*)
toma los diversos códigos de estado HTTP dengx.HTTP_*
como parámetro, responderá directamente al cliente.
No sé si mi entendimiento es correcto.
R: Respecto a tu primera pregunta, ngx.ok
no es un código de estado HTTP, sino una constante en OpenResty con un valor de 0
.
En cuanto a la segunda pregunta, la documentación oficial de ngx.exit
puede ser la respuesta exacta:
-
Cuando status >= 200 (es decir, ngx.HTTP_OK y superior), interrumpirá la ejecución de la solicitud actual y devolverá el código de estado a nginx.
-
Cuando status == 0 (es decir, ngx.OK), solo saldrá del manejador de la fase actual (o el manejador de contenido si se usa la directiva content_by_lua*) y continuará ejecutando las fases posteriores (si las hay) para la solicitud actual.
Sin embargo, la documentación no menciona cómo OpenResty maneja ngx.exit(ngx.ERROR)
y ngx.exit(ngx.DECLINED)
. Podemos hacer una prueba de la siguiente manera:
location /lua {
rewrite_by_lua "ngx.exit(ngx.ERROR)";
echo hello;
}
Al visitar este location
, puedes ver que el código de respuesta HTTP está vacío, el cuerpo de la respuesta también está vacío, y no pasa a la siguiente fase de ejecución.
A medida que te adentras más en el proceso de aprendizaje de OpenResty, en algún momento te darás cuenta de que ni la documentación ni los casos de prueba pueden responder tus preguntas. En este punto, necesitas construir tus propios casos de prueba para verificar tus ideas. Puedes hacerlo manualmente o agregar las pruebas al conjunto de casos de prueba construido por test::nginx
.
4. Variables y Condición de Carrera
P: Como se mencionó anteriormente, el alcance de la variable ngx.var
está entre los módulos nginx C
y lua-nginx-module
.
-
No entiendo muy bien esto. Desde la perspectiva de la solicitud, ¿significa una sola solicitud en un proceso worker?
-
Mi entendimiento es que cuando manipulamos variables dentro de un módulo. Si hay una operación de bloqueo entre dos operaciones, puede existir una condición de carrera. Entonces, si no hay una operación de bloqueo entre dos operaciones, y resulta que el proceso actual entra en la cola de listos cuando se agota el tiempo de CPU, ¿es posible que exista una condición de carrera?
R: Veamos estas preguntas.
Primero, respecto a la variable ngx.var
, tu entendimiento es correcto. El ciclo de vida de ngx.var
es el mismo que el de la solicitud, y desaparece cuando la solicitud termina. Pero su ventaja es que los datos pueden pasarse en módulos C y código Lua, lo cual no es posible de otras maneras.
Segundo, siempre que haya una operación de yield
entre dos operaciones, puede haber una condición de carrera en lugar de una operación de bloqueo. No hay condición de carrera cuando hay una operación de bloqueo. En otras palabras, siempre que no des la iniciativa al bucle de eventos de NGINX, no habrá condición de carrera.
5. El shared dict
No Necesita Bloqueos
P: Si múltiples workers almacenan datos concurrentemente, ¿es necesario agregar bloqueos?
Por ejemplo:
resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
local lock= ngx.xxxx.lock
lock.lock()
dogs:set("Jim", 8)
lock.unlock()
local v = dogs:get("Jim")
ngx.say(v)
'
R: No necesitas agregar un bloqueo aquí porque, sin importar si es una operación get
o set
, la operación de shared dict
es atómica. OpenResty ya ha considerado este tipo de manejo similar a bloqueos.
6. Operaciones de Tiempo en OpenResty
P: Usando ngx.now()
para obtener el tiempo, ¿ocurre esto en la fase de restauración de la función resume
?
R: NGINX está diseñado con el rendimiento como prioridad y almacena en caché el tiempo. Podemos verificar esto mediante el código fuente de ngx.now
:
static int
ngx_http_lua_ngx_now(lua_State *L)
{
ngx_time_t *tp;
tp = ngx_timeofday();
lua_pushnumber(L, (lua_Number) (tp->sec + tp->msec / 1000.0L));
return 1;
}
Como puedes ver, detrás de la función ngx.now()
que obtiene el tiempo actual está la función ngx_timeofday
de NGINX. La función ngx_timeofday
es una definición de macro:
#define ngx_timeofday() (ngx_time_t *) ngx_cached_time
Aquí el valor de ngx_cached_time
solo se actualizará en la función ngx_time_update
.
Entonces, la pregunta se convierte en "¿cuándo se llama a la función ngx_time_update
?" Si lo rastreas en el código fuente de NGINX, verás que las llamadas a ngx_time_update
ocurren en el bucle de eventos, entonces este problema se resuelve.
Resumen
También deberías poder darte cuenta a través de estas preguntas, el beneficio de los proyectos de código abierto es que puedes seguir las pistas y buscar respuestas en el código fuente, lo que te dará una sensación de resolver el caso.
Finalmente, espero que a través de la comunicación y las preguntas y respuestas, pueda ayudarte a convertir lo que aprendes en lo que obtienes. También te invito a compartir este artículo, y juntos nos comunicaremos y mejoraremos.