A regra, em uma linha
Todo Service do Kubernetes ganha um nome de DNS com quatro pedaços fixos, sempre na mesma ordem:
api.producao.svc.cluster.local
│ │ │ └── domínio do cluster
│ │ └────── tipo de registro (svc = Service, pod = Pod)
│ └─────────────── namespace
└─────────────────── nome do Service
É só isso. O problema nunca foi decorar essa linha — é que quase nunca você precisa dela inteira, e a forma curta que funciona muda conforme de onde você chama. É aí que a coisa vira mistério.
Por que curl api funciona aqui e quebra ali
Abra um pod qualquer e leia o /etc/resolv.conf. Você vai ver
algo assim — e este arquivo é a resposta para 90% dos casos de “o DNS não
resolve”:
search producao.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
A linha search é uma lista de sufixos que o resolver
experimenta quando o nome que você digitou não é absoluto. Repare
que o primeiro sufixo carrega o namespace do pod que está
chamando — não o do serviço que você quer.
Por isso curl api de dentro do namespace producao
acha na primeira tentativa: vira api.producao.svc.cluster.local.
E por isso o mesmo curl api, de um pod no namespace
web, testa api.web.svc.cluster.local,
api.svc.cluster.local, api.cluster.local, desiste
e devolve NXDOMAIN. O serviço está lá, no ar, saudável — só que ninguém
perguntou por ele.
servico.namespace.
O que é ndots:5 — e o custo escondido
O ndots decide a ordem das tentativas. A regra: se o
nome digitado tiver menos pontos que o ndots, o resolver
tenta primeiro colando cada sufixo do search, e só depois
tenta o nome como absoluto. Se tiver pontos igual ou mais, ele inverte —
tenta absoluto primeiro.
O Kubernetes usa ndots:5, um valor bem alto, para que formas
curtas como api.producao (1 ponto) e
api.producao.svc (2 pontos) continuem funcionando. A conta
fecha para dentro do cluster. Para fora, ela cobra caro:
| Você chama | Pontos | Consultas até a resposta |
|---|---|---|
api (mesmo namespace) | 0 | 1 — acerta de primeira |
api.producao | 1 | 2 |
api.github.com | 2 | 4 — três erram antes |
api.producao.svc.cluster.local | 4 | 4 — ainda passa pelo search |
api.producao.svc.cluster.local. | — | 1 — o ponto final corta tudo |
É a mesma conta que o simulador acima faz: cada linha ali é um round-trip até o CoreDNS.
As duas saídas
A primeira é o ponto final: um nome terminado em ponto é
absoluto, e o resolver não encosta no search. O efeito disso
na latência, com a conta feita, está em
rota
interna vs externa no Kubernetes.
A segunda é baixar o ndots só no pod que precisa — e essa não
está no artigo:
spec:
dnsConfig:
options:
- name: ndots
value: "2"
ndots:2, a forma
api.producao.svc (2 pontos) passa a ser tentada como absoluta
primeiro e só depois pelo search — e api.producao (1 ponto)
continua funcionando. Quem quebra é quem depende de forma com 2 pontos ou
mais dentro do cluster. Se a sua aplicação só chama servico ou
servico.namespace, é seguro.
As variantes que ninguém decora
Headless Service
Um Service com clusterIP: None não tem IP próprio. O nome
dele devolve os IPs de todos os pods por trás, em vez de
um IP virtual único. É o que você usa quando o cliente precisa enxergar as
réplicas individualmente — banco em cluster, fila, qualquer coisa que faça
o próprio balanceamento.
Pod de StatefulSet
É aqui que quase todo mundo trava. Pod de StatefulSet ganha nome estável e individual, e o padrão inclui o Service que governa o conjunto:
api-0.api.producao.svc.cluster.local
│ │ └── namespace
│ └────── nome do serviceName do StatefulSet
└──────────── nome do pod, com o ordinal
Repare que o Service aparece duas vezes na prática: uma como pai
do pod, outra como o próprio Service. E isso só funciona se o
serviceName do StatefulSet apontar para um Service
headless — com ClusterIP normal, o nome do pod não é
publicado.
Registro SRV, para porta nomeada
Se a porta do Service tem nome, existe um registro SRV que devolve porta e host juntos — útil para cliente que descobre a porta em vez de assumi-la:
_http._tcp.api.producao.svc.cluster.local
ExternalName
Um Service do tipo ExternalName não tem pod nenhum: ele vira
um CNAME para um endereço de fora. Serve para dar um nome interno estável a
algo externo — e trocar o destino sem mexer na aplicação.
Quando não resolve: o roteiro
Nesta ordem, que vai do mais comum para o mais raro:
1. Confira de onde você está chamando. Metade dos casos morre aqui — é o namespace errado na forma curta.
kubectl exec -it meu-pod -n web -- cat /etc/resolv.conf
2. Pergunte de dentro do cluster, não da sua máquina. Um pod descartável resolve a dúvida:
kubectl run -it --rm dbg --image=busybox:1.36 --restart=Never \
-n web -- nslookup api.producao.svc.cluster.local
3. Veja se o Service tem endpoint. Este é o caso cruel:
o DNS resolve certinho, devolve um IP, e nada responde — porque o
selector do Service não casa com o label de nenhum pod.
kubectl get endpoints api -n producao
# ENDPOINTS vazio ou <none> = o seletor não pegou ninguém
4. Só então desconfie do CoreDNS. Ele quase nunca é o culpado, mas quando é, aparece no log:
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
E se o domínio do cluster não for cluster.local
Ele é o padrão, mas não é lei — quem sobe o cluster pode trocar pela flag
--cluster-domain do kubelet, e alguns provedores gerenciados
fazem isso. Se o seu for diferente, todo o resto continua igual: só o
último pedaço muda. A ferramenta lá em cima aceita o sufixo que você usar.
Quer o passo anterior — quando usar endereço interno e quando expor pelo Ingress? Está em rota interna vs externa no Kubernetes.