Nginx
Isaac Alves Pinheiro
Posted on May 24, 2023
O NGINX é um servidor web/proxy reverso rápido, de alto rendimento e um proxy para correio eletrônico (IMAP/POP3), é um software livre e de código-aberto. Ele é um serviço, é um programa que roda e que serve para responder requisições web.
Ele não usa somente aquela ideia de processos do servidor Apache, threads e programação paralela, ele usa um outro conceito de programação assíncrona muito interessante:
Então, temos aqui uma base do funcionamento do NGINX. Quando você inicia um serviço do NGINX, ele cria um processo principal como se fosse o patrão desses colaboradores aqui. No qual esse patrão vai criar alguns processos colaboradores, alguns worker process. Esses worker process são criados baseado no número de núcleos (cores) que o seu processador tem. Suponha que eu tenho um servidor que tem um processador com quatro núcleos. Então eu poderia criar, por exemplo, 4 processos worker process para tratar requisições. Porque assim eu tenho um maior número de processos tratando cada número de requisições, então eu consigo tratar mais requisições.
Só que a sacada é: Não é como se cada processo tratasse uma requisição. Não é isso! Cada um desses processos trata um número grande de requisições, várias requisições, e para que ele consiga tratar mais de uma requisição, ele usa um conceito de Multiplexing I/O. Ou seja, ele faz mais de uma coisa de forma assíncrona.
Então, imagine que chegou uma requisição nesse worker aqui e depois chega uma nova requisição que caiu nesse mesmo worker. O que ele vai fazer? Ele vai colocar para executar o primeiro. Enquanto esse processo está esperando essa tarefa ser executada - por exemplo: o servidor de aplicação responder e o arquivo ser carregado - ele já trata outra requisição, coloca para carregar imagem e manda requisição para servidor de imagem. Depois ele pega a resposta da primeira requisição e devolve, continua tratando a segunda e devolve. Por isso o NGINX veio com a proposta de ser o servidor web mais rápido.
Existem benchmarks que colocam realmente o NGINX lá no topo, ou seja, é um servidor muito performático. Mas obviamente não existe bala de prata e ele tem seus cenários onde ele trabalha muito bem e não é ferramenta ideal.
Funcionamento do NGINX: Ele inicia um servidor, um processo. Esse processo cria outros processos colaboradores, e cada um desses processos consegue tratar várias requisições utilizando o conceito de programação assíncrona, garantindo assim uma grande performance.
Difere-se muito do servidor Apache, porque ele transfere a responsabilidade de um servidor web para um servidor de aplicação além de ser:
- Tolerante a falhas;
- Compatível com o IPv6;
Exemplo: Com Java você pode ter um Tomcat rodando, ou com PHP você vai ter um PHP FPM rodando. Enfim, você vai ter algum servidor de aplicação aqui, e o NGINX consegue se conectar à ele. Mas nós vamos focar só na parte do NGINX, sem nos conectarmos a algum servidor de aplicação.
Instalação do NGINX
Como baixar e configurar o NGINX server.
Windows
macOS
brew install nginx
Linux
sudo apt install nginx
Para iniciar o nginx é simples, basta escrever o nome dele na linha de comando no terminal:
nginx
Basta somente executa-lo e acessar o http://localhost:8080
[NGINX] Servidor HTTP
Exibe ajuda e lá podemos ver os caminhos do arquivo de configuração
nginx -h
Arquivos de configuração do servidor (nginx.conf
)
Essa é a área que configuramos o nosso servidor Nginx:
#user nobody;
worker_processes 1; # auto
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024; # limite de processos em um sistema operacional de arquivos assíncronos (file descriptors = sockets, connections, etc...)
}
http { # Servidor HTTP
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server { # Servidor Web
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
include servers/*;
}
Você pode usar o comando abaixo para retirar os comentários e visualizar somente o necessário, funciona como um filtro:
cat /opt/homebrew/etc/nginx/nginx.conf | grep -ve '^.\s*#' | grep -ve '^#' | sed '/^$/d'
Dessa forma, o arquivo de configuração fica assim:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8080;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
include servers/*;
}
Vamos editar nosso arquivo principal da página do Nginx. Para isso, acesse o comando abaixo:
nano /opt/homebrew/Cellar/nginx/1.23.2/html/index.html
Se definirmos location /
, significa /
literalmente tudo, ou seja, podemos definir qual o diretório raiz do projeto, qual o arquivo padrão, regras de redirecionamento, etc. Percebe-se que o arquivo não mostra o preview dele no localhost.
Recarrega o arquivo nginx
nginx -s reload
Testar o arquivo de configuração
nginx -t
Páginas de erro HTTP
Os códigos de erro de protocolo HTTP começam na faixa dos 400
:
server {
listen 80;
server_name localhost;
location / {
root C:/Users/isaac/Dev/nginx;
index index.html
}
error_page 404 400 401 402 /erro.html;
}
[NGINX] Proxy Reverso
Um proxy de rede funciona basicamente como se fosse um "túnel" ou "filtro", quando acessamos uma determinada área, o proxy . Então é assim que funciona um proxy, ele literalmente pega as requisições e dispara na internet para um servidor web acessar as informações, ou sejam um proxy reverso ele pega as requisições feitas e redireciona ainda para outro servidor (Como se acrescentasse mais uma etapa no processo de proxy).
Então, o proxy reverso é um servidor web que recebe as requisições e distribui para outros servidores.
Porque normalmente um proxy fica no lado do cliente. O conceito padrão de proxy é algo que fica no lado do cliente interceptando os pacotes de rede. Como nesse caso o proxy está no lado do servidor, chamamos de proxy reverso.
location / {
proxy_pass http://localhost;
}
Com esse simples código, eu tenho o conceito de proxy reverso.
Por que realizar um proxy reverso e não apenas deixar o servidor de aplicação lidar com todas as requisições? Com nginx na frente podemos responder arquivos estáticos muito mais rapidamente. Esse é um dos principais motivos. O nginx é um servidor incrivelmente performático, então nós ganhamos muito ao não enviar todas as requisições para o servidor de aplicação. O nginx pode enviar diretamente os arquivos estáticos sem processar nada, além de poder definir cache, compressão, etc.
Servidor 2 em 1
Com o proxy reverso podemos receber uma requisição e devolver muito rápido se for um arquivo estático, fazer cash e etc. Já para fazer uma rota ou uma URL que precisa de lógica, que precisa ser processada, eu mando para o servidor de aplicação.
Exemplo (default.conf):
server {
listen 80;
server_name localhost;
location / {
root /Users/isaac/Dev/nginx;
index index.html
}
location ~ \.php$ {
proxy_pass http://localhost:8000;
}
error_page 404 400 401 402 /erro.html
}
php -S localhost:8000
Com nginx na frente podemos responder arquivos estáticos muito mais rapidamente. Esse é um dos principais motivos. O nginx é um servidor incrivelmente performático, então nós ganhamos muito ao não enviar todas as requisições para o servidor de aplicação. O nginx pode enviar diretamente os arquivos estáticos sem processar nada, além de poder definir cache, compressão, etc.
[NGINX] API Gateway
Monolítica, Microserviços e Múltiplos serviços
Resumidamente:
Monolithic (Monolítica): é nada mais e nada menos do que uma arquitetura para uma aplicação rodando em somente um serviço contendo uma estrutura com poucas seções de maneira estruturada para o servidor;
Microserviços (Microservices): é nada mais é do que uma arquitetura com muitos micro (pequenos) serviços rodando em uma ou várias aplicações, ideal para projetos de grande porte e com diversas áreas diferentes.
Vamos criar dois serviços dentro de (microserviços.conf):
server {
listen 8001;
server_name localhost;
location / {
root /Users/isaac/Dev/nginx/servico1;
index index.html
}
error_page 404 400 401 /erro.html;
}
server {
listen 8002;
server_name localhost;
location / {
root /Users/isaac/Dev/nginx/servico2;
index index.html
}
error_page 404 400 401 /erro.html;
}
Agora vamos criar a index.html:
mkdir Dev/nginx/servico1 Dev/nginx/servico2
echo "Servico 1" > Dev/nginx/servico1/index.html
echo "Servico 2" > Dev/nginx/servico1/index.html
Configurando um servidor web que faz o redirecionamento para outros múltiplos servidores baseados na URL recebida (ponto de entrada para centralizar o acesso a múltiplos serviços em um único host):
location /servico1 {
proxy_pass http://localhost:8001/;
}
location /servico2 {
proxy_pass http://localhost:8002/;
}
Dessa forma, todas as requisições podem ser feitas para o mesmo servidor, facilitando a vida do cliente, dentre outras vantagens.
Um servidor web que faz o redirecionamento para outros múltiplos servidores baseado na URL recebida. Dessa forma, todas as requisições podem ser feitas para o mesmo servidor, facilitando a vida do cliente, dentre outras vantagens.
A partir do API Gateway é decidido onde essa requisição será direcionada.
- Problema: Clientes acessando livremente os serviços geram caos
- Gateway fornece um proxy, uma fachada, para as necessidades reais
- Desvantagem: Esse portão de entrada pode se tornar um pouco central de falha
O comportamento do Gateway
- Simplesmente autorizar e redirecionar os requests
- Uso de Decorator para adicionar informações necessárias aos requests
- Limitar o acesso ou conteúdo trafegado
- Impedir que determinadas URLs sejam acessadas por completo
Agora que conhecemos o conceito de um API Gateway, temos uma base melhor para esse artigo fenomenal do próprio Nginx: https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-1/
[NGINX] Load Balancing (Balanceamento de carga)
É um balanceador de carga (como um switch para serviços).
mv Dev/nginx/servico2/servico.html Dev/nginx/servico2/index.html
Podemos configura-lo de forma muito simples, com o upstream
:
upstream servicos {
server localhost:8001;
server localhost:8002;
}
server {
listen 8003;
server_name localhost;
location / {
proxy_pass http://servicos;
}
}
Na prática, através do nome definido em upstream, podemos acessar algum dos servidores deste grupo dependendo de algumas regras definidas.
Configuração de logs
access_log
é qualquer log de acesso, já o error_log
é um log somente de erros. Vamos ver um exemplo no (nginx.conf):
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
logs_format main '$remote_addr - $remote_user [$time_local] "$request"'
'$status $body_bytes_sent "$http_referer"
'"$http_user_agent" "$http_x_fowarded_for"';
#access_log logs/access.log main;
}
Logo:
mkdir Dev/nginx/logs
nginx -t
nginx -s reload
tail -f Dev/nginx/logs/servico1
tail -f Dev/nginx/logs/servico2
Formato de logs (nginx.conf)
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
logs_format main 'Remote Addr: $remote_addr - Time: [$time_local] "$request"'
'Status: $status, Referer: "$http_referer"
#access_log logs/access.log main;
}
sendfile on;
Formato de logs (microservicos.conf)
server {
listen 8001;
server_name localhost;
access_log /Users/isaac/Dev/nginx/logs/servico1.log main;
location / {
root /Users/isaac/Dev/nginx/logs/servico1.log;
index index.html;
}
}
server {
listen 8002;
server_name localhost;
access_log /Users/isaac/Dev/nginx/logs/servico2.log main;
location / {
root /Users/isaac/Dev/nginx/logs/servico2.log;
index index.html;
}
}
Adicionando informações
Dentro do load-balancer.conf
:
upstream servicos {
server localhost:8001;
server localhost:8002;
}
server {
listen 8003;
server_name localhost;
location / {
proxy_pass http://servicos;
proxy_set_header X-Real-IP $remote_addr;
}
}
No nginx.conf
:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
logs_format main 'Remote Addr: $http_x_real_ip, Time: [$time_local] "$request"'
'Status: $status, Referer: "$http_referer"
#access_log logs/access.log main;
}
sendfile on;
Definindo pesos (load-balancer.conf)
Se não distriuimos corretamente o peso para cada um servidor, deixaremos um desses servidores sobrecarregados.
upstream servicos {
server localhost:8001 weight=2;
server localhost:8002;
}
server {
listen 8003;
server_name localhost;
location / {
proxy_pass http://servicos;
proxy_set_header X-Real-IP $remote_addr;
}
}
Aplicamos o cenário de quando temos servidores com capacidades diferentes.
Servidores de backup
Vamos fazer o seguinte, vamos utilizar o servidor1 como o servidor principal e o servidor2 como o nosso servidor de backup.
upstream servicos {
server localhost:8001 fail_timeout=120s;
server localhost:8002 backup;
}
server {
listen 8003;
server_name localhost;
location / {
proxy_pass http://servicos;
proxy_set_header X-Real-IP $remote_addr;
}
}
Utilizaremos o servidor backup somente para casos extremos, ou seja, se o servidor principal falhar e estiver em instabilidade, o servidor backup o substitue.
[NGINX] Fast CGI
Lógicas do lado do servidor, e para isso foi criado o CGI. Com a mesma ideia do CGI, surgiu então o FastCGI cujo você não precisa criar outro processo por cada requisição, o que melhora muito na performance.
A principal diferença entre o CGI e o FastCGI está no processo, o FastCGI permanece vivo após o encerramento de uma requisição. Ao iniciar um processo FastCGO, ele ouvirá novas conexões e não morrerá mais, ou seja, o mesmo processo continua gerenciando os recursos da aplicação. Usando CGI, a cada requisição um processo é criado e depois morre.
Configurando o proxy
touch index.php
echo '<?php phpinfo();' > index.php
docker run --rm -it -p 9000:9000 -v $(pwd):/caminho/projeto php:fpm
Quero um serviço para rodar no localhost:8004
, vamos criar um novo documento (fpm.conf):
server {
listen 8004;
location / {
fastcgi_pass localhost:9000;
}
}
Parâmetros adicionais
Ainda dentro do (fpm.conf):
server {
listen 8004;
root /caminho/projeto;
location / {
include fastcgi.conf;
fastcgi_pass localhost:9000;
}
}
É um protocolo mais enxuto com fendas comprimidas e dessa forma a requisição do app não precisa receber todo o protocolo HTTP
Performance
Cache HTTP
O navegador (browser) pode ignorar os cabeçalhos de cache e não cachear o recurso. Tanto isso é verdade que temos a opção de desabilitar o cache do nosso navegador. Os cabeçalhos de cache instruem o navegador sobre o quanto tempo ele pode/deve manter o recurso em cache, mas cabe a ele aceitar essa instrução ou não. Todos os navegadores modernos tendem a seguir a instrução a menos que configuremos de forma diferente.
server {
listen 8005;
root /Users/isaac/Dev/performance;
index index.html;
location ~ \.jpg$ {
expires 30d;
add_header Cache-Control public;
}
}
Compreensão
Modo de compreensão instalado no próprio NGINX:
server {
listen 8005;
root /Users/isaac/Dev/performance;
index index.html
gzip on;
gzip_types image/jpg text/css;
location ~ \.jpg$ {
expires 30d;
add_header Cache-Control public;
}
}
Os tipos de recursos que são mais beneficiados pela compressão com gzip na web são os arquivos de texto (estáticos): html, css, js, svg, etc...
Os arquivos de texto podem ser facilmente comprimidos e são os que mais levam vantagem desta técnica. Arquivos binários ou arquivos de imagem, por exemplo, naturalmente já são comprimidos, por isso o efeito seria bem menor (ou inexistente).
Conexões
server {
listen 8005;
root /Users/isaac/Dev/performance;
index index.html;
gzip on;
gzip_types text/css;
add_header Keep-Alive "timeout=5, max=1000";
location ~ \.jpg$ {
expires 30d;
add_header Cache-Control public;
}
}
Cache
Caminho do Cache
É possível tranformar nosso servidor em um servidor cache, armazenando dados de resposta para não reprocessar determinadas requisições. Em um cenário em que URLs precisam de processamento e não mudam de usuário para usuário.
fastcgi_cache_path level=1.2 /tmp/cache keys_zone=fpm:10m;
proxy_cache_path /tmp/cache levels=1:2 keys_zone=proxy:1m;
server {
listen 8004;
root /caminho/projeto;
location / {
include fastcgi.conf;
fastcgi_pass localhost:9000;
}
}
Com uma simples página web por exemplo, nós não precisamos realizar todas as queries de cursos, formações, etc o tempo todo. Podemos executar uma vez só e armazenar o html montado em cache.
Usando o cache
fastcgi_cache_path /tmp/cache level=1:2 keys_zone=fpm:10m;
server {
listen 8004;
root /caminho/projeto;
location / {
fastcgi_pass localhost:9000;
include fastcgi.conf;
fastcgi_cache_key $request_method$request_uri;
fastcgi_cache fpm;
fastcgi_cache_valid 1m;
}
}
Verificando o status
Para podermos depurar quando necessário, se precisarmos saber se um cache está sendo encontrado ou não, ter um cabeçalho na resposta é uma forma bem fácil de obter essa informação.
fastcgi_cache_path /tmp/cache level=1:2 keys_zone=fpm:10m;
server {
listen 8004;
root /caminho/projeto;
location / {
fastcgi_pass localhost:9000;
include fastcgi.conf;
fastcgi_cache_key $request_method$request_uri;
fastcgi_cache fpm;
fastcgi_cache_valid 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
HTTPS
Um protocolo de Hipertexto seguro criptografado. Quanto utilizamos o HTTP os dados são transportados em texto puro para o servidor, visível para qualquer um. Nossos dados são enviados em um texto puro, ficando visível para qualquer um que consiga interceptar nossa conexão!
Gerando nosso próprio certificado
openssl req -x509 -nodes -days 30 -newkey rsa:2048 -keyout /tmp/localhost.key -out /tmp/localhost.crt
security add-certificate /tmp/localhost.crt
security add-trusted-cert /tmp/localhost.crt
Configurando o NGINX
server {
listen 443 ssl;
root /Users/isaac/Dev/performance;
index index.html
gzip on;
gzip_types text/css;
add_header Keep-Alive "timeout=5, max=1000";
ssl_certificate /tmp/localhost.crt;
ssl_certificate_key /tmp/localhost.key;
location ~ \.jpg$ {
expires 30d;
add_header Cache-Control public;
}
}
server {
listen 8005;
root /Users/isaac/Dev/performance;
index index.html;
gzip on;
gzip_types text/css;
add_header Keep-Alive "timeout=5, max=1000"
location ~ \.jpg$ {
expires 30d;
add_header Cache-Control public;
}
}
Posted on May 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.