Como dividir o DNS entre Kubernetes e un sidecar de OpenVPN
Un pod sidecar necesita dous mundos de nomes. Apunte un resolutor de loopback ao clúster e ao túnel, e deixe de depender dun resolv.conf editado.
Os pods que executan un sidecar OpenVPN fallan a resolución de nomes cun patrón recoñecible: os nomes do clúster colgan, os nomes remotos responden ata que o pod se reinicia, e o ficheiro de resolutor que editou volve atrás. Nada nesa secuencia é un fallo do túnel. Un pod ten un ficheiro de resolutor e dous mundos de nomes, e o ficheiro pode listar servidores en orde pero non pode expresar zonas. A solución duradeira é un resolutor que se executa dentro do pod e posúe a diversión, de xeito que o ficheiro queda trivial e ningunha política ten motivo para reescribilo.
Por que a edición que fixo segue desaparecendo
O ficheiro pertence ao kubelet, non a vostede. Con dnsPolicy: ClusterFirst o kubelet xérao para apuntar ao DNS do clúster e rexérao cada vez que arranca un contedor. Todo o que cambie mediante kubectl exec vive na capa de escritura do contedor, así que un reinicio, un desaloxo ou un despregamento descártao. A especificación do pod segue levando a mesma política, por iso volve o mesmo ficheiro.
Pór dnsPolicy: None detén a xeración e tamén elimina a resolución do clúster. Gana un ficheiro que controla e perde os nomes que necesitaba. Ese intercambio é todo o problema: resolv.conf non ten noción de zonas, só de servidores que probar en orde, así que non pode enviar cluster.local a un enderezo e unha zona privada a outro.
Execute un resolutor no pod, en loopback
Apunte o pod a un único servidor de nomes en loopback e despois deixe que un contedor CoreDNS que comparte o espazo de nomes de rede do pod responda no porto 53. Fixe a política en None, dea ao pod ese único enderezo e engada o contedor DNS xunto ao VPN e á aplicación:
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-corefileOs tres contedores comparten un único espazo de nomes de rede, así que 127.0.0.1:53 desde o contedor da aplicación chega a CoreDNS sen servizo, sen port-forward e sen rodeo pola rede do clúster. Montar o Corefile desde un ConfigMap manteno como unha entrada versionada en lugar dun ficheiro editado no sitio. O resolv.conf xerado contén entón un só servidor de nomes e nada máis, que é un estado sobre o que pode razoar.
Dous detalles morden aquí. A imaxe oficial liga o porto 53 como root, e algunhas políticas do clúster obrigan a escribilo explicitamente. Cada contedor do pod agora depende de que ese único contedor estea en marcha, así que engada unha sonda de dispoñibilidade contra un nome inofensivo e deixe que a aplicación tolere fallos de arranque.
Dea a cada zona privada o seu propio upstream
Un Corefile é unha lista de zonas, cada unha nomea o servidor que pode respondela. Un bloque cobre a zona do túnel, e o bloque que o atrapa todo vai ao final e toma todo o que ningún bloque explícito reclamou. O bloque que o atrapa todo debe apuntar a un upstream real, nunca de volta ao pod: .:53 { forward . 10.100.0.10 } reutiliza o resolutor do clúster porque ese resolutor xa reenvía os nomes públicos aos resolutores do propio nodo. Nada máis no pod necesita saber a que rede pertence un nome.
.:53 {
errors
cache 30
forward . 10.100.0.10
}
svc.acme.internal:53 {
errors
cache 30
forward . 192.168.240.1
}Lea o enderezo do clúster do servizo kube-dns en kube-system en lugar de copiar un dun artigo, porque os CIDR de servizo difiren. O enderezo do túnel vén do valor dhcp-option DNS que envía o servidor OpenVPN, ou do servidor autoritativo desa zona. Se o seu resolutor do clúster non responde nomes públicos, poña o enderezo do resolutor do nodo no bloque que o atrapa todo, lido no nodo e non dentro do pod.
Non apunte o bloque que o atrapa todo a /etc/resolv.conf, que é a liña única tentadora e unha trampa nesta configuración exacta. Con dnsPolicy: None o ficheiro do propio pod contén o único servidor de nomes 127.0.0.1, así que ese obxectivo é o propio CoreDNS e cada consulta non coincidente recorre ata esgotarse. Un bloque chamado svc.acme.internal coincide con ese nome e con cada nome debaixo, o que cobre db.svc.acme.internal e exclúe svc.acme.internal.example.net. O punto final é cosmético, porque os nomes de zona normalízanse á forma plenamente cualificada; o límite do sufixo é o que decide onde cae unha consulta.
Decida se o reenvío pertence ao resolutor do clúster
Se cada carga de traballo do espazo de nomes necesita a zona do túnel, un bloque de reenvío no ConfigMap de CoreDNS baixo kube-system é máis barato que un sidecar por pod. Ese cambio é de alcance de clúster: require dereitos de administrador e un reinicio dun despregamento do que todos dependen. Limíteo a esa única zona, nunca á raíz. O enderezo do túnel dunha soa carga de traballo convértese entón no resolutor de cada pod para a zona, e o día que cae o túnel, todo o clúster perde o nome.
O sidecar mantén o radio de explosión dentro dun só pod, que é o mellor valor por defecto cando só uns poucos servizos necesitan a rede remota. Escolla o que escolla, anote que resolutor é dono da zona, porque a seguinte persoa que depure un tempo de espera preguntarao.
Nomes que se resolven mentres as conexións aínda fallan
A resolución é unha das dúas cousas que o túnel debe levar; a outra é o camiño de volta, e a rede do pod escóndeo ben. Se o propio servidor OpenVPN se executa como pod, as regras que engada dentro dese contedor non gobernan o tráfico que sae polo nodo. Ese fallo é estreito: só os pods do nodo que tamén executaba o servidor obtiveron respostas, e todo o demais expirou.
Executar o servidor con hostNetwork: true devólveo ao espazo de nomes do nodo, onde unha regra FORWARD e a tradución de orixe teñen efecto. Traduza o pool de clientes á saída, ou ningún nodo remoto terá ruta de volta a un enderezo de túnel como 10.8.0.6. Comprobe os dous sentidos antes de tocar de novo o Corefile. Un túnel que leva a consulta e non a resposta produce exactamente o mesmo tempo de espera que un resolutor que nunca recibiu a consulta.
Verifique desde un pod novo, non desde a sesión que arranxou
Pregunte explicitamente por ambas as zonas e despois repita a consulta tras substituír o pod. A configuración que só existe nun contedor en marcha non é configuración:
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 +shortSe o primeiro par responde e o par tras a substitución non, o Corefile está ben e a especificación do pod non. Se ambos responden e a aplicación aínda non pode conectar, deixe de depurar DNS e probe a ruta TCP con nc -vz db.svc.acme.internal 5432 desde dentro do contedor. Garde a saída de dig no tícket, porque é o artefacto que separa un problema de nomes dun problema de encamiñamento.