Origens: tudo começou como um truque de DNS
O primeiro balanceamento de carga não foi um equipamento. Foi o DNS round robin: publicar vários registros de endereço para um mesmo nome e deixar que os resolvers entregassem endereços diferentes. Não custava nada, e falhava de jeitos que ainda importam hoje: o tempo de vida do registro determina por quanto tempo um cliente continua usando um servidor morto, resolvers e aplicações mantêm o endereço em cache além desse prazo, e o DNS não sabe nada sobre o que está vivo ou morto atrás do nome.
Essa limitação definiu a categoria. Tudo o que veio depois foi uma tentativa de levar a decisão para mais perto do tráfego e de dar a ela informação sobre a saúde dos servidores.
Início dos anos 1990: proxies reversos e dispatchers. Software na frente de um conjunto de servidores, terminando as conexões e repassando-as adiante. Era flexível, mas lento no hardware da época.
Meados dos anos 1990: switching de camada 4. Hardware dedicado, reescrevendo endereços em wire speed, na velocidade máxima da interface. É aqui que o termo virtual server (servidor virtual) entra no vocabulário: um endereço na frente, um pool atrás e a tradução no meio. F5, Cisco LocalDirector, Alteon e Foundry definiram essa geração.
Do fim dos anos 1990 em diante: camada 7 e o . Quando o equipamento passou a terminar o TCP, ele pôde ler o HTTP; e, lendo o HTTP, pôde rotear por hostname e por path, inserir cookies, comprimir, fazer cache e fazer o offload do TLS. A categoria mudou de nome para application delivery controller, e o load balancer, sem alarde, virou o lugar onde mora boa parte da lógica da aplicação.
Anos 2010: software e nuvem. O HAProxy, o NGINX e, mais tarde, o Envoy trouxeram o data plane de volta para o software, rodando num hardware tão rápido que isso deixou de fazer diferença. Os provedores de nuvem transformaram o balanceamento num serviço gerenciado. Depois, o service mesh levou o proxy para o lado de cada workload, um sidecar por pod, e as mesmas quatro perguntas passaram a ser respondidas para cada serviço, e não para o ambiente inteiro.
A história é um pêndulo: primeiro, hardware para resolver um problema de desempenho do software; depois, software de novo, quando esse problema de desempenho deixou de existir. O que não mudou foi a decisão que precisa ser tomada.
O que acontece por baixo
Vale conhecer estas partes, porque são elas que explicam os modos de falha:
Camada 4 ou camada 7. Na camada 4, o equipamento reescreve endereços e portas e encaminha o tráfego; ele nunca vê a requisição. Na camada 7, ele termina a conexão do cliente, lê a requisição e abre uma conexão própria com o servidor. Essa diferença define todo o resto: roteamento por conteúdo, persistência por cookie e inspeção de TLS exigem terminar a conexão, e terminar a conexão faz do load balancer um participante da conversa, e não um simples repassador.
Source e o endereço do cliente. Quando o load balancer abre a conexão com o backend, o servidor vê o endereço do load balancer, a menos que alguma coisa leve junto o endereço original. Daí o X-Forwarded-For e o header Forwarded, padronizado na 7239. E daí também o incidente que vive se repetindo, em que toda linha de log e todo rate limit enxergam um único cliente, porque o header nunca foi configurado ou nunca foi tratado como confiável.
Direct server return. O servidor responde direto ao cliente e, na volta, não passa pelo load balancer. É excelente para tráfego assimétrico, como vídeo, e descarta qualquer recurso de camada 7, porque o equipamento nunca vê o caminho de volta.
Reúso de conexão e multiplexação. No e no , muitas requisições compartilham a mesma conexão, e isso quebra, sem ninguém perceber, o balanceamento por número de conexões: uma única conexão pode levar mil requisições, e aí o least connections (menos conexões) deixa de medir a carga. Os proxies modernos balanceiam por requisição, e não por conexão, e essa é uma mudança real de comportamento em relação à época da camada 4.
Terminação ou passthrough de TLS. Terminar o TLS dá visibilidade e offload e faz do load balancer o dono do certificado; o passthrough preserva a criptografia de ponta a ponta e deixa cego todo recurso de camada 7 pelo qual você pagou. A maioria dos ambientes acaba fazendo as duas coisas, em lugares diferentes, e a confusão sobre qual é qual é uma fonte comum de surpresas.
Quatro perguntas, da que mais dá problema à que menos dá
Os fabricantes disputam mercado nos algoritmos porque algoritmo é fácil de colocar numa tabela. Na operação, a ordem de importância é praticamente a inversa:
- Quais servidores são considerados vivos? O health check (a verificação de saúde)
- Este cliente precisa voltar ao mesmo servidor? A persistência
- O que acontece quando o conjunto muda? O draining (a drenagem das conexões) e o slow start
- Qual servidor vivo recebe esta requisição? O algoritmo
Se as três primeiras estiverem erradas, a quarta não salva você. Se estiverem certas, a diferença entre os algoritmos costuma ser desprezível.
Tudo depende do health check
O que o load balancer sabe sobre a realidade é o que o health check conta para ele. Todo o resto é conta feita em cima disso; por isso, um health check que testa a coisa errada produz tráfego bem distribuído, e com toda a convicção, para servidores quebrados.
Um check de conexão TCP prova que há um processo escutando. Não prova que a aplicação funciona, e um servidor que aceita conexões e devolve erro para tudo vai continuar no pool, tranquilamente.
Um check HTTP na raiz do site prova que o servidor web responde. Em geral, ele não passa pelo banco de dados, pelo cache nem por nada de que a aplicação realmente precisa, e a falha que importa fica invisível.
Um check profundo, que testa todas as dependências, é pior. Quando o banco de dados compartilhado fica lento, todos os servidores falham no check ao mesmo tempo, o pool esvazia e o load balancer derruba o serviço inteiro por causa de uma degradação que ele poderia ter atravessado. Essa queda é muito comum: o health check transformou uma falha parcial numa falha total.
O meio-termo que funciona é um endpoint feito só para isso, que verifica o que esta instância precisa para atender uma requisição, deixa de fora, de propósito, as dependências que todas as instâncias compartilham e é rápido o bastante para rodar com frequência. Depois, defina os limites com honestidade: quantas falhas seguidas antes de tirar o servidor, quantos sucessos antes de devolvê-lo e quanto tempo o check espera. São esses três números, e não o algoritmo, que decidem com que rapidez você tira um servidor doente do pool e quanto o pool fica oscilando (o flapping) numa instabilidade passageira.
Persistência é uma limitação, não um recurso
A persistência de sessão, que manda o cliente de volta ao servidor que guarda o estado dele, existe porque as aplicações guardam estado em memória. Vale chamar a coisa pelo nome: é a admissão de que a aplicação não é stateless, e todo método de persistência é um contorno que tem um custo.
A persistência por endereço de origem quebra numa época de endereços compartilhados: milhares de assinantes atrás de um mesmo NAT da operadora caem todos no mesmo servidor, o que cria ao mesmo tempo um hotspot e uma distribuição injusta. A persistência por cookie funciona bem no HTTP, mas exige terminar a conexão para inserir ou ler o cookie. A persistência por sessão TLS fica presa a uma sessão que pode ser retomada ou trocada.
A consequência mais importante para a operação é que persistência e capacidade brigam entre si. Um pool perfeitamente balanceado, com persistência, não está balanceado: o balanceamento vale só para os clientes novos, e os que já existem ficam onde estão. Se você acrescentar servidores durante um pico de tráfego, os novos vão ficar quase ociosos, porque a carga que você queria mover está presa nos outros.
A correção de verdade é de arquitetura: levar o estado para um repositório compartilhado, para que qualquer servidor possa responder. É essa mudança que faz a escalabilidade funcionar. A persistência é o que se usa até lá.
As quedas se escondem na hora de mudar o pool
Tirar um servidor deveria significar: parar de mandar conexões novas, deixar as existentes terminarem e só então removê-lo. Um draining curto demais corta requisições em andamento; um draining que nunca termina trava a manutenção. Os dois são erros de configuração que a equipe descobre no meio de um deploy.
Acrescentar um servidor é o caso que todo mundo subestima. Uma instância que acabou de subir tem o cache frio, o pool de conexões vazio e um runtime que ainda não esquentou. Mande para ela a parte inteira do tráfego logo de cara e ela vai ficar lenta, falhar no próprio health check, ser removida, voltar ao pool e ficar nesse vaivém. O slow start, que aumenta aos poucos a parte do tráfego de um membro novo, existe exatamente para isso, e fica desligado muito mais vezes do que deveria.
A falha que junta tudo isso: um pool sobrecarregado tira um servidor que está sofrendo, a carga dele vai para os que sobraram, eles ficam sobrecarregados e o pool se esvazia, um membro de cada vez. Todo plano de capacidade que conta com N servidores deveria ser testado com N-1, porque é nesse estado que você vai estar na prática.
Os algoritmos, em resumo, e o que cada um pressupõe
- O round robin pressupõe que todas as requisições custam o mesmo e que os servidores são idênticos. As duas premissas costumam ser falsas, e ele continua em uso porque é previsível.
- O least connections pressupõe que uma conexão aberta é um bom indicador de carga. É bom para conexões longas e enganoso quando a conexão custa pouco e o trabalho de verdade está em outro lugar.
- O least response time (menor tempo de resposta) mede o que realmente interessa, mas premia o servidor que é rápido porque está falhando rápido. Use junto com um health check que perceba os erros.
- Qualquer algoritmo ponderado (weighted) é o jeito honesto de lidar com servidores de capacidades diferentes e também o jeito de deslocar tráfego durante uma migração.
- O hashing sobre uma chave manda o mesmo cliente, ou a mesma chave, sempre para o mesmo servidor, sem guardar estado. É a mesma ideia que o hashing consistente aplica na escala de uma rede de distribuição de conteúdo, e vale a pena quando a sua necessidade de persistência é determinística, e não baseada em estado guardado.
Escolha pelo perfil real do seu tráfego: quanto tempo duram as conexões, quanto varia o custo das requisições e se os servidores são idênticos. Se você não consegue descrever essas três coisas, a escolha do algoritmo é chute com nome bonito.
Arquiteturas de implantação
Two-arm (roteado). Os clientes ficam de um lado, os servidores do outro e o load balancer no meio. É uma arquitetura limpa, e a rota padrão dos servidores precisa apontar para ele.
One-arm. O load balancer fica ao lado dos servidores, na mesma sub-rede, e usa source NAT para que as respostas voltem por ele. É simples de encaixar numa rede que já existe, mas esconde o endereço do cliente, a menos que um header o carregue.
Direct server return. Assimétrico por natureza, como vimos acima.
Global (vários sites). O direcionamento por DNS ou o anycast decide qual site recebe o tráfego. Cada um traz os seus limites: o direcionamento por DNS esbarra no tempo de vida do registro e nos resolvers que o ignoram, e o anycast leva a decisão para o roteamento, onde você tem menos controle sobre o momento em que o tráfego muda de lugar.
Sidecar e service mesh. Um proxy para cada workload. Políticas e telemetria ficam uniformes entre os serviços, e o custo operacional é que agora você mantém uma frota de proxies do tamanho da sua frota de workloads.
Os fabricantes, por categoria
As categorias importam mais que os nomes, porque cada uma traz um viés que vem da própria estrutura:
- Fabricantes dedicados de ADC: F5, Citrix, A10, Radware. Recursos profundos de camada 7, scripting e inspeção; você está comprando um motor de políticas completo e assumindo junto o modelo de operação dele.
- Plataformas de firewall e de rede: Fortinet, Palo Alto e outros oferecem balanceamento ao lado da inspeção. A consolidação é conveniente, mas os recursos costumam ser mais rasos que os dos especialistas.
- Software : HAProxy, NGINX, Envoy, Traefik. São os data planes que a maior parte da indústria de fato usa, inclusive dentro de produtos comerciais; o Envoy, em especial, virou a base dos service meshes.
- Serviços gerenciados de nuvem: os load balancers dos provedores se dividem, grosso modo, em versões de rede, de camada 4, e de aplicação, de camada 7. São os mais baratos de adotar e os menos portáveis, e o comportamento do health check e do draining muda o bastante de um provedor para outro para fazer diferença numa migração.
- Service mesh: Istio, Linkerd e similares. Políticas por serviço, TLS mútuo e telemetria detalhada, em troca de um control plane novo e nada pequeno.
Na hora da compra, a pergunta honesta não é qual é o mais rápido. É com quais modos de falha você está disposto a conviver na operação, já que o load balancer está no caminho de tudo e, quando ele cai, a queda é total.
O lugar do load balancer no domínio de falha
Por construção, ele está no caminho de tudo. Isso faz dele o lugar onde a resiliência rende mais e o componente individual de maior impacto que você opera:
- Na frente dos servidores, ele esconde as falhas individuais, e esconde de você também; é por isso que as métricas de cada membro do pool importam mais que o total.
- Quando termina o TLS, ele vira o ponto por onde passam todos os certificados, com as preocupações de emissão e renovação descritas no artigo sobre certificados.
- Em par, o failover do próprio load balancer é a parte que ninguém testa até o dia em que ela faz falta.
- Na escala global, a distribuição por DNS ou por anycast leva a decisão para antes do load balancer e herda o cache do DNS: o tráfego continua chegando a algum lugar enquanto durar o tempo de vida do registro, não importa o que você mude.
O hábito mais útil de todos: acompanhe as métricas de cada membro, não a média do pool. A média tende a parecer saudável justamente enquanto um membro, em silêncio, devolve erros tão rápido que atrai mais tráfego que os servidores que estão trabalhando de verdade.
Esta série, em ordem
Este artigo abre a série Balanceamento de carga e persistência. As quatro perguntas dele (quem está vivo, quem recebe esta requisição, se este cliente precisa voltar ao mesmo lugar, o que acontece quando algo falha) organizam as partes: primeiro, o virtual server e o proxy TCP, que fazem o trabalho; depois, a persistência: como escolher o método, como ela quebra atrás de NAT, como combiná-la com um fallback e como mantê-la num failover; em seguida, a mesma decisão tomada em escala global, em dois níveis; e, por fim, como a indústria foi construída apesar de uma previsão de dez clientes.
- Como funciona um virtual server do BIG-IP. A porta de entrada de um BIG-IP: o listener, a pilha de profiles, a persistência, o membro do pool e o source NAT.
- O proxy TCP: o que um middlebox de camada 4 enxerga e o que não enxerga. Dois fluxos TCP independentes, emendados na camada 4: o que um proxy reescreve, o que ele mantém em pool e o que ele não consegue ver.
- Como escolher um método de persistência (e seus modos de falha). Afinidade por endereço de origem ou cookie, e o jeito, bem conhecido, como cada um falha.
- Persistência por endereço de origem e o problema do mega-proxy. Como prender um cliente pelo IP dele, e como NATs grandes fazem muitos clientes parecerem um só.
- Fallback de persistência e as configurações match-across. Um método principal e um de fallback, e as três configurações match-across que decidem até onde um registro é compartilhado.
- Espelhamento de persistência em um par HA. Por que um standby que assume as conexões ainda esquece qual cliente estava em qual membro, a menos que esses mapeamentos tenham sido espelhados.
- GSLB em dois níveis: o wide IP escolhe o pool, e o pool escolhe o servidor. O global server load balancing (o balanceamento global de servidores) como duas decisões: o wide IP escolhe um pool, e depois o pool escolhe um servidor.
- Load balancers e ADCs: construídos apesar da estimativa de mercado. Dez clientes no mundo inteiro, estimou um webmaster da Netscape em 1995; a indústria foi construída mesmo assim.