Conhecimento do NGINX utilizado no OpenResty
API7.ai
September 17, 2022
Através do post anterior, você tem um conhecimento geral sobre o OpenResty. Nos próximos artigos, vou levá-lo através dos dois pilares do OpenResty: NGINX e LuaJIT, e você poderá aprender melhor o OpenResty dominando esses fundamentos.
Hoje vou começar com o NGINX, e aqui vou apenas introduzir alguns conceitos básicos do NGINX que podem ser usados no OpenResty, que é apenas um pequeno subconjunto do NGINX.
Em relação à configuração, no desenvolvimento do OpenResty, precisamos prestar atenção aos seguintes pontos.
- Configurar o
nginx.conf
o mínimo possível. - Evitar o uso de combinações de múltiplas diretivas como
if
,set
,rewrite
, etc. - Não usar configurações, variáveis e módulos do NGINX quando for possível resolver com código Lua.
Esses métodos maximizarão a legibilidade, a manutenção e a extensibilidade. A seguinte configuração do NGINX é um exemplo típico de má prática de usar configuração 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;
}
Isso é o que precisamos evitar ao desenvolver com OpenResty.
Configuração do NGINX
O NGINX controla seu comportamento através de arquivos de configuração, que podem ser considerados como uma DSL simples. O NGINX lê a configuração quando o processo é iniciado e a carrega na memória. Se você modificar o arquivo de configuração, precisará reiniciar ou recarregar o NGINX e esperar até que o NGINX leia o arquivo de configuração novamente para que a nova configuração entre em vigor. Apenas a versão comercial do NGINX oferece parte dessa capacidade dinâmica em tempo de execução, na forma de APIs.
Vamos começar com a seguinte configuração, que é muito simples.
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;
}
}
No entanto, mesmo essas configurações simples envolvem alguns conceitos fundamentais.
Primeiro, cada diretiva tem seu contexto, que é seu escopo no arquivo de configuração do NGINX.
O nível superior é o main
, que contém algumas instruções que não têm relação com o negócio específico, como worker_processes
, pid
e error_log
, que fazem parte do contexto main
. Além disso, há uma relação hierárquica entre os contextos. Por exemplo, o contexto de location
é server
, o contexto de server
é http
, e o contexto de http
é main
.
As diretivas não podem ser executadas no contexto errado. O NGINX verificará se o nginx.conf
é legal ao iniciar. Por exemplo, se mudarmos listen 80
; do contexto server
para o contexto main
e iniciarmos o serviço NGINX, veremos um erro como este:
"listen" directive is not allowed here ......
Segundo, o NGINX pode lidar não apenas com solicitações HTTP e tráfego HTTPS, mas também com tráfego UDP e TCP. O L7 está no HTTP e o L4 está no Stream. No OpenResty, lua-nginx-module
e stream-lua-nginx-module
correspondem a esses dois, respectivamente.
Uma coisa a se notar aqui é que o OpenResty não suporta todos os recursos do NGINX, e você precisa verificar a versão do OpenResty. A versão do OpenResty é consistente com a do NGINX, facilitando a identificação.
As diretivas de configuração envolvidas no nginx.conf
acima estão nos módulos principais do NGINX ngx_core_module, ngx_http_core_module, e ngx_stream_core_module, que você pode clicar para ver a documentação específica.
Modo MASTER-WORKER
Depois de entender o arquivo de configuração, vamos olhar para o modo de multiprocessos do NGINX (como mostrado na figura abaixo). Como você pode ver, quando o NGINX é iniciado, haverá um processo Master
e vários processos Worker
(ou apenas um processo Worker, dependendo de como você o configurou).
Primeiro, o processo Master
, como o nome sugere, desempenha o papel de "gerente" e não é responsável por lidar com solicitações dos clientes. Ele gerencia o processo Worker
, incluindo receber sinais do administrador e monitorar o status dos Worker
s. Quando um processo Worker
sai de forma anormal, o processo Master
reiniciará um novo processo Worker
.
Os processos Worker
são os "funcionários reais" que lidam com as solicitações dos clientes. Eles são bifurcados do processo Master
e são independentes uns dos outros. Esse modelo de multiprocessos é muito mais avançado que o modelo de multithreads do Apache, sem bloqueios entre threads e fácil de depurar. Mesmo que um processo falhe e saia, geralmente não afeta o trabalho dos outros processos Worker
.
O OpenResty adiciona um agente privilegiado único ao modelo Master-Worker do NGINX. Esse processo não escuta em nenhuma porta e tem os mesmos privilégios que o processo Master
do NGINX, então ele pode fazer algumas tarefas que exigem altos privilégios, como algumas operações de escrita em arquivos de disco local.
Se o processo privilegiado trabalhar com o mecanismo de atualização quente binária do NGINX, o OpenResty pode implementar toda a atualização binária em tempo real sem depender de programas externos.
Reduzir a dependência de programas externos e tentar resolver problemas dentro do processo OpenResty facilita a implantação, reduz os custos de operação e manutenção e diminui a probabilidade de erros no programa. O processo privilegiado e o ngx.pipe
no OpenResty são todos para esse propósito.
Fases de Execução
As fases de execução também são uma característica essencial do NGINX e estão intimamente relacionadas à implementação específica do OpenResty. O NGINX tem 11 fases de execução, que podemos ver no código-fonte 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;
Se você quiser saber mais sobre o papel dessas 11 fases, pode ler a documentação do NGINX, então não vou entrar em detalhes aqui.
Coincidentemente, o OpenResty também tem 11 diretivas *_by_lua
relacionadas à fase do NGINX, como mostrado na figura abaixo (da documentação do lua-nginx-module
).
init_by_lua
é executado apenas quando o processo Master
é criado, e init_worker_by_lua
é executado apenas quando cada processo Worker
é criado. As outras diretivas *_by_lua
são acionadas por solicitações de clientes e são executadas repetidamente.
Portanto, durante a fase init_by_lua
, podemos pré-carregar módulos Lua e dados públicos somente leitura para aproveitar o recurso COW (copy on write) do sistema operacional e economizar memória.
A maioria das operações pode ser feita dentro de content_by_lua
, mas eu recomendaria dividi-las de acordo com diferentes funções, como a seguir.
set_by_lua
: definindo variáveis.rewrite_by_lua
: encaminhamento, redirecionamento, etc.access_by_lua
: acesso, permissões, etc.content_by_lua
: gerando conteúdo de retorno.header_filter_by_lua
: processamento de filtragem de cabeçalho de resposta.body_filter_by_lua
: processamento de filtragem de corpo de resposta.log_by_lua
: registro de logs.
Vou dar um exemplo para mostrar os benefícios de dividir dessa forma. Vamos supor que muitas APIs de texto simples sejam fornecidas externamente, e agora precisamos adicionar lógica personalizada de criptografia e descriptografia. Então, precisamos mudar o código de todas as APIs?
location /mixed {
content_by_lua '...';
}
Claro que não. Usando o recurso de fase, podemos descriptografar na fase access
e criptografar na fase body filter
sem fazer nenhuma alteração no código na fase content
original.
location /mixed {
access_by_lua '...';
content_by_lua '...';
body_filter_by_lua '...';
}
Atualização Binária do NGINX em Tempo Real
Finalmente, vou explicar brevemente a atualização binária do NGINX em tempo real. Sabemos que, após modificar o arquivo de configuração do NGINX, você precisa recarregá-lo para que ele funcione. Mas quando o NGINX se atualiza, ele pode fazer isso em tempo real. Isso pode parecer colocar o carro na frente dos bois, mas é compreensível, dado que o NGINX começou com balanceamento de carga tradicional, proxy reverso e cache de arquivos.
A atualização quente é feita enviando os sinais USR2
e WINCH
para o processo Master
antigo. Para essas duas etapas, a primeira inicia o novo processo Master
; a segunda desliga o processo Worker
gradualmente.
Após essas duas etapas, o novo Master
e o novo Worker
são iniciados. Neste ponto, o Master
antigo não sai. A razão para não sair é simples: se você precisar reverter, ainda pode enviar sinais HUP
para o Master
antigo. Claro, se você tiver certeza de que não precisa reverter, pode enviar um sinal KILL
para o Master
antigo para sair.
E é isso, a atualização binária do NGINX em tempo real está concluída.
Se você quiser saber informações mais detalhadas sobre isso, pode verificar a documentação oficial para continuar aprendendo.
Resumo
Em geral, o que você usa no OpenResty são os fundamentos do NGINX, principalmente relacionados à configuração, processos mestre-escravo, fases de execução, etc. As outras coisas que podem ser resolvidas com código Lua são resolvidas com código sempre que possível, em vez de usar módulos e configurações do NGINX, o que é uma mudança de pensamento ao aprender OpenResty.
Por fim, deixei uma pergunta aberta para você: o Nginx oficialmente suporta NJS, o que significa que você pode escrever JS para controlar parte da lógica do NGINX, semelhante ao OpenResty. O que você acha disso? Bem-vindo para compartilhar este artigo.