Ir para o conteúdo

Blogue ·

Como um agente escolhe o servidor de fronteira

DNS com vários registos A, fronteiras de camada 4 e PROXY protocol v2, com as medições: o que faz um cliente com vários endereços e porque voltar é difícil.

Por Provibr

Para quem corre um agente no hardware dos seus clientes, o endereço que ele marca vive em dois sítios incómodos: no DNS público e no ficheiro de credenciais que entregou no momento da inscrição. Ambos são difíceis de mudar depois, e um deles diz à internet inteira onde está o seu plano de controlo.

Pusemos uma camada de servidores de fronteira à frente do nosso. Eis o que isso exigiu, incluindo as partes que só apareceram quando agentes reais passaram por lá.

O problema é o endereço, não o tráfego

O nome do gateway resolvia para a origem. Todos os agentes inscritos o conheciam, e também qualquer pessoa capaz de escrever dig. Uma inundação volumétrica apontada a esse endereço derruba o plano de controlo para toda a gente, e não há nada de inteligente a fazer depois do facto.

O objetivo era estreito, e enunciá-lo mantém o desenho honesto: o endereço da origem desaparece do caminho que os agentes usam, para que uma inundação atinja uma máquina que se substitui em dez minutos. Explicitamente não era objetivo que as fronteiras protegessem o conteúdo. O TLS mútuo já faz isso de ponta a ponta, e uma fronteira capaz de inspecionar o tráfego seria um retrocesso.

Camada quatro, porque terminar o TLS quebraria aquilo que importa

O desenho óbvio é um proxy inverso que termina o TLS na fronteira. É o desenho errado aqui, por duas razões específicas deste tipo de sistema.

O gateway autentica os agentes pelo seu certificado de cliente, fixado contra uma impressão digital guardada por agente. Terminar o TLS na fronteira acaba aí o aperto de mão mútuo, pelo que a origem teria de confiar naquilo que a fronteira afirmasse. E significaria copiar a chave privada do gateway para várias máquinas virtuais baratas cuja finalidade inteira é serem descartáveis.

Por isso, cada fronteira é um simples reencaminhador de TCP. Vê texto cifrado, não guarda segredos e não corre nenhuma aplicação, o que é uma superfície suficientemente pequena para que «substituí-la» responda à maioria dos problemas.

Custa uma coisa. Todas as ligações passam a parecer originadas na fronteira, pelo que a origem deixa de saber quem está a chamar.

PROXY protocol v2, e a diferença entre uma afirmação e uma medição

A correção habitual é o PROXY protocol: o reencaminhador antecede cada ligação com um pequeno cabeçalho binário que transporta o endereço original. O gateway interpreta-o e volta a pôr o endereço real nos seus registos.

A decisão de desenho que vale a pena copiar é manter dois valores em vez de um.

  • O endereço do cabeçalho é aquilo que a outra ponta diz que o cliente é. É uma afirmação.
  • O endereço do socket exterior, a máquina de onde os bytes vieram de facto, é uma coisa que medimos nós próprios.

Um cabeçalho forjado consegue fazer o primeiro mentir. O segundo continua verdadeiro. Mantê-los como campos separados faz com que uma linha de registo possa ser lida daí a anos sem dúvidas sobre qual dos dois é, e torna fácil a resposta honesta a «podemos confiar no IP»: nada depende dele a não ser uma linha de registo, porque a identidade depende do certificado do par.

Duas notas de implementação que nos pouparam desgostos:

Farejar, não exigir. Uma migração para o PROXY protocol não é atómica. Se os primeiros bytes parecerem um ClientHello de TLS em vez da assinatura do protocolo, devolvem-se ao fluxo e a ligação é reencaminhada intacta. Isso faz da instalação do interpretador e da sua ativação nos reencaminhadores dois passos independentes.

Manter um mapa indexado pela ligação interna. Assim que os bytes chegam a uma biblioteca de gRPC, o endereço do par é uma porta de loopback, e tanto o agente como a fronteira ficam impossíveis de encontrar. Indexar o par real pela porta de origem da ligação interna, e limpá-lo ao fechar, é o único sítio onde essa informação sobrevive.

Há também uma pequena vitória operacional escondida ali: uma ligação que abre e fecha sem enviar nada não produz linha de registo nenhuma. As verificações de saúde fazem isso constantemente, e não as escrever removeu dezenas de milhares de linhas inúteis por dia.

Descoberta: o conjunto A é a lista

Como é que o agente fica a saber que há mais do que uma fronteira? As opções eram registos TXT com um conteúdo estruturado, registos SRV, ou simplesmente vários registos A no mesmo nome.

Vários registos A ganharam por um argumento. O conjunto A já é a lista de sítios a que ligar. Um registo TXT seria uma segunda fonte de verdade para o mesmo facto, e as duas podem discordar. O SRV acrescentaria pesos de que não precisamos, porque o agente mede por si próprio em vez de lhe ser dito qual preferir. O TTL é curto, pelo que remover uma fronteira é uma alteração de DNS mais um TTL de paciência.

O que um cliente faz de facto com vários registos A

Antes de construir qualquer lógica de escolha, valia a pena medir o que acontece de graça. Um cliente HTTP moderno experimenta os endereços do conjunto de registos e distribui por eles o seu tempo limite de ligação. Pusemos à frente um endereço que sabíamos estar morto e medimos:

Comportamento do endereço mortoTempo até ligar ao que está vivo
Ligação recusada7 ms
Sem rota para o anfitrião7,5 ms
SYN descartado em silêncio, limite de ligação de 25 s12,5 s
SYN descartado em silêncio, limite de ligação de 10 s5,0 s

O padrão é claro que chegue para se desenhar em cima dele: as rejeições duras são gratuitas, e um buraco negro custa aproximadamente o tempo limite de ligação dividido pelo número de endereços. A comutação por falha já funcionava, portanto, sem alterar o agente, pelo que a mudança de DNS podia ser publicada antes da lógica de escolha. Também quer dizer que o tempo limite de ligação passa a ser uma função do número de registos publicados, o que não é óbvio até se ter esta tabela à frente.

Comutar por falha não é escolher

A comutação por falha gratuita escolhe o endereço que responder primeiro, o que não é o mesmo que o melhor. Um agente em Amesterdão ficou numa fronteira noutro país durante dias, porque foi essa que respondeu primeiro e a sessão viveu depois uma semana.

Por isso o agente mede. Os pormenores que contaram:

Sondar com um aperto de mão de TLS completo, e não com uma ligação de TCP. Uma fronteira pode aceitar uma ligação e não reencaminhar nada, que é exatamente a falha que a sonda tem de apanhar. Uma ligação simples é respondida com igual alegria por um reencaminhador avariado e por um que funciona.

Dar a cada sonda o seu próprio orçamento, em paralelo. Uma fronteira que aceite e depois fique calada pode deixar um aperto de mão pendurado para lá do tempo limite de ligação, pelo que o orçamento por endereço fica ao lado dele em vez de depender dele.

Guardar a ordenação inteira, e não o vencedor. O endereço escolhido e a lista ordenada vão para o ficheiro de estado do agente, juntamente com o conjunto de endereços a partir do qual foram medidos. Isso torna um reinício barato, e faz com que uma comutação por falha sobreviva a um arranque em vez de voltar alegremente ao endereço que acabou de falhar.

Invalidar pelo conjunto, e não pela escolha. Se uma fronteira for acrescentada ou removida, a medição está velha mesmo que o endereço escolhido continue lá dentro.

Não fazer nada quando não há nada para escolher. Com menos de dois candidatos, o mecanismo inteiro é ignorado, que é o que acontece em desenvolvimento, onde o nome resolve para um único endereço.

E a regra que governa tudo isto: a escolha nunca pode tornar-se um novo modo de falha. O DNS recusa, ninguém responde, o ficheiro de estado não pode ser escrito: todos esses caminhos acabam em «sem escolha», e o agente liga-se exatamente como se ligava antes de nada disto existir.

Voltar é mais difícil do que comutar por falha

Comutar por falha é fácil. A metade difícil é que depois o agente fica na fronteira mais lenta enquanto essa sessão viver, o que pode ser semanas.

Por isso corre uma vigia ao lado da sessão ativa que faz duas coisas e mais nada. Experimenta os endereços classificados acima do atual, com o mesmo aperto de mão completo, e volta a medir tudo quando a última medição estiver velha. Os números, e a razão de cada um:

  • Um intervalo de cinco minutos com dispersão total, para que as sondas de uma frota não se sincronizem. Cada minuto custa cinco vezes mais e não ganha nada, porque a histerese abaixo faz com que qualquer regresso demore sempre duas rondas.
  • Uma série de dois antes de voltar. Uma sonda bem-sucedida é uma fronteira que acabou de reiniciar.
  • Dois limiares para a remedição periódica: um absoluto de 20 ms e um relativo de 25 %. Ambos têm de ser cumpridos. O absoluto sozinho aprovaria uma mudança de 100 ms para 79 ms; o relativo sozinho aprovaria 4 ms para 3 ms. Duas fronteiras no mesmo centro de dados diferem menos do que um milissegundo e, sem isto, trocariam de lugar dia sim, dia não.

Mudar é uma religação limpa com um motivo próprio, colocada junto aos outros finais de sessão planeados em vez de ser modelada como um erro. Isso importa mais do que parece: um erro alimentaria a escada de recuo exponencial, pelo que um agente que voltasse à sua fronteira preferida seria castigado com um atraso crescente por fazer a coisa certa. A caixa de saída de mensagens é reproduzida e o depósito de duplicados garante que nada é executado duas vezes.

Ver a mesma coisa do outro lado

A plataforma vigia a mesma coisa do seu lado, e há aí dois pormenores que vale a pena roubar.

Resolver o nome da mesma forma que o agente o resolve, através do resolvedor do sistema e não com uma consulta de DNS direta. Uma consulta direta falhou por completo numa máquina cujo resolvedor apontava para si própria e, mais importante, o resolvedor do sistema é aquilo que o tráfego real usa, pelo que devolve a mesma lista.

Tirar dois números de um único socket: a ligação de TCP, que é a ida e volta até à própria fronteira, e o aperto de mão de TLS completo, que tem de viajar pelo túnel até à origem e voltar. A saúde depende do segundo. Um reencaminhador com um backend morto responde perfeitamente ao primeiro.

A última lição é de firewall. O Docker contorna por completo a firewall do anfitrião. As regras de que só as fronteiras podem alcançar as portas do gateway têm de viver na cadeia que o próprio Docker consulta, ou são decorativas com ar de ativas. E essa cadeia vê o tráfego nos dois sentidos, pelo que as nossas próprias sondas de saída, da origem para as fronteiras, correspondiam à mesma porta de destino e eram descartadas, fazendo o monitor comunicar todas as fronteiras como em baixo enquanto todos os agentes estavam bem.

O que isto não resolve

As fronteiras não protegem o conteúdo, e seria um erro começar a tratá-las como uma fronteira de segurança. Um endereço que estivesse em baixo durante uma medição completa só é redescoberto na medição seguinte, uma lacuna conhecida e aceite. E o TTL do DNS é um piso à rapidez com que uma fronteira sai da rotação, o que é o preço de deixar o conjunto A ser a lista.

É assim que o agente alcança a plataforma no Provibr, onde só se liga para fora e mantém as credenciais no anfitrião do próprio cliente.

Comece com um host e um agente

Crie uma conta, instale o agente com um único comando e ligue o seu primeiro host Proxmox. O período experimental dura 14 dias e não pede quaisquer dados de pagamento.