O que é balanceamento no Nginx (e o que ele não é)
Balanceamento de carga é a distribuição de requisições entre múltiplas instâncias de um mesmo serviço (backends), com o objetivo de melhorar capacidade e reduzir o impacto de picos. No Nginx, isso é feito principalmente com o bloco upstream, que define um “pool” de servidores, e um proxy_pass apontando para esse pool.
É importante separar dois conceitos:
- Balanceamento: distribuir tráfego entre instâncias saudáveis para aumentar throughput e reduzir latência média.
- Alta disponibilidade (HA): garantir que o ponto de entrada continue acessível mesmo se um componente cair. Ter dois backends atrás de um único Nginx melhora a resiliência do serviço, mas não elimina o Nginx como ponto único de falha. HA normalmente envolve redundância do próprio Nginx (ou de um load balancer externo), IP flutuante, health checks ativos, etc.
Bloco upstream e round-robin (padrão)
O algoritmo padrão do Nginx para upstream é round-robin: a cada nova requisição, ele alterna o backend escolhido, distribuindo de forma relativamente uniforme (considerando conexões e disponibilidade).
Exemplo mínimo de upstream
upstream app_pool { server 127.0.0.1:3001; server 127.0.0.1:3002;}Quando você usa proxy_pass http://app_pool;, o Nginx passa a encaminhar requisições alternando entre :3001 e :3002.
Pesos (weight): mais tráfego para quem aguenta mais
Você pode ajustar a proporção de tráfego com weight. Um servidor com peso 2 tende a receber aproximadamente o dobro de requisições de um servidor com peso 1.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
upstream app_pool { server 127.0.0.1:3001 weight=2; server 127.0.0.1:3002 weight=1;}Isso é útil quando uma instância tem mais CPU/memória, ou quando você está fazendo uma migração gradual (canary simples) sem ferramentas externas.
Falhas e “quarentena” do upstream: max_fails e fail_timeout
Em configuração básica, o Nginx não faz health check ativo (ele não fica “pingando” o backend). Em vez disso, ele aprende por falhas durante o tráfego real. Duas diretivas comuns:
max_fails: número de falhas que, ao ocorrerem dentro de uma janela, fazem o Nginx considerar o servidor temporariamente indisponível.fail_timeout: janela de tempo para contar falhas e também o período típico em que o servidor fica “marcado como ruim” antes de ser tentado novamente.
Exemplo:
upstream app_pool { server 127.0.0.1:3001 max_fails=2 fail_timeout=10s; server 127.0.0.1:3002 max_fails=2 fail_timeout=10s;}Interpretação prática: se um backend falhar 2 vezes em até 10 segundos, ele tende a ser evitado por cerca de 10 segundos, e depois volta a ser testado conforme novas requisições chegarem.
O que conta como “falha”?
Em geral, falhas de conexão com o upstream (connection refused, timeout, reset) contam. Já códigos HTTP 500/502/503 podem ou não influenciar dependendo de como você configura proxy_next_upstream (mais abaixo). Sem ajustes, o Nginx pode retornar o erro ao cliente sem tentar outro upstream em algumas situações.
Limitações de checagens na configuração básica
Sem módulos/complementos de health check ativo, o Nginx “descobre” problemas apenas quando há tráfego. Isso traz limitações:
- Se o tráfego for baixo, um backend ruim pode demorar a ser detectado.
- Um backend pode “voltar” e ainda assim falhar intermitentemente; o Nginx vai alternar tentativas conforme o tráfego.
- Você não tem, nativamente, uma URL de health check ativa por backend (ex.:
/health) sendo consultada periodicamente pelo Nginx na configuração básica.
Mesmo assim, max_fails e fail_timeout já ajudam a reduzir o impacto de quedas óbvias (porta fechada, processo morto, etc.).
Laboratório: duas instâncias e um Nginx distribuindo tráfego
Objetivo do laboratório: subir duas instâncias simples em portas diferentes, colocar o Nginx na frente com upstream e confirmar a distribuição e o comportamento quando uma instância falha.
1) Suba duas instâncias de aplicação (exemplo com Python)
Em dois terminais diferentes, rode servidores HTTP simples servindo uma resposta diferente. Uma forma prática é criar duas pastas com um index.html distinto.
No Terminal A:
mkdir -p /tmp/app1 && echo 'APP1' > /tmp/app1/index.htmlcd /tmp/app1python3 -m http.server 3001No Terminal B:
mkdir -p /tmp/app2 && echo 'APP2' > /tmp/app2/index.htmlcd /tmp/app2python3 -m http.server 3002Teste direto (sem Nginx) para confirmar:
curl -s http://127.0.0.1:3001/; echocurl -s http://127.0.0.1:3002/; echo2) Configure o upstream e o server que faz proxy para o pool
Crie um arquivo de configuração do site (ajuste o caminho conforme sua organização). O foco aqui é o bloco upstream e o uso de logs para observabilidade.
upstream app_pool { server 127.0.0.1:3001 weight=1 max_fails=2 fail_timeout=10s; server 127.0.0.1:3002 weight=1 max_fails=2 fail_timeout=10s;}log_format upstream_debug '$remote_addr - $time_local ' '"$request" $status ' 'upstream=$upstream_addr ' 'upstream_status=$upstream_status ' 'rt=$request_time urt=$upstream_response_time';server { listen 8080; access_log /var/log/nginx/upstream_access.log upstream_debug; error_log /var/log/nginx/upstream_error.log warn; location / { proxy_pass http://app_pool; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; proxy_next_upstream_tries 2; }}Pontos importantes:
log_format upstream_debuginclui$upstream_addre$upstream_status, essenciais para enxergar para qual backend cada requisição foi enviada.proxy_next_upstream ...permite que, em certos erros, o Nginx tente outro backend em vez de falhar imediatamente para o cliente.proxy_next_upstream_tries 2limita quantas tentativas totais serão feitas (útil para evitar “loop” de tentativas em cascata).
3) Recarregue o Nginx e gere tráfego
Recarregue a configuração (use o comando adequado ao seu ambiente). Em seguida, faça várias requisições:
for i in $(seq 1 10); do curl -s http://127.0.0.1:8080/; echo;doneVocê deve ver alternância entre APP1 e APP2 (round-robin).
4) Confirme a distribuição via logs (observabilidade)
Agora observe o log de acesso customizado:
tail -f /var/log/nginx/upstream_access.logProcure por linhas como:
"GET / HTTP/1.1" 200 upstream=127.0.0.1:3001 upstream_status=200 ..."GET / HTTP/1.1" 200 upstream=127.0.0.1:3002 upstream_status=200 ...Isso confirma, de forma objetiva, qual upstream atendeu cada requisição.
Simulando falha de um upstream e observando o comportamento
1) Derrube uma instância
Pare o servidor do Terminal B (porta 3002) com Ctrl+C.
2) Gere tráfego novamente
for i in $(seq 1 10); do curl -i http://127.0.0.1:8080/ | head -n 1;doneCom proxy_next_upstream habilitado, a tendência é que o Nginx tente o backend que falhou e, ao detectar erro de conexão/timeout, faça failover para o outro (até o limite de tentativas). Você deve ver respostas 200 continuarem vindo, desde que pelo menos um upstream esteja saudável.
3) Verifique nos logs o failover e a marcação de falha
No upstream_access.log, observe upstream=... e upstream_status=.... Em falhas, você pode ver algo como:
upstream=127.0.0.1:3002 upstream_status=502, 200Isso indica que a primeira tentativa foi no :3002 e falhou (502 gerado pelo proxy), e a segunda tentativa foi no :3001 e teve sucesso (200). O formato pode variar conforme o tipo de falha e o que foi registrado.
No upstream_error.log, você pode ver mensagens do tipo connect() failed (111: Connection refused) while connecting to upstream, úteis para confirmar a causa.
Ajustes úteis para testes: weight e comportamento em degradação
Testando weight na prática
Altere o upstream para dar mais tráfego ao APP1:
upstream app_pool { server 127.0.0.1:3001 weight=3 max_fails=2 fail_timeout=10s; server 127.0.0.1:3002 weight=1 max_fails=2 fail_timeout=10s;}Recarregue o Nginx, suba novamente o APP2 e repita o loop de curl. No log, conte ocorrências de upstream=127.0.0.1:3001 versus :3002; a proporção tende a se aproximar de 3:1.
Entendendo o “retorno” de um upstream após falha
Depois que um upstream é marcado como indisponível por fail_timeout, ele volta a ser elegível e será testado novamente conforme novas requisições chegarem. Se ele ainda estiver fora, novas falhas vão mantê-lo sendo evitado. Esse ciclo é um motivo para você observar logs durante incidentes: o comportamento é guiado por falhas reais, não por checagens proativas.
Checklist do laboratório (o que você deve conseguir observar)
- Round-robin alternando respostas APP1/APP2 quando ambos estão no ar.
$upstream_addrno log confirmando para qual backend cada requisição foi.- Ao derrubar um backend, o Nginx registrando erros no
error_loge mantendo respostas 200 via o backend saudável (quandoproxy_next_upstreampermite). - Após alguns erros, o backend com falha sendo evitado temporariamente (efeito de
max_fails/fail_timeout), reduzindo tentativas repetidas nele.