Så delar du DNS mellan Kubernetes och en OpenVPN-sidecar
En sidecar-pod behöver två namnärvärldar. Rikta en loopback-resolver mot klustret och tunneln och sluta förlita dig på en redigerad resolv.conf.
Poddar som kör en OpenVPN-sidecar misslyckas med namnmatchning i ett igenkänneligt mönster: klusternamn hänger, fjärrnamn svarar tills podden startas om, och resolverfilen du redigerade går tillbaka. Ingenting i den sekvensen är ett tunnelfel. En pod har en resolverfil och två namnärvärldar, och filen kan lista servrar i ordning men inte uttrycka zoner. Den hållbara lösningen är en resolver som kör i podden och äger uppdelningen, så filen förblir enkel och ingen policy har skäl att skriva om den.
Varför ändringen du gjorde ständigt försvinner
Filen tillhör kubelet, inte dig. Under dnsPolicy: ClusterFirst genererar kubelet den för att peka på klustrets DNS och genererar om den varje gång en container startar. Allt du ändrar via kubectl exec lever i containerns skrivbara lager, så omstart, värkning eller rollout kastar det. Podspecen bär fortfarande samma policy, och därför kommer samma fil tillbaka.
Att sätta dnsPolicy: None stoppar omgenereringen och tar också bort klusteruppslagningen. Du får en fil du kontrollerar och förlorar namnen du behövde. Den bytet är hela problemet: resolv.conf har inget begrepp om zoner, bara servrar som provas i ordning, så den kan inte skicka cluster.local till en adress och en privat zon till en annan.
Kör en resolver i podden, på loopback
Rikta podden mot en enda namnserver på loopback och låt sedan en CoreDNS-container som delar poddens nätverksnamnrymd svara på port 53. Sätt policyn till None, ge podden den enda adressen och lägg till DNS-containern vid sidan av VPN:en och applikationen:
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-corefileAlla tre containrarna delar en nätverksnamnrymd, så 127.0.0.1:53 från applikationscontainern når CoreDNS utan tjänst, utan port-forward och utan omväg genom klusternätet. Att montera Corefile från en ConfigMap håller den som versionshanterad indata i stället för en fil som redigeras på plats. Den genererade resolv.conf innehåller då en namnserver och inget annat, ett tillstånd du kan resonera om.
Två detaljer biter här. Den officiella imagen binder port 53 som root, och vissa klusterpolicyer kräver att du skriver det explicit. Varje container i podden beror nu på att den enda containern kör, så lägg till en readiness-probe mot ett ofarligt namn och låt applikationen tåla startfel.
Ge varje privat zon sin egen upstream
En Corefile är en lista över zoner, var och en namnger servern som kan svara på den. Ett block täcker tunnelzonen, och catch-all-blocket kommer sist och tar allt ingen explicit block har gjort anspråk på. Catch-all måste peka på en riktig upstream, aldrig tillbaka till podden: .:53 { forward . 10.100.0.10 } återanvänder klusterresolvern eftersom den redan vidarebefordrar publika namn till nodens egna resolvrar. Inget annat i podden behöver veta vilket nät ett namn tillhör.
.:53 {
errors
cache 30
forward . 10.100.0.10
}
svc.acme.internal:53 {
errors
cache 30
forward . 192.168.240.1
}Läs klusteradressen från kube-dns-tjänsten i kube-system i stället för att kopiera en från en artikel, eftersom tjänstens CIDR skiljer sig. Tunneladressen kommer från dhcp-option DNS-värdet som OpenVPN-servern skickar, eller från den auktoritativa servern för zonen. Om din klusterresolver inte svarar på publika namn, lägg nodens resolveradress i catch-all-blocket, läst på noden och inte i podden.
Peka inte catch-all mot /etc/resolv.conf, vilket är den frestande enradaren och en fälla i exakt den här uppsättningen. Under dnsPolicy: None innehåller poddens egen fil den enda namnservern 127.0.0.1, så målet är CoreDNS själv och varje icke-matchande fråga upprepas tills den löper ut. Ett block som heter svc.acme.internal matchar det namnet och varje namn under det, vilket täcker db.svc.acme.internal och utesluter svc.acme.internal.example.net. Punkten i slutet är kosmetisk, eftersom zonnamn normaliseras till fullt kvalificerad form; suffixgränsen avgör var en fråga landar.
Avgör om vidarebefordran hör till klusterresolvern
Om varje arbetsbelastning i namnrymden behöver tunnelzonen är ett vidarebefordringsblock i CoreDNS ConfigMap under kube-system billigare än en sidecar per pod. Den ändringen är på klusternivå: den kräver administratörsrättigheter och omstart av en deployment som alla är beroende av. Begränsa den till den enda zonen, aldrig till roten. Tunneladressen för en arbetsbelastning blir då resolvern för varje pod för zonen, och dagen tunneln faller förlorar hela klustret namnet.
Sidecaren håller sprängradien inom en pod, vilket är en bättre standard när bara några få tjänster behöver fjärrnätet. Vad du än väljer, notera vilken resolver som äger zonen, eftersom nästa person som felsöker en timeout kommer att fråga.
Namn som löses medan anslutningar fortfarande misslyckas
Uppslagning är en av två saker som tunneln måste bära; den andra är returvägen, och podnätet döljer den väl. Om OpenVPN-servern själv kör som en pod styr inte reglerna du lägger till i den containern trafiken som går ut via noden. Felet är smalt: bara poddarna på noden som också körde servern fick svar, och allt annat löpte ut.
Att köra servern med hostNetwork: true sätter den tillbaka i nodens namnrymd, där en FORWARD-regel och källöversättning verkar. Översätt klientpoolen på väg ut, annars har ingen fjärrnod en rutt tillbaka till en tunneladress som 10.8.0.6. Kontrollera båda riktningarna innan du rör Corefile igen. En tunnel som bär frågan men inte svaret ger exakt samma timeout som en resolver som aldrig fick frågan.
Verifiera från en ny pod, inte från sessionen du reparerade
Fråga båda zonerna explicit och upprepa sedan frågan efter att podden bytts ut. En konfiguration som bara finns i en körande container är inte en konfiguration:
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 +shortOm det första paret svarar och paret efter utbytet inte gör det, är Corefile korrekt och podspecen inte. Om båda svarar och applikationen fortfarande inte kan ansluta, sluta felsöka DNS och testa TCP-vägen med nc -vz db.svc.acme.internal 5432 inifrån containern. Spara dig-utdata i ärendet, eftersom det är artefakten som skiljer ett namnproblem från ett routingproblem.