Pods met een OpenVPN-sidecar falen in naamresolutie met een herkenbaar patroon: clusternamen blijven hangen, externe namen antwoorden tot de pod opnieuw start, en het resolverbestand dat je aanpaste springt terug. Niets in die reeks is een tunnelstoring. Een pod heeft één resolverbestand en twee naamwerelden, en het bestand kan servers in volgorde noemen maar geen zones uitdrukken. De blijvende oplossing is een resolver die in de pod draait en de splitsing bezit, zodat het bestand eenvoudig blijft en geen enkel beleid reden heeft het te herschrijven.

Waarom de wijziging die je maakte steeds verdwijnt

Het bestand is van de kubelet, niet van jou. Onder dnsPolicy: ClusterFirst genereert de kubelet het om naar cluster-DNS te wijzen, en genereert het opnieuw telkens wanneer een container start. Alles wat je via kubectl exec wijzigt, leeft in de schrijfbare laag van de container, dus een herstart, evictie of rollout gooit het weg. De podspecificatie draagt nog steeds hetzelfde beleid, en daarom komt hetzelfde bestand terug.

dnsPolicy: None instellen stopt de regeneratie en haalt ook clusterresolutie weg. Je krijgt een bestand dat je beheert en verliest de namen die je nodig had. Die ruil is het hele probleem: resolv.conf heeft geen begrip van zones, alleen servers die op volgorde geprobeerd worden, dus het kan cluster.local niet naar het ene adres sturen en een privézone naar het andere.

Draai één resolver in de pod, op loopback

Wijs de pod naar één nameserver op loopback, en laat dan een CoreDNS-container die de netwerknaamruimte van de pod deelt antwoorden op poort 53. Zet het beleid op None, geef de pod dat ene adres, en voeg de DNS-container toe naast de VPN en de applicatie:

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

Alle drie containers delen één netwerknaamruimte, dus 127.0.0.1:53 uit de applicatiecontainer bereikt CoreDNS zonder service, zonder port-forward en zonder omweg via het clusternetwerk. De Corefile uit een ConfigMap mounten houdt hem als geversioneerde input in plaats van een bestand dat ter plaatse wordt bewerkt. De gegenereerde resolv.conf bevat dan één nameserver en niets anders, een toestand waarover je kunt redeneren.

Twee details bijten hier. De officiële image bindt poort 53 als root, en sommige clusterbeleidsregels eisen dat je dat expliciet schrijft. Elke container in de pod hangt nu af van die ene container die draait, dus voeg een readiness-probe toe op een onschuldige naam en laat de applicatie opstartfouten verdragen.

Geef elke privézone zijn eigen upstream

Een Corefile is een lijst zones, elk noemt de server die erop kan antwoorden. Één blok dekt de tunnelzone, en het catch-all-blok komt laatst en neemt alles wat geen expliciet blok claimt. De catch-all moet naar een echte upstream wijzen, nooit terug naar de pod: .:53 { forward . 10.100.0.10 } hergebruikt de clusterresolver omdat die al publieke namen doorstuurt naar de resolvers van de node zelf. Niets anders in de pod hoeft te weten bij welk netwerk een naam hoort.

.:53 {
  errors
  cache 30
  forward . 10.100.0.10
}

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

Lees het clusteradres van de kube-dns-service in kube-system in plaats van er een uit een artikel te kopiëren, omdat service-CIDR's verschillen. Het tunneladres komt uit de dhcp-option DNS-waarde die de OpenVPN-server pusht, of van de autoritatieve server voor die zone. Als je clusterresolver geen publieke namen beantwoordt, zet dan het resolveradres van de node in het catch-all-blok, gelezen op de node en niet in de pod.

Wijs de catch-all niet naar /etc/resolv.conf, dat is de verleidelijke oneliner en een val in precies deze opzet. Onder dnsPolicy: None bevat het bestand van de pod zelf de enige nameserver 127.0.0.1, dus dat doel is CoreDNS zelf en elke niet-passende query herhaalt zich tot de time-out. Een blok met de naam svc.acme.internal matcht die naam en elke naam eronder, wat db.svc.acme.internal dekt en svc.acme.internal.example.net uitsluit. De punt aan het eind is cosmetisch, omdat zonnenamen naar volledig gekwalificeerde vorm worden genormaliseerd; de suffixgrens bepaalt waar een query landt.

Bepaal of het doorsturen bij de clusterresolver hoort

Als elke workload in de naamruimte de tunnelzone nodig heeft, is een doorstuurb lok in de CoreDNS ConfigMap onder kube-system goedkoper dan een sidecar per pod. Die wijziging is op clusterniveau: hij vereist beheerdersrechten en een herstart van een deployment waar iedereen van afhangt. Beperk hem tot die ene zone, nooit tot de root. Het tunneladres van één workload wordt dan de resolver van elke pod voor de zone, en op de dag dat de tunnel valt verliest het hele cluster de naam.

De sidecar houdt de explosieradius binnen één pod, wat de betere standaard is wanneer slechts enkele services het externe netwerk nodig hebben. Wat je ook kiest, noteer welke resolver de zone bezit, want de volgende persoon die een time-out debugt zal het vragen.

Namen die oplossen terwijl verbindingen nog falen

Resolutie is een van de twee dingen die de tunnel moet dragen; het andere is de terugweg, en podnetwerken verbergen die goed. Als de OpenVPN-server zelf als pod draait, beheersen de regels die je in die container toevoegt het verkeer dat via de node naar buiten gaat niet. Die fout is smal: alleen pods op de node die ook de server draaide kregen antwoorden, en al het andere liep af.

De server met hostNetwork: true draaien zet hem terug in de naamruimte van de node, waar een FORWARD-regel en brontranslatie werken. Vertaal de clientpool bij het naar buiten gaan, anders heeft geen enkele externe node een route terug naar een tunneladres zoals 10.8.0.6. Controleer beide richtingen voordat je de Corefile weer aanraakt. Een tunnel die de query draagt maar niet het antwoord geeft precies dezelfde time-out als een resolver die de query nooit ontving.

Verifieer vanuit een nieuwe pod, niet vanuit de sessie die je repareerde

Vraag beide zones expliciet, en herhaal de query daarna nadat de pod is vervangen. Een configuratie die alleen in een draaiende container bestaat is geen configuratie:

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

Als het eerste paar antwoordt en het paar na de vervanging niet, is de Corefile in orde en de podspecificatie niet. Als beide antwoorden en de applicatie nog steeds geen verbinding krijgt, stop dan met DNS debuggen en test het TCP-pad met nc -vz db.svc.acme.internal 5432 van binnenuit de container. Bewaar de dig-uitvoer in de ticket, want dat is het artefact dat een naamprobleem van een routingprobleem scheidt.

Gerelateerde artikelen