Joao Brietzke Blog

← voltar

Route 53: health checks e políticas de roteamento

O nome da funcionalidade engana. Uma "política de roteamento" do Route 53 não roteia nada. Nenhum pacote da sua aplicação passa por ele. O Route 53 recebe uma pergunta, devolve uma resposta e sai de cena, e o cliente abre a conexão sozinho, direto no endereço que recebeu.

O que a política decide é mais modesto e mais interessante: qual resposta devolver quando existe mais de uma resposta possível para o mesmo nome. Toda a inteligência de tráfego que se constrói em cima disso, canário, failover entre regiões, roteamento por país, é consequência de responder a mesma pergunta de formas diferentes conforme quem pergunta e conforme o que está vivo.

Este post é a camada de baixo do que ficou como tabela em Route 53 e DNS. Começa pelos health checks, porque sem eles metade das políticas é só sorteio estático.


O conjunto de registros

Antes de qualquer política, uma regra estrutural. Registros que dividem o mesmo nome e o mesmo tipo formam um conjunto, e o Route 53 escolhe um (ou alguns) deles para responder. Três restrições saem daí:

  • Cada registro do conjunto precisa de um Set ID único. É o identificador que separa api.exemplo.com A de api.exemplo.com A, que de outra forma seriam o mesmo registro.
  • Todos os registros do conjunto precisam usar a mesma política. Não existe um weighted convivendo com um failover no mesmo nome e tipo.
  • api.exemplo.com tipo A e api.exemplo.com tipo AAAA são conjuntos independentes, com políticas independentes. Um cliente com IPv6 pode receber uma decisão diferente de um cliente com IPv4 se você configurar os dois de forma inconsistente, e esse é um bug clássico de configuração.

E a consequência que atravessa o post inteiro: a decisão é tomada por consulta de resolver, não por requisição de usuário nem por sessão. Um resolver que cacheou a resposta serve milhares de clientes com ela até o TTL expirar.

Health checks

O Route 53 não sabe nada sobre a sua aplicação. O health check é o único canal por onde a realidade entra na decisão. Existem três tipos, e eles resolvem problemas diferentes.

Endpoint health check

Um conjunto de verificadores distribuídos por várias regiões AWS sonda o endpoint de fora da sua infraestrutura. Cada verificador decide sozinho, e o registro é considerado saudável quando uma fração suficiente deles concorda. Isso importa: o health check é imune a uma falha de rede local a um verificador, porque nunca é uma sonda única.

Parâmetro Padrão Efeito
Intervalo 30s (ou 10s no modo fast) De quanto em quanto tempo cada verificador sonda
Failure threshold 3 falhas consecutivas Quantas falhas até marcar como doente
Protocolo TCP, HTTP, HTTPS O TCP só valida handshake, não valida aplicação
String matching desligado Procura um texto nos primeiros 5120 bytes do corpo

O tempo de detecção é intervalo vezes threshold, ou seja, cerca de 90 segundos no padrão e cerca de 30 no modo fast. Esse número vai reaparecer mais adiante, porque ele é metade do tempo de recuperação de um failover.

Quatro detalhes que mordem:

String matching é o que separa health check de ping sofisticado. Um /health que devolve 200 com {"db":"down"} no corpo passa num check HTTP comum. Configurar a busca por uma string como "status":"ok" transforma a sonda em verificação de aplicação de verdade.

HTTPS não valida certificado. Health check não substitui monitoramento de expiração de certificado. Ele completa o handshake TLS sem se importar com validade ou cadeia.

Os verificadores vêm de faixas de IP publicadas pela AWS. Security group, NACL ou WAF restritivos precisam liberar essas faixas, senão o check falha para sempre e a sua aplicação saudável é tratada como morta.

O endpoint precisa ser alcançável pela internet. Uma instância em subnet privada não pode ser sondada diretamente. Para ela, o caminho é o terceiro tipo de health check.

Calculated health check

Combina outros health checks numa expressão booleana: AND, OR, NOT, ou "pelo menos N de M estão saudáveis". Não sonda nada por conta própria.

Serve para expressar saúde composta. "O serviço está saudável se pelo menos 2 dos 3 backends estiverem" é uma frase que nenhum health check individual consegue dizer. Também serve como interruptor manual: um check calculado que sempre falha, pendurado como dependência, é um jeito de tirar uma região de rotação de propósito.

CloudWatch alarm health check

Ancora a saúde num alarme do CloudWatch em vez de numa sonda. É a única forma de checar algo que não tem endpoint público para sondar: profundidade de fila SQS, latência do RDS, taxa de 5xx agregada no ALB, throttle no DynamoDB, ou qualquer métrica customizada que a aplicação publique.

O detalhe operacional: o estado INSUFFICIENT_DATA do alarme precisa de uma decisão explícita sua, você escolhe se ele conta como saudável ou como doente. Métrica que só existe quando há tráfego (erro 5xx, por exemplo) some de madrugada, e a escolha errada aqui derruba a produção às três da manhã sem que nada tenha quebrado.

Evaluate Target Health

O caso especial que confunde quase todo mundo. Em registros alias apontando para recursos AWS, você normalmente não anexa um health check, você liga a flag Evaluate Target Health.

Com ela ligada, o Route 53 herda a saúde do próprio recurso. Um ALB é considerado saudável se tiver ao menos um target saudável em ao menos uma AZ, e essa informação já existe dentro do ELB, sem custo e sem sonda externa. Mais importante: isso cascateia por aliases encadeados, e é exatamente o que faz o aninhamento de políticas funcionar mais adiante.

O comportamento de fail-open

A regra que mais surpreende: se todos os registros de um conjunto estiverem doentes, o Route 53 responde como se todos estivessem saudáveis.

A lógica é defensável. Devolver um endereço provavelmente ruim é melhor do que devolver resposta nenhuma, porque a ausência de resposta quebra também os clientes que teriam algum caminho alternativo. A consequência prática é que ausência de resposta nunca é sintoma de outage. Do ponto de vista do DNS, tudo caído e tudo no ar são indistinguíveis. O alarme precisa vir do CloudWatch em cima do health check, não do comportamento da resolução.

As oito políticas

Simple

Um único registro, sem lógica e sem health check. Pode conter vários valores (vários IPs num mesmo A), e nesse caso todos são devolvidos em ordem aleatória e o cliente escolhe.

A distinção que importa é com o multivalue: aqui é um registro com N valores, e todos entram na resposta esteja quem estiver vivo. Lá são N registros independentes, cada um com saúde própria.

Use quando não existe decisão a tomar.

Weighted

Peso de 0 a 255 por registro, e a chance de ser devolvido é peso dividido pela soma dos pesos.

Duas armadilhas específicas:

Peso 0 desliga aquele registro, mas se todos forem 0 o Route 53 devolve todos como iguais. Zerar o conjunto inteiro não é um kill switch, é o contrário do que a intuição diz.

A distribuição é por consulta de resolver, e o cache distorce. Um peso de 1 contra 99 não significa que 1% dos usuários cai no canário. Significa que ~1% das consultas de resolvers cai. Se um resolver grande sortear o canário, ele manda toda a base de clientes dele para lá até o TTL expirar. Weighted com TTL alto é uma péssima ferramenta de canário.

Bom para: canário e blue/green movendo peso gradualmente, migração de datacenter para nuvem, e testes A/B grosseiros onde a granularidade de resolver é aceitável.

Latency

Devolve o registro associado à região AWS com a menor latência medida até quem perguntou. Não é distância geográfica, é latência de rede real, medida continuamente pela AWS a partir de tráfego de internet.

Duas coisas decorrem disso. Cada registro precisa ser associado a uma região AWS, o que amarra a política a recursos que vivem na AWS. E a resposta muda ao longo do tempo sem você mexer em nada, porque as medições mudam quando a topologia da internet muda.

A limitação estrutural aparece aqui e vale para as três políticas geográficas: o Route 53 enxerga o IP do resolver recursivo, não o do usuário. Alguém em Porto Alegre usando um resolver corporativo em Miami é tratado como se estivesse em Miami. O EDNS Client Subnet mitiga, o resolver repassa um prefixo da sub-rede do cliente junto da pergunta, e o Route 53 respeita, mas nem todo resolver envia.

Failover

Ativo-passivo puro. Um registro marcado PRIMARY, um marcado SECONDARY. Não é uma lista de prioridades de N itens, são exatamente dois papéis.

  • O primário precisa de health check ou de evaluate target health. Sem isso não existe evento capaz de disparar o failover, e a política vira um simple caro.
  • O secundário pode ter health check também. Se os dois estiverem doentes, o primário é devolvido.

O tempo de recuperação não é o tempo de detecção. É detecção mais TTL:

falha real
   │
   ├── até 90s  ── health check marca como doente (30s × 3)
   │
   ├── até TTL  ── resolvers ainda servem a resposta antiga do cache
   │
   ▼
tráfego chega no secundário

Com TTL de 300 segundos, o pior caso passa de seis minutos. Registro de failover quer TTL de 60 segundos ou menos, e esse é um dos poucos lugares onde o custo extra de consultas se justifica sem discussão.

O padrão clássico: primário no ALB, secundário num bucket S3 servindo uma página estática de manutenção. Custa quase nada e cobre o caso em que a aplicação inteira desapareceu.

Geolocation

Decide pela origem geográfica da consulta, em três níveis discretos: continente, país e subdivisão (estados dos EUA e algumas outras regiões). A correspondência mais específica ganha: subdivisão vence país, que vence continente, que vence o registro Default.

O erro mais comum é não criar o registro Default. Sem ele, qualquer consulta vinda de um lugar que você não mapeou recebe resposta vazia, e o nome simplesmente não resolve para aquele usuário. É um outage silencioso e regional, o tipo mais difícil de perceber de dentro do escritório.

Use para o que é regra de negócio, não de performance: idioma e conteúdo localizado, licenciamento por território, restrição de distribuição, requisitos de compliance. Se o objetivo é velocidade, a política certa é latency.

Geoproximity

Também é geografia, mas contínua em vez de discreta. Calcula a distância entre o usuário e cada recurso e devolve o mais próximo. O diferencial é o bias, um número de -99 a +99 que expande ou encolhe artificialmente a área de influência de cada recurso.

O bias é o motivo de existir da política. Ele permite dizer "quero mandar mais tráfego para São Paulo do que a geografia sugeriria", que é uma decisão de capacidade, não de rede. E os recursos podem ser regiões AWS ou coordenadas de latitude e longitude arbitrárias, o que a torna a única política geográfica que atende recursos fora da AWS de forma natural.

Em uma frase: geolocation responde "de onde ele é", geoproximity responde "o que está mais perto dele, com o meu polegar na balança".

Multivalue answer

Até 8 registros saudáveis, devolvidos embaralhados, cada um com health check próprio. O cliente escolhe um.

Vale insistir: não é load balancer. Não conhece carga, não termina conexão, e depende inteiramente de o cliente tentar outro endereço quando o primeiro falha. Navegadores fazem isso. Muito cliente HTTP customizado, não.

O valor real dele é ser round-robin com remoção de nó morto, que é justamente o que o simple com múltiplos valores não oferece.

IP-based

Você cria CIDR collections, que mapeiam blocos de IP de cliente para nomes de localização, e associa registros a esses nomes. É a única política em que você define o mapeamento em vez de aceitar o da AWS.

Casos reais: mandar clientes de uma ISP específica por um caminho específico por causa de acordo de peering, direcionar a faixa corporativa da empresa para um ambiente interno, ou corrigir à mão uma geolocalização que a AWS erra.

Aninhamento, onde isso vira arquitetura

Cada política isolada resolve pouco. O poder está em compor, e a composição funciona porque um registro alias pode apontar para outro registro da mesma zona.

O padrão canônico de multi-região:

api.exemplo.com                       (latency)
├── us-east-1 ──alias──▶ api-use1.exemplo.com   (failover)
│                        ├── PRIMARY   ──alias──▶ ALB us-east-1
│                        └── SECONDARY ──alias──▶ S3 página de manutenção
└── sa-east-1 ──alias──▶ api-sae1.exemplo.com   (weighted)
                         ├── peso 90 ──alias──▶ ALB estável
                         └── peso 10 ──alias──▶ ALB canário

Latency escolhe a região, failover garante a região, weighted faz o canário dentro dela.

A saúde propaga de baixo para cima via Evaluate Target Health. Se todos os registros de api-use1 ficarem doentes, o nó inteiro fica doente, e o latency exclui us-east-1 da escolha e manda o usuário para sa-east-1. É assim que se obtém failover entre regiões sem que exista uma política chamada "failover entre regiões". Ele emerge da composição.

O Traffic Flow é a interface visual para montar essa árvore como um objeto único, com versionamento e rollback, em vez de dezenas de registros soltos. Ele cobra por policy record ativo, na casa de algumas dezenas de dólares por mês, então costuma ser dispensável para árvores pequenas mantidas por infraestrutura como código.

O que quebra na prática

TTL é o piso de toda reação. Nenhuma política reage mais rápido que o TTL. DNS failover é uma ferramenta de minutos, não de segundos. Se você precisa de segundos, a decisão tem que estar mais perto do tráfego: health check do próprio ALB entre AZs, ou Global Accelerator, que faz o desvio na rede via anycast e não depende de DNS nenhum.

Cliente que cacheia para sempre. JVMs com networkaddress.cache.ttl em -1 guardam a resolução pela vida inteira do processo. O Route 53 pode fazer tudo certo e a aplicação continuar batendo no IP morto até alguém reiniciar o pod.

Fail-open esconde o desastre. Todos doentes significa todos devolvidos. O sintoma de outage total é indistinguível do funcionamento normal na resposta DNS.

A geografia é do resolver, não do usuário. Vale para latency, geolocation e geoproximity. As três herdam a mesma imprecisão de origem.

Custo. A hosted zone é barata. O que aparece na fatura são os health checks, cobrados por unidade e por mês (com adicional para fast interval e string matching), e as consultas respondidas por políticas avançadas, que custam mais por milhão do que as simples. Consultas respondidas por alias apontando para recurso AWS são gratuitas, o que é mais um argumento a favor do alias.

Como isso se conecta com o resto

Em Load balancer, a distribuição acontecia na conexão, com o balanceador no meio do caminho, olhando carga e terminando sessão. Aqui a distribuição acontece antes da conexão existir, e o Route 53 nunca vê o tráfego. São dois níveis diferentes da mesma ideia, e a diferença entre eles explica por que multivalue answer não substitui um ALB e por que um ALB não faz failover entre regiões.

O Gateway Load Balancer é o terceiro ponto dessa reta: lá a decisão era de rota, feita na tabela de roteamento, invisível para o DNS e para o cliente. DNS escolhe destino, load balancer escolhe backend, rota escolhe caminho.

E os health checks ancorados em alarme do CloudWatch são a ponte com o resto da operação: eles permitem que uma métrica de aplicação, e não só uma sonda de rede, decida para onde o seu tráfego vai.

As cinco coisas para levar

  1. Política de roteamento escolhe qual resposta devolver, não por onde o tráfego passa. O Route 53 sai de cena antes do primeiro pacote.
  2. Health check é o único canal por onde a realidade entra. Endpoint sonda de fora, calculated compõe, alarme do CloudWatch cobre o que não tem endpoint público.
  3. Fail-open: todos doentes é tratado como todos saudáveis. O alarme tem que vir do CloudWatch, nunca da resolução falhar.
  4. Tempo de recuperação de um failover é detecção mais TTL, não só detecção. Sem TTL baixo, nenhum health check rápido adianta.
  5. As políticas realmente úteis são as compostas. Latency por fora, failover ou weighted por dentro, com evaluate target health propagando saúde de baixo para cima.

Referências