Dia 13: Load Balancing — HAProxy, Nginx e LVS em Alta Disponibilidade

No Dia 13 do curso de redes, abordamos uma das peças fundamentais de qualquer infra-estrutura moderna: o balanceamento de carga. Quando um único servidor já não dá conta do tráfego, a resposta não é comprar uma máquina maior — é distribuir o trabalho entre várias. O HAProxy, o Nginx e o LVS são três ferramentas que fazem exactamente isso, cada uma com o seu nível de operação e caso de uso ideal.

Neste artigo:

1. Introdução ao Load Balancing

Load balancing é a técnica de distribuir tráfego de rede entre múltiplos servidores de destino, de forma transparente para o cliente. O cliente liga-se a um endereço — normalmente um IP virtual (VIP) — e o balanceador de carga decide qual servidor de backend vai processar o pedido.

Existem dois níveis principais de operação:

Nível Designação Funciona com Exemplos
L4 Transporte (TCP/UDP) Portas e IPs — não inspecciona conteúdo LVS, HAProxy em modo tcp
L7 Aplicação (HTTP/HTTPS) Headers, URLs, cookies, conteúdo HAProxy em modo http, Nginx

Um balanceador de carga de L4 é mais rápido e consome menos recursos — não desencapsula a carga útil. Um balanceador de carga de L7 é mais inteligente: pode encaminhar /api/* para um backend e /static/* para outro, com base no caminho do URL.

ℹ O balanceamento de carga distribui tráfego entre múltiplos servidores — aumenta disponibilidade e permite escalar horizontalmente sem indisponibilidade.

Os três actores principais que vamos cobrir são o HAProxy (dedicado, L4/L7), o Nginx (servidor web que também faz proxy inverso e balanceamento de carga L7) e o LVS (Linux Virtual Server — balanceamento de carga L4 no kernel). No Dia 11 cobrimos firewalls como pfSense e OPNsense; o balanceamento de carga é a peça complementar para escala.

2. HAProxy — L4 e L7

O HAProxy é o balanceador de carga código aberto mais usado em ambientes Linux. É dedicado — não serve conteúdo, apenas distribui ligações. Suporta os modos L4 (mode tcp) e L7 (mode http), com configuração em ficheiro único (/etc/haproxy/haproxy.cfg).

A estrutura do HAProxy divide-se em quatro secções: global (parâmetros do processo), defaults (valores herdados por todas as secções), frontend (ponto de entrada — onde os clientes se ligam) e backend (conjunto de servidores de destino).

Exemplo de configuração L7 com verificações de saúde e round-robin:

# HAProxy — frontend + backend
frontend web_front
    bind *:80
    default_backend web_servers
backend web_servers
    balance roundrobin
    option httpchk GET /health
    server web1 192.168.1.10:80 check
    server web2 192.168.1.11:80 check

Neste exemplo, o frontend escuta na porta 80 e envia todo o tráfego para o backend web_servers. O option httpchk faz um GET a /health em cada servidor; se um não responder, o HAProxy remove-o automaticamente da rotação. A directiva check em cada server activa a verificação de saúde para esse servidor.

Para operar em L4 (TCP puro, por exemplo para uma base de dados ou serviço não-HTTP), basta trocar mode http por mode tcp no frontend e no backend, removendo o option httpchk.

O HAProxy destaca-se pela sua página de estatísticas embutida. Basta adicionar um listen stats com stats enable e stats uri /haproxy?stats para ter um painel em tempo real com ligações activas, taxa de pedidos, erros e estado de cada servidor. Para alta disponibilidade do próprio balanceador de carga, o HAProxy é tipicamente combinado com Keepalived usando VRRP para comutação pós-falha entre dois nós HAProxy.

3. Nginx como Load Balancer

O Nginx é principalmente um servidor web, mas funciona excelentemente como proxy inverso e balanceador de carga L7. Ao contrário do HAProxy, o Nginx pode servir conteúdo estático directamente e fazer proxy de pedidos dinâmicos para os backends — tudo no mesmo processo.

A configuração de balanceamento de carga no Nginx gira em torno da directiva upstream, que define o conjunto de servidores de backend, e do proxy_pass dentro de um location, que encaminha o tráfego para esse upstream.

# Nginx — upstream
upstream backend {
    least_conn;
    server 192.168.1.10:80;
    server 192.168.1.11:80;
}
server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

Neste exemplo, least_conn define o algoritmo de distribuição (menor número de ligações activas). Sem qualquer directiva de algoritmo, o Nginx usa round-robin por omissão. O proxy_pass envia cada pedido para o upstream backend, que por sua vez escolhe o servidor segundo o algoritmo configurado.

O Nginx suporta verificações de saúde passivos por omissão — se um servidor devolver erros 5xx ou tempo limite, o Nginx marca-o temporariamente como indisponível e deixa de lhe enviar pedidos durante fail_timeout segundos. Para verificações de saúde activos (o Nginx faz sondas periódicas mesmo sem tráfego), é necessário o módulo nginx_upstream_check_module ou o Nginx Plus (versão comercial).

Para preservar sessões (persistência de sessão), o Nginx oferece ip_hash — o mesmo IP de cliente vai sempre para o mesmo servidor. Alternativamente, pode-se usar cookies com sticky cookie no Nginx Plus.

Característica HAProxy Nginx
Nível de operação L4 e L7 L7 (principalmente)
Serve conteúdo estático Não Sim
Health checks activos Nativos Requer módulo/Plus
Painel de estatísticas Embutido Requer módulo de terceiros
Foco principal Load balancing puro Servidor web + proxy

4. LVS — Linux Virtual Server

O LVS (Linux Virtual Server) é balanceamento de carga de L4 implementado directamente no kernel do Linux, através do módulo IPVS (IP Virtual Server). É mais rápido e tem menos sobrecarga do que o HAProxy ou o Nginx, porque opera ao nível do IP e não inspecciona o conteúdo dos pacotes — mas também é menos flexível.

O LVS suporta três modos de encaminhamento:

  • NAT (Network Address Translation): o LVS reescreve o IP de destino, o servidor responde ao LVS e este reescreve o IP de origem de volta para o cliente. O servidor não precisa de configurar nada de especial, mas o LVS é um gargalo porque todo o tráfego passa por ele em ambos os sentidos.
  • DR (Direct Routing): o LVS reencaminha o pacote ao servidor modificando o MAC frame, mas o servidor responde directamente ao cliente. O LVS só processa o tráfego de entrada — muito mais eficiente. O servidor precisa de configurar o VIP na interface de loopback.
  • TUN (Túnel IP): similar ao DR, mas o pacote é encapsulado num túnel IP. Permite que os servidores estejam em redes diferentes — útil em centros de dados distribuídos.

A ferramenta de linha de comando para gerir o LVS é o ipvsadm. O exemplo seguinte cria um serviço virtual na porta 80 com algoritmo round-robin e adiciona dois servidores reais em modo DR (Direct Routing, flag -g):

# LVS com ipvsadm
ipvsadm -A -t 192.168.1.100:80 -s rr
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.10:80 -g
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.11:80 -g
ipvsadm -L -n

As flags -A adiciona um serviço virtual, -a adiciona um servidor real, -t indica TCP (UDP seria -u), -s rr define o algoritmo round-robin e -g selecciona o modo gateway (DR). O -L -n lista a tabela IPVS actual com resolução numérica.

O LVS é frequentemente combinado com Keepalived — que gere o comutação pós-falha entre nós LVS usando VRRP e também faz verificações de saúde dos servidores reais, adicionando ou removendo-os da tabela IPVS conforme a sua disponibilidade. É a base do modelo de alta disponibilidade usado em muitos ISPs e centros de dados.

5. Algoritmos de Distribuição

O algoritmo de distribuição determina como o balanceador de carga escolhe o servidor de destino para cada ligação ou pedido. A escolha do algoritmo correcto tem impacto directo no desempenho e na equidade da distribuição.

Algoritmo Como funciona Quando usar
Round-robin Distribui pedidos em sequência circular: 1, 2, 3, 1, 2, 3… Servidores com capacidade igual
Least-conn Escolhe o servidor com menos ligações activas Servidores com capacidade diferente ou pedidos de duração variável
Source hash Hash do IP de origem — mesmo cliente vai sempre ao mesmo servidor Sessões que precisam de persistência (sem cookies)

No HAProxy, o algoritmo define-se com balance roundrobin, balance leastconn ou balance source. No Nginx, usa-se least_conn, ip_hash ou nada (round-robin por omissão). No LVS, as opções são -s rr (round-robin), -s lc (least-connection) e -s sh (source hashing).

Algoritmos avançados incluem weighted round-robin (atribui pesos a servidores — servidores mais potentes recebem mais pedidos), uri hash (HAProxy balance uri — mesmo URL vai ao mesmo servidor, útil para caches) e random (distribuição aleatória, útil em ambientes onde a sobrecarga de controlo de estado é indesejável).

6. Health Checks e Session Persistence

Health checks são verificações periódicas que o balanceador de carga faz a cada servidor de backend para determinar se está capaz de receber tráfego. Sem verificações de saúde, o balanceador de carga não tem forma de saber se um servidor caiu — continua a enviar-lhe pedidos, que falham.

⚠ Sem verificações de saúde, o balanceador de carga continua a enviar tráfego para servidores empanados — sempre configurar verificações de saúde activas.

Existem dois tipos de verificações de saúde:

  • Activos: o balanceador de carga faz sondagens periódicas independentes (ex: GET a /health de 5 em 5 segundos). No HAProxy: option httpchk GET /health. No LVS com Keepalived: configurar TCP_CHECK ou HTTP_GET no ficheiro keepalived.conf.
  • Passivos: o balanceador de carga observa as respostas aos pedidos reais. Se um servidor devolver 5xx ou tempo limite, é marcado temporariamente indisponível. O Nginx faz isto por omissão com max_fails e fail_timeout.

Session persistence (ou sessões fixas) garante que um cliente é sempre encaminhado para o mesmo servidor. É necessária quando a aplicação guarda estado localmente (cesto de compras, sessão em ficheiro) e não pode funcionar se o pedido for para um servidor diferente.

As três abordagens principais são:

  • IP hash: o balanceador de carga calcula um hash do IP de origem do cliente. Simples mas problemático — todos os clientes atrás do mesmo NAT (uma empresa inteira) vão para o mesmo servidor, causando desequilíbrio.
  • Cookie sticky: o balanceador de carga injecta um cookie no primeiro pedido (ex: SERVERID=web1) e usa o cookie nos pedidos seguintes para encaminhar para o mesmo servidor. No HAProxy: cookie SERVERID insert indirect nocache com cookie web1 em cada servidor.
  • URL parameter: usa um parâmetro no URL para identificar o servidor. Menos comum, mas útil quando cookies não são viáveis.

A melhor prática é evitar persistência de sessão sempre que possível — tornar a aplicação sem estado e guardar o estado numa base de dados partilhada ou cache (ex: Redis). Isto permite que qualquer servidor atenda qualquer cliente, maximizando a flexibilidade do balanceador de carga.

7. Erros Comuns e Lista de Verificação

A configuração de balanceamento de carga tem armadilhas frequentes. Abaixo estão os erros mais comuns e uma lista de verificação para os evitar.

Erro Causa Solução
Tráfego enviado para servidor caído Health checks não configurados Configurar option httpchk no HAProxy ou TCP_CHECK no Keepalived
Desequilíbrio de carga Round-robin com servidores de capacidade diferente Usar leastconn ou weighted round-robin
Sessões perdidas Sem persistência de sessão em aplicação com estado Configurar cookie sticky ou tornar a aplicação sem estado
Load balancer é ponto único de falha Apenas um nó balanceador de carga Dois nós em activo-espera com Keepalived/VRRP
Cliente atrás de NAT sobrecarrega um servidor ip_hash com muitos clientes num só IP Usar cookie sticky em vez de IP hash
Lento em modo NAT LVS em modo NAT — todo o tráfego passa pelo director Mudar para modo DR (Direct Routing)

Lista de verificação para produção:

  • ✓ Health checks activos configurados em todos os backends
  • ✓ Página de /health ou equivalente implementada na aplicação
  • ✓ Tempo limite de verificação de saúde adequado (não demasiado curto — falsos negativos; nem demasiado longo — detecção lenta)
  • ✓ Algoritmo de distribuição adequado ao padrão de tráfego
  • ✓ Session persistence configurada se a aplicação for stateful
  • ✓ Dois nós de balanceador de carga em activo-espera com comutação pós-falha automática (Keepalived/VRRP)
  • ✓ Painel de estatísticas acessível e monitorizada
  • ✓ Registos centralizados do balanceador de carga (syslog ou journald)
  • ✓ Teste de comutação pós-falha: desligar um backend e verificar que o tráfego é redistribuído sem erros visíveis
  • ✓ Teste de recuperação: voltar a ligar o backend e verificar que reentra na rotação

No próximo dia continuamos a explorar redes de alta disponibilidade. Para revisar conceitos de firewalls e segurança de perímetro, consulte o Dia 11 — Firewalls: pfSense e OPNsense, e para um exemplo prático de HAProxy com Keepalived, veja o artigo sobre HAProxy + Keepalived para Alta Disponibilidade.