DEV Community

Isaac Alves Pinheiro
Isaac Alves Pinheiro

Posted on

Nginx

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 
Enter fullscreen mode Exit fullscreen mode

Linux

sudo apt install nginx
Enter fullscreen mode Exit fullscreen mode

Para iniciar o nginx é simples, basta escrever o nome dele na linha de comando no terminal:

nginx
Enter fullscreen mode Exit fullscreen mode

Basta somente executa-lo e acessar o http://localhost:8080

welcome-screen-e1450116630667

[NGINX] Servidor HTTP

Exibe ajuda e lá podemos ver os caminhos do arquivo de configuração

nginx -h
Enter fullscreen mode Exit fullscreen mode

Arquivos de configuração do servidor (nginx.conf)

Essa é a área que configuramos o nosso servidor Nginx:

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/*;
}
Enter fullscreen mode Exit fullscreen mode

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' 
Enter fullscreen mode Exit fullscreen mode

Dessa forma, o arquivo de configuração fica assim:

NGINX

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/*;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Testar o arquivo de configuração

nginx -t 
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

[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;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode
php -S localhost:8000
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/;
  }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Logo:

mkdir Dev/nginx/logs
nginx -t
nginx -s reload
tail -f Dev/nginx/logs/servico1
tail -f Dev/nginx/logs/servico2
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Quero um serviço para rodar no localhost:8004, vamos criar um novo documento (fpm.conf):

server {
   listen 8004;

   location / {
       fastcgi_pass localhost:9000;
   }
}
Enter fullscreen mode Exit fullscreen mode

Parâmetros adicionais

Ainda dentro do (fpm.conf):

server {
    listen 8004;
    root /caminho/projeto;

    location / {
        include fastcgi.conf;
        fastcgi_pass localhost:9000;
    }
}
Enter fullscreen mode Exit fullscreen mode

É 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
security add-certificate /tmp/localhost.crt
security add-trusted-cert /tmp/localhost.crt
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)