Pular para o conteúdo
11 min de leitura

DNS Interno e Service Discovery no Kubernetes: Como o CoreDNS Encontra Seus Serviços

Compartilhar

Por Equipe Nebular ·

Como funciona a resolução de nomes dentro do cluster: CoreDNS, records de Service, FQDN, headless services, ndots e as armadilhas de latência de DNS.

Neste artigo

Quando você sobe uma aplicação num único servidor, encontrar o banco de dados é trivial: ele está em localhost ou num IP fixo que você anota e nunca mais muda. No Kubernetes esse conforto desaparece. Pods nascem e morrem o tempo todo, cada um recebe um IP novo e efêmero, e o pod que existe agora provavelmente não é o mesmo que existia há cinco minutos. Se o seu serviço de checkout precisa falar com o serviço de pagamentos, ele não pode guardar o IP de um pod específico — esse IP some no primeiro deploy, no primeiro reinício, no primeiro reescalonamento.

É esse o problema que o service discovery resolve, e no Kubernetes ele é resolvido principalmente por DNS. Em vez de perseguir IPs voláteis, sua aplicação fala com um nome estável — pagamentos, pagamentos.financeiro, ou o nome completo pagamentos.financeiro.svc.cluster.local — e alguém traduz esse nome para o endereço certo no momento da chamada. Esse "alguém" é o CoreDNS, o servidor DNS que roda dentro do cluster e que quase ninguém percebe até o dia em que ele fica lento e derruba metade da aplicação.

Neste guia você vai entender como a resolução de nomes acontece por dentro: onde o CoreDNS roda, como cada pod é apontado para ele, que tipos de record um Service gera, por que StatefulSets dependem de headless services, e a armadilha mais famosa de todas — o ndots:5 que faz uma consulta simples virar cinco consultas e transforma latência de DNS num gargalo silencioso. No fim, você vai saber diagnosticar DNS lento antes que ele diagnostique você.

O CoreDNS é o servidor DNS do cluster

O CoreDNS é um servidor DNS leve, escrito em Go e organizado em plugins, que roda como um Deployment normal dentro do namespace kube-system. Ele não é mágica de kernel nem parte do control plane; são apenas alguns pods, expostos por um Service de ClusterIP fixo (tipicamente algo como 10.96.0.10, dependendo da faixa configurada no cluster). Esse ClusterIP é a peça central: ele é conhecido, é estável, e é para ele que todo pod do cluster envia suas perguntas de DNS.

A ligação entre um pod comum e o CoreDNS acontece através do kubelet. Quando o kubelet cria um pod, ele escreve o arquivo /etc/resolv.conf de dentro do container apontando o nameserver para o ClusterIP do CoreDNS. É por isso que qualquer processo dentro do pod — sua aplicação, um curl, uma chamada de biblioteca — resolve nomes automaticamente pelo CoreDNS, sem que você configure nada. O resolver do sistema operacional dentro do container simplesmente segue o resolv.conf, e esse arquivo já vem apontado para o lugar certo.

Dentro do CoreDNS, quem faz a ponte com o Kubernetes é o plugin kubernetes. Ele observa a API do cluster (Services e Endpoints) e responde às consultas de DNS com base no estado real: quando você pergunta pelo nome de um Service, o plugin sabe qual ClusterIP retornar porque está assistindo à API o tempo todo. Consultas para nomes que não pertencem ao cluster — google.com, por exemplo — são repassadas para os servidores DNS de upstream através do plugin forward, então o mesmo CoreDNS resolve tanto o mundo interno quanto o externo.

Todo Service do tipo ClusterIP ganha automaticamente um nome DNS previsível, seguindo o padrão <servico>.<namespace>.svc.cluster.local. Esse é o FQDN, o nome de domínio totalmente qualificado. Um Service chamado pagamentos no namespace financeiro responde no nome pagamentos.financeiro.svc.cluster.local, e uma consulta A (ou AAAA, para IPv6) por esse nome retorna o ClusterIP do Service — um IP virtual estável que o kube-proxy depois distribui entre os pods reais por trás dele.

O que confunde muita gente é conseguir chamar o serviço só por pagamentos, sem digitar o FQDN inteiro. Isso não é um atalho do Kubernetes; é o comportamento clássico do resolver, habilitado pelo campo search do resolv.conf. O kubelet preenche esse campo com uma lista de sufixos de busca, tipicamente <namespace>.svc.cluster.local, svc.cluster.local e cluster.local. Quando sua aplicação pede pagamentos, o resolver tenta anexar cada sufixo em ordem: primeiro pagamentos.financeiro.svc.cluster.local, e como esse resolve, a busca para ali.

Isso explica duas regras práticas. Dentro do mesmo namespace, o nome curto basta: um pod em financeiro chama pagamentos e chega. Para falar com um serviço em outro namespace, você precisa qualificar pelo menos com o namespace: relatorios.analytics a partir de financeiro, porque o sufixo de busca do namespace local não vai encontrar relatorios em analytics. E em qualquer chamada de fora do cluster, ou quando você quer eliminar ambiguidade e cortar consultas extras, use o FQDN completo — de preferência com o ponto final, pagamentos.financeiro.svc.cluster.local., que sinaliza ao resolver que o nome já é absoluto e não deve receber sufixo nenhum.

Headless Services: quando você quer os pods, não o VIP

O Service comum entrega um ClusterIP — um único IP virtual que esconde os pods reais atrás de um balanceamento. Isso é ótimo para um serviço stateless de web, onde qualquer réplica serve igual. Mas existem casos em que você precisa endereçar os pods individualmente, com identidade estável: um cluster de banco de dados onde a réplica primária é diferente das secundárias, um sistema de mensageria com brokers nomeados, qualquer coisa com estado que precise saber quem é quem.

Para isso existe o headless service, criado com clusterIP: None. Ele não recebe IP virtual e não faz balanceamento. Em vez disso, uma consulta DNS pelo nome do headless service retorna os IPs de todos os pods por trás dele, um por record A. Sua aplicação recebe a lista completa e decide com qual pod falar — que é exatamente o que um cliente de banco distribuído ou um driver de cluster precisa para montar sua própria topologia.

É por isso que StatefulSets dependem de headless services. Um StatefulSet dá a cada pod um nome ordinal estável (banco-0, banco-1, banco-2), e combinado com o headless service cada pod ganha um nome DNS próprio e previsível: banco-0.meu-banco.dados.svc.cluster.local. Esse nome sobrevive a reinícios — o pod banco-0 pode morrer e voltar em outro nó com outro IP, mas o nome DNS continua apontando para ele. Além dos records A por pod, o CoreDNS também publica records SRV, que carregam informação de porta e permitem descoberta mais rica, usada por sistemas que negociam serviços por porta nomeada.

Vale mencionar ainda o Service do tipo ExternalName, que não aponta para pods coisa nenhuma. Ele é apenas um apelido: o CoreDNS responde a consultas por esse Service com um CNAME para um domínio externo, como o endpoint de um banco gerenciado fora do cluster. É uma forma limpa de dar um nome interno e estável a uma dependência externa, para que sua aplicação continue chamando banco-prod mesmo que o hostname real do provedor mude.

A armadilha do ndots:5

Aqui está a parte que morde quase todo cluster em produção mais cedo ou mais tarde. O resolv.conf que o kubelet injeta traz uma opção ndots:5. Esse número controla quando o resolver considera um nome "absoluto o suficiente" para consultar direto, versus quando ele tenta anexar os sufixos de busca primeiro. Com ndots:5, qualquer nome com menos de cinco pontos é tratado como relativo e passa por toda a lista de search antes de ser tentado como absoluto.

O estrago aparece quando você chama um domínio externo comum, como api.stripe.com — que tem só dois pontos, abaixo do limite de cinco. O resolver, obedecendo o ndots, primeiro tenta api.stripe.com.financeiro.svc.cluster.local, depois api.stripe.com.svc.cluster.local, depois api.stripe.com.cluster.local, cada uma dando NXDOMAIN, e só na quarta ou quinta tentativa consulta api.stripe.com de fato. Uma resolução que deveria ser uma pergunta virou quatro ou cinco, e cada uma bate no CoreDNS. Multiplique isso pelo volume de requisições externas de uma aplicação real e você tem o CoreDNS sobrecarregado, timeouts de DNS intermitentes e uma latência de cauda que ninguém consegue explicar.

Há três formas de domar isso. A mais simples e cirúrgica é terminar seus nomes externos com um ponto final — api.stripe.com. — o que marca o nome como absoluto e faz o resolver pular toda a expansão de search. A segunda é ajustar o dnsConfig do pod para reduzir o ndots (por exemplo, para 2), sabendo que isso pode quebrar a resolução por nome curto de serviços internos se sua aplicação depende dela. A terceira, para cargas com muito tráfego externo, é revisar o código para usar FQDNs absolutos nas dependências externas. Entender o ndots é o que separa quem culpa "a rede" de quem resolve o problema em dez minutos.

NodeLocal DNSCache e dimensionamento

Mesmo com o ndots bem resolvido, um cluster grande gera um volume enorme de consultas de DNS, e centralizar tudo em alguns pods de CoreDNS pode virar gargalo. A resposta padrão para isso é o NodeLocal DNSCache: um cache de DNS que roda em cada nó, como um DaemonSet, interceptando as consultas dos pods locais. A maioria das perguntas passa a ser respondida pelo cache no próprio nó, sem sair para a rede, o que corta latência (elimina um salto de rede e o overhead de conexão) e reduz drasticamente a carga que chega ao CoreDNS central. Como bônus, ele usa TCP para falar com o CoreDNS de upstream, evitando problemas conhecidos de perda de pacotes UDP em algumas configurações de rede.

Do lado do CoreDNS em si, vale acompanhar duas coisas. Primeiro, o número de réplicas: o CoreDNS costuma vir com um autoscaler ou com um número fixo modesto, e clusters que crescem precisam de mais réplicas para não saturar. Segundo, o plugin cache do próprio CoreDNS, que guarda respostas por um TTL e evita reconsultar a API ou o upstream repetidamente. Um recurso adicional útil é o autopath, que faz o CoreDNS resolver a expansão de search do lado do servidor, devolvendo a resposta certa numa única viagem em vez de deixar o cliente tentar sufixo por sufixo — uma mitigação server-side para o mesmo problema do ndots.

Diagnóstico e por onde começar

Quando algo cheira a DNS, o primeiro passo é sempre testar de dentro de um pod, porque é o resolv.conf do pod que importa, não o do seu laptop. Suba um pod de depuração com ferramentas de rede e rode nslookup ou dig contra o nome que está falhando — tanto o nome curto quanto o FQDN — e observe o que volta: NXDOMAIN indica nome errado ou sufixo de busca faltando; timeout indica CoreDNS sobrecarregado ou inacessível; respostas lentas indicam o problema de ndots ou falta de cache. Inspecionar cat /etc/resolv.conf dentro do pod revela o nameserver, a lista de search e o ndots em vigor — três informações que explicam a maioria dos mistérios.

Os sintomas clássicos de DNS doente valem memorizar: latência de cauda que aparece e some sem padrão, timeouts esporádicos em chamadas a serviços externos, aplicações que ficam mais lentas conforme o tráfego cresce mas cuja CPU não sobe. Quase sempre a causa é uma destas três — poucas réplicas de CoreDNS, ausência de cache local, ou a expansão descontrolada de search por ndots.

Se você está começando agora, siga uma ordem simples. Primeiro, entenda e confie no FQDN: prefira nomes qualificados nas comunicações entre namespaces e termine domínios externos com ponto final para cortar consultas inúteis. Segundo, para qualquer carga com estado, use headless service com StatefulSet e endereços de pod estáveis em vez de tentar rastrear IPs. Terceiro, se o cluster tem escala, ative o NodeLocal DNSCache e monitore as métricas do CoreDNS — latência de resolução, taxa de erro e uso de CPU dos pods de DNS — como você monitora qualquer outra dependência crítica. DNS interno é infraestrutura invisível quando funciona e catastrófica quando falha; tratá-lo como cidadão de primeira classe da sua observabilidade é o que evita o incidente que ninguém consegue explicar.

Compartilhar

Compartilhar

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly