Pods que executam um sidecar OpenVPN falham a resolução de nomes num padrão reconhecível: nomes do cluster penduram, nomes remotos respondem até o pod reiniciar, e o arquivo de resolver que você editou volta ao anterior. Nada nessa sequência é uma falha do túnel. Um pod tem um arquivo de resolver e dois mundos de nomes, e o arquivo pode listar servidores em ordem mas não pode expressar zonas. A correção duradoura é um resolver que roda dentro do pod e possui a divisão, então o arquivo continua trivial e nenhuma política tem motivo para reescrevê-lo.

Por que a edição que você fez continua desaparecendo

O arquivo pertence ao kubelet, não a você. Sob dnsPolicy: ClusterFirst o kubelet gera o arquivo para apontar ao DNS do cluster e o regenera sempre que um contêiner inicia. Tudo o que você muda via kubectl exec vive na camada gravável do contêiner, então reinício, evicção ou rollout descartam. A especificação do pod ainda carrega a mesma política, e é por isso que o mesmo arquivo volta.

Definir dnsPolicy: None interrompe a regeneração e também remove a resolução do cluster. Você ganha um arquivo que controla e perde os nomes de que precisava. Essa troca é o problema inteiro: resolv.conf não tem noção de zonas, apenas servidores tentados em ordem, então não pode enviar cluster.local a um endereço e uma zona privada a outro.

Execute um resolver no pod, em loopback

Aponte o pod para um único servidor de nomes em loopback, depois deixe um contêiner CoreDNS que compartilha o namespace de rede do pod responder na porta 53. Defina a política como None, dê ao pod esse único endereço e adicione o contêiner DNS ao lado da VPN e da aplicação:

spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 127.0.0.1
    options:
      - name: ndots
        value: "5"
  containers:
    - name: dns
      image: coredns/coredns
      args: ["-conf", "/etc/coredns/Corefile"]
      volumeMounts:
        - {name: corefile, mountPath: /etc/coredns, readOnly: true}
    - name: vpn
      image: your-openvpn-client-image
    - name: app
      image: your-app-image
  volumes:
    - name: corefile
      configMap:
        name: split-dns-corefile

Os três contêineres compartilham um namespace de rede, então 127.0.0.1:53 do contêiner da aplicação alcança o CoreDNS sem serviço, sem port-forward e sem desvio pela rede do cluster. Montar o Corefile de um ConfigMap o mantém como entrada versionada em vez de arquivo editado no local. O resolv.conf gerado então contém um servidor de nomes e nada mais, um estado sobre o qual dá para raciocinar.

Dois detalhes mordem aqui. A imagem oficial liga a porta 53 como root, e algumas políticas de cluster exigem que você escreva isso explicitamente. Todo contêiner do pod agora depende daquele único contêiner estar ativo, então adicione uma readiness probe contra um nome inofensivo e deixe a aplicação tolerar falhas de inicialização.

Dê a cada zona privada seu próprio upstream

Um Corefile é uma lista de zonas, cada uma nomeando o servidor que pode responder. Um bloco cobre a zona do túnel, e o bloco catch-all vem por último e leva tudo o que nenhum bloco explícito reivindicou. O catch-all deve apontar para um upstream real, nunca de volta ao pod: .:53 { forward . 10.100.0.10 } reutiliza o resolver do cluster porque ele já encaminha nomes públicos aos resolvers do próprio nó. Nada mais no pod precisa saber a qual rede um nome pertence.

.:53 {
  errors
  cache 30
  forward . 10.100.0.10
}

svc.acme.internal:53 {
  errors
  cache 30
  forward . 192.168.240.1
}

Leia o endereço do cluster do serviço kube-dns em kube-system em vez de copiar de um artigo, porque os CIDRs de serviço variam. O endereço do túnel vem do valor dhcp-option DNS que o servidor OpenVPN envia, ou do servidor autoritativo daquela zona. Se o resolver do cluster não responde a nomes públicos, coloque o endereço do resolver do nó no catch-all, lido no nó e não dentro do pod.

Não aponte o catch-all para /etc/resolv.conf, que é a linha única tentadora e uma armadilha nesta configuração exata. Sob dnsPolicy: None o arquivo do próprio pod contém o único servidor de nomes 127.0.0.1, então esse alvo é o próprio CoreDNS e toda consulta sem correspondência repete até expirar. Um bloco chamado svc.acme.internal casa com esse nome e todo nome abaixo dele, o que cobre db.svc.acme.internal e exclui svc.acme.internal.example.net. O ponto final é cosmético, porque nomes de zona são normalizados para a forma totalmente qualificada; a fronteira do sufixo decide onde a consulta cai.

Decida se o encaminhamento pertence ao resolver do cluster

Se toda carga de trabalho no namespace precisa da zona do túnel, um bloco de encaminhamento no ConfigMap do CoreDNS sob kube-system custa menos que um sidecar por pod. Essa mudança é em nível de cluster: exige direitos de administrador e reinício de um deployment do qual todos dependem. Limite-a àquela única zona, nunca à raiz. O endereço de túnel de uma carga de trabalho vira então o resolver de todo pod para a zona, e no dia em que o túnel cai o cluster inteiro perde o nome.

O sidecar mantém o raio de explosão dentro de um pod, que é o padrão melhor quando só alguns serviços precisam da rede remota. Seja o que for que escolher, anote qual resolver possui a zona, porque a próxima pessoa a depurar um timeout vai perguntar.

Nomes que resolvem enquanto as conexões ainda falham

A resolução é uma das duas coisas que o túnel precisa carregar; a outra é o caminho de volta, e a rede do pod o esconde bem. Se o próprio servidor OpenVPN roda como pod, as regras que você adiciona naquele contêiner não governam o tráfego que sai pelo nó. Essa falha é estreita: só os pods no nó que também rodava o servidor recebiam respostas, e todo o resto expirou.

Rodar o servidor com hostNetwork: true o coloca de volta no namespace do nó, onde uma regra FORWARD e a tradução de origem funcionam. Traduza o pool de clientes na saída, senão nenhum nó remoto tem rota de volta a um endereço de túnel como 10.8.0.6. Verifique os dois sentidos antes de tocar no Corefile outra vez. Um túnel que carrega a consulta mas não a resposta produz exatamente o mesmo timeout de um resolver que nunca recebeu a consulta.

Verifique a partir de um pod novo, não da sessão que você corrigiu

Pergunte às duas zonas explicitamente, depois repita a consulta após o pod ser substituído. Uma configuração que existe só num contêiner em execução não é configuração:

kubectl exec -it deploy/app -- dig @127.0.0.1 kubernetes.default.svc.cluster.local +short
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short
kubectl delete pod -l app=your-app
kubectl wait --for=condition=Ready pod -l app=your-app --timeout=90s
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short

Se o primeiro par responde e o par após a substituição não, o Corefile está certo e a especificação do pod não. Se ambos respondem e a aplicação ainda não conecta, pare de depurar DNS e teste o caminho TCP com nc -vz db.svc.acme.internal 5432 de dentro do contêiner. Guarde a saída do dig no ticket, porque é o artefato que separa um problema de nome de um problema de roteamento.

Artigos relacionados