← todos os artigos

CoreDNS 1.15: cache de SERVFAIL muda o comportamento quando o upstream cai

0

Quando o DNS upstream começa a falhar, a reação mais óbvia é aumentar réplicas do CoreDNS. Só que mais réplicas também podem significar mais consultas repetidas contra o mesmo upstream indisponível.

O CoreDNS 1.15.0, publicado em 21 de outubro de 2026, muda uma parte pequena desse comportamento: o plugin cache agora consegue guardar um SERVFAIL produzido quando o próximo plugin retorna erro sem escrever uma resposta.

Esse é o caso do forward quando o upstream estoura o timeout. Antes da correção, a falha não passava pelo ResponseWriter do cache. Cada nova tentativa da mesma consulta voltava ao upstream e pagava novamente o timeout completo. O PR #8629 fecha essa diferença entre um SERVFAIL recebido do servidor e um SERVFAIL gerado localmente a partir do erro.

Parece um detalhe interno, mas ele muda a carga durante uma falha. E exige entender três coisas separadas: cache de sucesso, cache de SERVFAIL e resposta stale.

Este texto é uma análise da release e da documentação oficial. Não executei teste de carga ou falha em cluster.

O que mudou no caminho do timeout

O CoreDNS já tinha a diretiva servfail dentro do plugin cache. Pela documentação da versão 1.15.0, ela guarda respostas SERVFAIL por 5 segundos quando nenhum valor é definido. O limite configurável não pode passar de 5 minutos; o valor zero desabilita esse cache.

A lacuna estava em como a falha chegava ao cache. Se o upstream realmente respondesse SERVFAIL, a resposta podia ser guardada. Se o forward desistisse por timeout e retornasse erro sem escrever uma mensagem DNS, não havia o que o cache armazenar.

A versão 1.15.0 gera e guarda o SERVFAIL respeitando a diretiva existente. Enquanto ele estiver válido, consultas idênticas recebem a falha do cache em vez de abrir outra tentativa contra o upstream.

Isso reduz repetição durante a janela configurada. Não transforma falha em sucesso e não aumenta a disponibilidade dos registros que nunca chegaram ao cache.

A configuração precisa expressar a intenção

Um Corefile de referência pode declarar o comportamento de forma explícita:

.:53 {
    errors
    health
    ready

    kubernetes cluster.local in-addr.arpa ip6.arpa

    forward . /etc/resolv.conf

    cache 300 {
        servfail 5s
    }

    prometheus :9153
}

Esse trecho é um padrão de referência. Ele não deve substituir o Corefile do seu cluster sem revisar plugins, zonas, portas e customizações existentes.

O cache 300 limita o TTL máximo guardado para as respostas normais. Ele não força todos os registros a durarem 300 segundos: um registro com TTL menor mantém o valor menor.

Já servfail 5s controla a janela das falhas. Aumentar esse período reduz novas tentativas contra o upstream, mas também prolonga a falha percebida depois que o upstream se recupera. Desabilitar com servfail 0 preserva o comportamento sem cache para esse tipo de resposta.

Não existe um valor universal. O ponto é alinhar a janela com o timeout do forward, o comportamento de retry dos clientes e o tempo aceitável para perceber recuperação.

SERVFAIL em cache não é serve_stale

As duas funções lidam com indisponibilidade, mas respondem perguntas diferentes.

O cache de SERVFAIL diz: “essa consulta falhou recentemente; durante um período curto, devolva a mesma falha sem repetir todo o caminho”.

serve_stale diz: “existe uma resposta positiva ou negativa que venceu, mas ainda pode ser usada enquanto tentamos atualizar”.

Um exemplo de referência com stale imediato:

cache 300 {
    serve_stale 1h immediate 30s 30s
    servfail 5s
}

Conforme a documentação do 1.15.0, immediate devolve a entrada vencida antes de consultar a origem. Nesse exemplo, a resposta stale volta com TTL de 30 segundos e uma falha de refresh só permite nova tentativa depois de 30 segundos para aquela entrada.

O modo verify consulta a origem antes de servir o dado vencido. Isso pode aumentar a latência. A documentação também esclarece um detalhe que costuma passar despercebido: com FAILURE_RECHECK positivo, requisições concorrentes podem receber stale imediatamente enquanto uma verificação está em andamento, mesmo que ela termine com sucesso depois.

Escolher entre disponibilidade e atualização do dado é uma decisão operacional. Um endereço antigo pode manter a aplicação funcionando ou direcionar tráfego para um destino que já deveria ter saído de rotação. Avalie o tipo de registro e a dinâmica do ambiente.

Como observar sem inventar um “número saudável”

Com o plugin prometheus habilitado, a documentação lista métricas para requests, hits, entradas, evictions e respostas stale.

Duas relações ajudam a acompanhar o efeito da configuração:

sum(rate(coredns_cache_served_stale_total[5m]))
sum(rate(coredns_cache_hits_total[5m]))
/
sum(rate(coredns_cache_requests_total[5m]))

A primeira mostra a taxa de respostas stale. A segunda calcula a proporção de hits sobre requests no período. Trate divisão por zero e dimensões como server, zones e view conforme seu conjunto real de séries.

Essas queries não definem um limite ideal. Um pico de stale durante uma falha conhecida pode confirmar que o mecanismo atuou. Crescimento contínuo fora de incidentes pode indicar upstream instável, janela excessiva ou dependência de dados vencidos.

A lista documentada não traz um contador específico de “SERVFAIL servido do cache”. Para analisar esse caso, combine métricas do cache, erros do forward, latência e logs. Não atribua todo cache hit ao novo comportamento.

O que mais revisar no 1.15.0

A mesma release também muda o tratamento de hostnames no forward. O PR #8563 passa a tratar nomes de upstream como FQDNs absolutos, evitando que search domains de /etc/resolv.conf redirecionem a resolução para outro servidor.

Configurações que dependiam de nomes curtos precisam usar o hostname completo, como dns-upstream.default.svc.cluster.local. O próprio PR classifica essa mudança como incompatível para esse caso.

A release inclui ainda correções de panic em plugins, melhorias de transporte e DNS-over-QUIC para upstreams. Antes de atualizar, compare os plugins presentes no seu Corefile com as notas completas.

Como aplicar em EKS, GKE e AKS

Primeiro, confira o contexto e as imagens em execução:

kubectl config current-context
kubectl -n kube-system get deployments \
  -o custom-columns='NAME:.metadata.name,IMAGES:.spec.template.spec.containers[*].image'

Configurar servfail não instala a correção do 1.15.0. Confirme se a build do provedor contém a mudança; backports precisam de confirmação do fornecedor. Confira também quais argumentos de serve_stale sua versão aceita. Os exemplos são referências e não foram executados nesses serviços.

EKS: leve o Corefile para a configuração do add-on

Para o add-on gerenciado do EKS, use configurationValues.corefile: edições diretas no ConfigMap podem ser sobrescritas em atualizações. Preserve ready e os demais plugins existentes.

Com AWS CLI, jq e o contexto Kubernetes apontando para o mesmo cluster:

CLUSTER=meu-cluster
aws eks describe-addon --cluster-name "$CLUSTER" \
  --addon-name coredns > addon-before.json

VERSION=$(jq -r '.addon.addonVersion' addon-before.json)
aws eks describe-addon-configuration --addon-name coredns \
  --addon-version "$VERSION" --query configurationSchema --output text

kubectl -n kube-system get configmap coredns \
  -o jsonpath='{.data.Corefile}' > Corefile

jq '.addon.configurationValues | if . == null or . == "" then {} else fromjson end' \
  addon-before.json > values-before.json

Edite o arquivo Corefile: no bloco cache existente, acrescente servfail 5s. Não crie um segundo plugin cache no mesmo server block. Depois, preserve os outros valores e envie o Corefile completo:

jq --rawfile corefile Corefile '.corefile = $corefile' \
  values-before.json > values-after.json

aws eks update-addon --cluster-name "$CLUSTER" --addon-name coredns \
  --configuration-values file://values-after.json

aws eks wait addon-active --cluster-name "$CLUSTER" --addon-name coredns

A API valida os valores contra o schema. Se houver conflito, revise os campos antes de escolher a política de resolução. Este comando altera configuração, sem solicitar outra versão.

GKE: confira qual DNS está por trás do serviço

Na documentação do GKE, Standard pode usar kube-dns; a implementação passa a ser baseada em CoreDNS no GKE 1.36. Autopilot usa Cloud DNS por padrão. O nome do Service kube-dns sozinho não identifica o backend.

Para um Standard com kube-dns, inspecione a customização:

kubectl -n kube-system get configmap kube-dns -o yaml

A interface documentada expõe stubDomains e upstreamNameservers, não um Corefile arbitrário. Um encaminhamento por domínio pode ser declarado assim, incorporando a chave ao ConfigMap existente:

data:
  stubDomains: |
    {"corp.example": ["10.20.0.10"]}

Substitua domínio e IP pelos do seu DNS. Isso configura encaminhamento, não servfail ou serve_stale. Não há equivalência direta por essa interface: para o DNS gerenciado, acompanhe a build disponibilizada pelo GKE e os controles documentados do backend escolhido.

AKS: demonstre o ajuste numa zona customizada

O AKS documenta customizações por coredns-custom, com chaves terminadas em .server ou .override. Para uma zona encaminhada, incorpore este exemplo ao ConfigMap, preservando outras entradas:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns-custom
  namespace: kube-system
data:
  corp.server: |
    corp.example:53 {
        errors
        cache 300 {
            servfail 5s
        }
        forward . 10.20.0.10
    }

Troque domínio e IP. Salve como coredns-custom.yaml e aplique na janela planejada:

kubectl apply -f coredns-custom.yaml
kubectl -n kube-system rollout restart deployment coredns
kubectl -n kube-system rollout status deployment coredns

O ajuste vale somente para corp.example, sem alterar o cache da zona raiz. Não adicione outro cache via .override onde já existe um: essa diretiva só pode aparecer uma vez por server block.

Nos três casos, guarde a configuração anterior. Após aplicar, confira logs de inicialização, resolução interna e externa e, em ambiente de validação, falha e recuperação do upstream. Pods prontos não comprovam sozinhos o comportamento do cache.

O que fazer a seguir

Primeiro, descubra qual imagem e versão cada deployment de DNS está usando; não assuma que a versão do Kubernetes determina sozinha a versão do CoreDNS.

Depois, revise cache, servfail, serve_stale e os destinos do forward. Torne explícitos os valores que representam sua política e substitua hostnames curtos de upstream quando existirem.

Valide em ambiente representativo consultas normais, timeout do upstream, recuperação e mudança de registros. A pergunta que fecha a atualização é prática: quando o upstream falha e volta, o CoreDNS reduz a pressão sem esconder a recuperação por mais tempo do que sua operação aceita?