01 - L4 (Transport Layer) vs L7 (Application Layer)
video: https://youtu.be/... no frontmatter da nota.L4 (Transport Layer) vs L7 (Application Layer)
Balanceadores de carga distribuem requisições entre múltiplos destinos para escalar horizontalmente e sobreviver a falhas, e a divisão mais fundamental dessa tecnologia é a camada em que o balanceador enxerga o tráfego. Um balanceador de camada 4 opera sobre TCP e UDP, tomando decisões de roteamento com base em IP de origem, IP de destino, porta e, em alguns casos, protocolo transport; ele nunca interpreta a aplicação. Um balanceador de camada 7 termina a conexão de aplicação, lê HTTP completo, headers, path, cookies e corpo, e decide o destino a partir de regras semânticas. A escolha entre os dois não é hierárquica, é um trade-off: L4 compra throughput brutal e transparência com inteligência mínima, L7 compra roteamento fino e recursos de aplicação pagando custo de processamento por requisição. Em arquiteturas de produção reais, os dois coexistem, e saber em que ponto da cadeia cada um atua é o que permite desenhar camadas de tráfego coerentes.
Conceito e o modelo de atuação
O balanceador L4 se comporta como um comutador de conexões: recebe o segmento TCP ou o datagrama UDP e encaminha para um destino escolhido, sem abrir a conversa. Existem duas técnicas subjacentes, e a diferença entre elas importa em operação.
- NAT (DNAT): o balanceador reescreve o IP de destino do pacote e o pacote volta por ele no caminho de retorno; é transparente para o cliente e funciona com qualquer protocolo TCP/UDP.
- DSR (Direct Server Return): só o fluxo de entrada passa pelo balanceador, e o servidor responde diretamente ao cliente com o IP virtual próprio do balanceador; elimina o gargalo do retorno, mas limita recursos que exigem visibilidade da conexão.
O balanceador L7 é um proxy explícito: o cliente abre a conexão TCP com o balanceador, e o balanceador abre outra conexão com o destino. Essa terminação dupla é o que lhe dá poder: para escolher o backend, ele precisa ter lido a requisição inteira, e lendo a requisição inteira pode modificá-la, filtrá-la, comprimí-la e observá-la.
A consequência estrutural é o fluxo de conexões: em L4, uma conexão cliente é uma conexão backend, e o mapeamento persiste até o fim; em L7, muitas conexões e requisições de clientes multiplexam sobre um pequeno pool de conexões para o backend, o que reduz uso de porta e de memória nos servidores de aplicação e permite keepalive eficiente.
O problema que cada um resolve
O L4 resolve escala bruta e protocolos que não são HTTP. Bancos de dados replicados, Redis, brokers MQTT, servidores de jogos sobre UDP e tráfego TCP proprietário precisam de distribuição sem que haja semântica de aplicação para interpretar. Nesses cenários, um L7 é inútil: não existe path para rotear. O L4 também resolve sobrecarga: decidir por IP e porta custa poucas operações por pacote, e appliances e serviços dedicados de L4 atingem milhões de conexões por segundo com latência mínima.
O L7 resolve direcionamento inteligente. Em uma arquitetura de microsserviços atrás de um único endpoint público, é o L7 que envia /api/pedidos para o serviço de pedidos, /api/relatorios para o serviço de leitura e /admin para o cluster interno, tudo na mesma porta 443. É também o L7 que aplica autenticação no edge, limita requisição por API key, injeta headers de rastreamento e faz canary roteando 2% das requisições com header específico para a versão nova.
O problema que ambos resolvem em conjunto é disponibilidade: health checks removem destinos doentes do pool, e a remoção automática é o que transforma redundância em tolerância a falhas real. Sem verificação de saúde, balanceamento é só distribuição; com ela, passa a ser recuperação.
Sticky sessions e afinidade
A afinidade de sessão existe porque aplicações mantêm estado em memória, e é uma solução de mitigação, não de arquitetura.
- Por cookie aplicativo: a aplicação emite o cookie e o balanceador o lê para fixar o destino; funciona em L7, sobrevive a reinício do balanceador e é o padrão do cookie JSESSIONID em Java.
- Por cookie gerado pelo balanceador: o próprio load balancer emite um cookie de duração fixa que mapeia para o destino; o cliente não precisa saber nada e o backend não muda.
- Por IP de origem: o L4 garante que o mesmo IP sempre cai no mesmo destino; quebra atrás de NAT corporativo, onde milhares de usuários compartilham o mesmo IP, e gera distribuição desigual.
A crítica arquitetural é o alvo certo: afinidade é paliativo para estado local. O desenho saudável externaliza a sessão em Redis ou em store dedicado, torna cada instância stateless, e a afinidade deixa de ser requisito; aí o balanceador escolhe livremente o destino mais saudável e mais próximo da capacidade, e o canary e o auto scaling funcionam sem cordas. Afinidade também corrompe o balanceamento: um usuário de API pesada fixado em um nó gera hot spot que nenhum algoritmo corrige.
Tráfego de WebSocket e gRPC de streaming aprofunda o problema. Uma vez estabelecida, a conexão persiste por tempo indeterminado e todo o volume dela cai no destino sorteado no handshake; em round robin puro, poucas conexões longas concentram carga que deveria estar distribuída. A resposta operacional é reconnection no cliente com jitter, limites de vida de conexão no edge e migração do estado de sessão para camada externa, de modo que a queda de um nó seja transparente para o usuário.
A conscientização de zona completa o quadro: balanceadores modernos mantêm tráfego dentro da mesma zona de disponibilidade do cliente, reduzindo latência e custo de transferência entre zonas, e redistribuem automaticamente quando um destino de uma zona fica insalubre. Esse comportamento só funciona bem com destinos uniformes e sem afinidade, mais um argumento para o backend stateless.
TLS termination
A terminação de TLS é o ponto onde L4 e L7 divergem de forma prática. O balanceador L7 termina o handshake TLS nele mesmo, mantém a sessão criptografada com o cliente, descriptografa, decide e recriptografa ou não para o backend. Isso centraliza gestão de certificados, habilita renovação automática, permite inspeção de conteúdo para roteamento e desloca o custo criptográfico do servidor de aplicação para o edge, que é dimensionado para isso.
O L4 típico não enxerga dentro do TLS: pode fazer passthrough, entregando o fluxo criptografado ao destino, que precisa ter o certificado e a chave. Essa é a exigência de quem precisa preservar identidade e conformidade fim a fim, ou de quem quer que o servidor real veja o certificado do cliente para autenticação mútua. O passthrough é também o modo de maior throughput, porque ninguém descriptografa nada no caminho.
| Aspecto | L4 (TCP/UDP) | L7 (HTTP) |
|---|---|---|
| Camada de decisão | Transporte: IP, porta, protocolo | Aplicação: path, headers, cookies, corpo |
| Modelo de conexão | Passagem de fluxo ou NAT | Proxy com terminação dupla |
| Throughput | Altíssimo, decisão barata por pacote | Menor, processamento por requisição |
| Visibilidade de conteúdo | Nenhuma, não descriptografa | Total, lê e modifica requisição |
| TLS | Passthrough, certificado no destino | Terminação no balanceador, centralizada |
| Roteamento por conteúdo | Impossível | Path, host, método, header, query |
| Sticky sessions | Por IP de origem | Por cookie, mais robusto |
| Protocolos servidos | Qualquer TCP ou UDP | HTTP, HTTPS, HTTP/2, gRPC, WebSocket |
| Custo típico | Menor por unidade de tráfego | Maior, cobra por LCU ou regra |
| Caso ideal | Banco, UDP, tráfego de alto volume | APIs, microsserviços, roteamento fino |
Funcionamento interno e algoritmos
O coração do balanceador é a escolha do destino por conexão ou por requisição, e os algoritmos base se repetem nas duas camadas.
- Round robin: distribui ciclicamente de forma igual; simples e previsível, mas cego à carga real de cada destino.
- Least connections: envia ao destino com menos conexões ativas, corrigindo desequilíbrio quando o tempo de atendimento varia muito entre requisições.
- Weighted variants: atribui pesos por capacidade, permitindo misturar instâncias grandes e pequenas ou conduzir migração gradual de versão.
- Source IP hash: determinístico por origem, fornece afinidade sem estado no balanceador.
Em L7, o mesmo algoritmo opera por requisição, e não por conexão, o que permite que um único keepalive de navegador distribua entre vários pods. Em L4, a decisão acontece uma vez por conexão e vale enquanto ela durar; conexões longas criam desequilíbrio persistente que só least connections corrige parcialmente, porque a conexão estabelecida não migra.
Aplicação prática: a pilha combinada
A arquitetura comum de produção empilha as duas camadas de forma complementar, e o exemplo abaixo ilustra o desenho típico.
# Camada L7: Nginx como roteador de aplicação atrás de um NLB (L4)
# O NLB entrega o TCP bruto na porta 443 e o Nginx decide por path
upstream servico_pedidos {
least_conn; # menos conexões ativas: bom para latência variável
server pedidos-1:8080 max_fails=3 fail_timeout=15s;
server pedidos-2:8080 max_fails=3 fail_timeout=15s;
keepalive 64; # reutiliza conexões com o upstream
}
upstream servico_relatorios {
least_conn;
server relatorios-1:8080;
server relatorios-2:8080;
keepalive 32;
}
server {
listen 443 ssl;
server_name api.exemplo.com.br;
ssl_certificate /etc/nginx/certs/api.crt; # terminação TLS centralizada
ssl_certificate_key /etc/nginx/certs/api.key;
location /v1/pedidos/ {
proxy_pass http://servico_pedidos;
proxy_set_header X-Request-Id $request_id; # rastreio ponta a ponta
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /v1/relatorios/ {
proxy_pass http://servico_relatorios;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
O desenho explora o melhor dos dois mundos: o NLB da camada de rede absorve picos, ataques volumétricos e flutuação de porta com IP estático e throughput elástico, e o Nginx aplica semântica, TLS e roteamento. Em Kubernetes, o par é exatamente o mesmo conceito: Service do tipo LoadBalancer como L4 e Ingress como L7.
Health checks e drenagem de conexões
O health check da camada 4 testa a abertura de conexão TCP no endereço e porta do destino: se o handshake completa, o destino conta como saudável. É barato, rápido e independe de aplicação, mas tem falso positivo clássico: o processo escuta a porta com a fila de accept cheia e o check passa enquanto requisições reais estagnam. Em L4 puro, por não haver como perguntar à aplicação, a mitigação combina intervalo curto, limiar de falha agressivo e supervisão de processo no host.
O health check da camada 7 executa uma requisição HTTP real, em geral GET em /healthz, e pode exigir código 200, validação de corpo e presença de dependência crítica. Ele enxerga o que o usuário enxerga, e é o único check que valida o caminho completo do proxy ao backend. O desenho correto distingue liveness de readiness: o readiness do balanceador responde se o destino deve receber tráfego, enquanto a dependência opcional degradada não deve tirar o destino do pool, apenas disparar alarme.
A drenagem de conexões é o mecanismo que impede erro visível em operação de rotina. Ao retirar um destino do pool, durante deploy ou scale in, o balanceador para de enviar conexões novas e aguarda o término das conexões existentes dentro de um prazo de drain; em L7, o limite é por requisição e as conexões esvaziam rápido, enquanto em L4 conexões longas, como WebSocket e banco, podem segurar o drain por horas. Sem drenagem, cada deploy interrompe requisições em voo, e o sintoma típico é taxa de erro 502 de poucos por cento exatamente no fim da janela de publicação.
Critério de decisão
A decisão entre L4 e L7 se apoia em quatro perguntas, nesta ordem. Primeiro, o protocolo é HTTP ou algo sobre TCP/UDP genérico? Não sendo HTTP, a resposta é L4, sem discussão. Segundo, existe necessidade de rotear pelo conteúdo, como path, header, cookie ou API key? Existindo, é L7, porque a informação só está visível na camada de aplicação. Terceiro, o requisito de throughput e latência é extremo, na casa de milhões de conexões ou tráfego UDP volumétrico? Sendo, o L4 dedicado na frente reduz custo e risco. Quarto, há exigência de inspecionar ou preservar o TLS fim a fim? Passthrough pede L4, e inspeção pede L7.
O desenho resultante maduro é híbrido e em camadas: um L4 na borda, robusto e barato, protegendo e absorvendo a conexão, com um ou mais L7 atrás, aplicando semântica por domínio e serviço. A fronteira entre eles deve ser documentada em termos de responsabilidade sobre TLS, health check e timeout, pois duplicar essas responsabilidades entre camadas cria comportamento imprevisível em falha.
Erros comuns e boas práticas
- Usar L7 onde só existe TCP: aplicar balanceador de aplicação em banco ou MQTT não funciona; sem HTTP não há path para decidir, e a conexão falha.
- Depender de sticky session como projeto: afinidade mascara estado local e impede escalonamento real; externalize a sessão e use afinidade apenas como transição.
- Desligar keepalive entre proxy e backend: abrir conexão TCP por requisição consome porta e latência; configure pool de conexões no upstream.
- Ignorar conexões longas em L4: round robin com conexões de minutos gera hot spots; use least connections ou limite o tempo de vida da conexão.
- Terminar TLS sem recriptografar para dentro: tráfego interno em texto claro viola requisitos de conformidade; criptografe de novo ou use mTLS entre serviços.
- Health check por TCP aberto: porta aberta não significa aplicação saudável; valide endpoint real com resposta esperada na camada correspondente.
A boa prática final é decidir por requisito, e não por modismo: se o protocolo não é HTTP, a resposta é L4; se o roteamento precisa ler a requisição, é L7; quando o tráfego é misto, empilhe os dois com fronteiras claras. Documente qual camada é responsável por TLS, por afinidade e por health check, porque a sobreposição dessas responsabilidades entre camadas é a origem da maioria dos incidentes de balanceamento.