CoreDNS 1.15: cache de SERVFAIL muda o comportamento quando o upstream cai
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?