Pods, die einen OpenVPN-Sidecar ausführen, scheitern bei der Namensauflösung nach einem erkennbaren Muster: Clusternamen hängen, entfernte Namen antworten, bis der Pod neu startet, und Ihre bearbeitete Resolver-Datei springt zurück. Nichts in dieser Abfolge ist ein Tunneldefekt. Ein Pod hat eine Resolver-Datei und zwei Namenswelten, und die Datei kann Server in Reihenfolge auflisten, aber keine Zonen ausdrücken. Die dauerhafte Lösung ist ein Resolver, der im Pod läuft und die Aufteilung besitzt, sodass die Datei trivial bleibt und keine Richtlinie einen Grund hat, sie neu zu schreiben.

Warum die Änderung, die Sie vorgenommen haben, immer wieder verschwindet

Die Datei gehört dem kubelet, nicht Ihnen. Unter dnsPolicy: ClusterFirst erzeugt das kubelet sie so, dass sie auf Cluster-DNS zeigt, und erzeugt sie neu, wann immer ein Container startet. Alles, was Sie über kubectl exec ändern, lebt in der beschreibbaren Schicht des Containers, sodass ein Neustart, eine Eviction oder ein Rollout es verwirft. Die Pod-Spezifikation trägt weiterhin dieselbe Richtlinie, weshalb dieselbe Datei zurückkommt.

Das Setzen von dnsPolicy: None stoppt die Neuerzeugung und entfernt auch die Cluster-Auflösung. Sie gewinnen eine Datei, die Sie kontrollieren, und verlieren die Namen, die Sie brauchten. Dieser Tausch ist das ganze Problem: resolv.conf kennt keine Zonen, nur Server, die der Reihe nach versucht werden, sodass es cluster.local nicht an eine Adresse und eine private Zone an eine andere senden kann.

Betreiben Sie einen Resolver im Pod, auf Loopback

Richten Sie den Pod auf einen einzigen Namensserver auf Loopback, und lassen Sie dann einen CoreDNS-Container, der den Netzwerk-Namespace des Pods teilt, auf Port 53 antworten. Setzen Sie die Richtlinie auf None, geben Sie dem Pod diese eine Adresse und fügen Sie den DNS-Container neben dem VPN und der Anwendung hinzu:

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 drei Container teilen einen Netzwerk-Namespace, sodass 127.0.0.1:53 aus dem Anwendungscontainer CoreDNS ohne Service, ohne Port-Forward und ohne Umweg über das Clusternetz erreicht. Das Einbinden der Corefile aus einer ConfigMap hält sie als versionierte Eingabe statt als an Ort und Stelle bearbeitete Datei. Die erzeugte resolv.conf enthält dann einen Namensserver und sonst nichts, ein Zustand, über den Sie nachdenken können.

Zwei Details beißen hier. Das offizielle Image bindet Port 53 als root, und manche Cluster-Richtlinien verlangen, dass Sie das ausdrücklich hinschreiben. Jeder Container im Pod hängt jetzt davon ab, dass dieser eine Container läuft, also fügen Sie eine Readiness-Probe gegen einen harmlosen Namen hinzu und lassen Sie die Anwendung Startfehler tolerieren.

Geben Sie jeder privaten Zone ihren eigenen Upstream

Eine Corefile ist eine Liste von Zonen, die jeweils den Server benennen, der sie beantworten kann. Ein Block deckt die Tunnelzone ab, und der Catch-all kommt zuletzt und nimmt alles, was kein expliziter Block beansprucht hat. Der Catch-all muss auf einen echten Upstream zeigen, niemals zurück auf den Pod: .:53 { forward . 10.100.0.10 } verwendet den Cluster-Resolver wieder, weil dieser bereits öffentliche Namen an die Resolver des Nodes weiterleitet. Sonst muss nichts im Pod wissen, zu welchem Netz ein Name gehört.

.:53 {
  errors
  cache 30
  forward . 10.100.0.10
}

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

Lesen Sie die Cluster-Adresse vom kube-dns-Service in kube-system, statt eine aus einem Artikel zu kopieren, weil Service-CIDRs unterschiedlich sind. Die Tunneladresse stammt aus dem dhcp-option DNS-Wert, den der OpenVPN-Server übermittelt, oder vom autoritativen Server für diese Zone. Wenn Ihr Cluster-Resolver keine öffentlichen Namen beantwortet, setzen Sie die Resolver-Adresse des Nodes in den Catch-all, gelesen auf dem Node und nicht im Pod.

Richten Sie den Catch-all nicht auf /etc/resolv.conf, das ist der verlockende Einzeiler und in genau diesem Aufbau eine Falle. Unter dnsPolicy: None enthält die Datei des Pods selbst den einzigen Namensserver 127.0.0.1, also ist dieses Ziel CoreDNS selbst, und jede nicht passende Abfrage rekursiert, bis sie abläuft. Ein Block namens svc.acme.internal passt auf diesen Namen und jeden Namen darunter, was db.svc.acme.internal abdeckt und svc.acme.internal.example.net ausschließt. Der abschließende Punkt ist kosmetisch, weil Zonennamen in vollqualifizierte Form normalisiert werden; die Suffixgrenze entscheidet, wo eine Abfrage landet.

Entscheiden Sie, ob die Weiterleitung in den Cluster-Resolver gehört

Wenn jede Workload im Namespace die Tunnelzone braucht, ist ein Weiterleitungsblock in der CoreDNS-ConfigMap unter kube-system billiger als ein Sidecar pro Pod. Diese Änderung ist clustervweit: Sie braucht Administratorrechte und einen Neustart eines Deployments, von dem alle abhängen. Beschränken Sie sie auf diese eine Zone, niemals auf die Wurzel. Die Tunneladresse einer einzelnen Workload wird dann zum Resolver jedes Pods für die Zone, und an dem Tag, an dem der Tunnel ausfällt, verliert der ganze Cluster den Namen.

Der Sidecar hält den Wirkungsradius in einem Pod, was die bessere Voreinstellung ist, wenn nur wenige Dienste das entfernte Netz brauchen. Was auch immer Sie wählen, schreiben Sie auf, welcher Resolver die Zone besitzt, denn die nächste Person, die einen Timeout debuggt, wird danach fragen.

Namen, die auflösen, während Verbindungen noch scheitern

Die Auflösung ist eines der beiden Dinge, die der Tunnel tragen muss; das andere ist der Rückweg, und das Pod-Netz verbirgt ihn gut. Wenn der OpenVPN-Server selbst als Pod läuft, regeln Regeln, die Sie in diesem Container hinzufügen, keinen Verkehr, der über den Node hinausgeht. Dieser Fehler ist eng: Nur Pods auf dem Node, der auch den Server ausführte, bekamen Antworten, und alles andere lief in einen Timeout.

Der Server mit hostNetwork: true läuft wieder im Namespace des Nodes, wo eine FORWARD-Regel und Quellübersetzung wirken. Übersetzen Sie den Client-Pool beim Hinausgehen, sonst hat kein entfernter Node eine Route zurück zu einer Tunneladresse wie 10.8.0.6. Prüfen Sie beide Richtungen, bevor Sie die Corefile erneut anfassen. Ein Tunnel, der die Abfrage und nicht die Antwort trägt, erzeugt genau denselben Timeout wie ein Resolver, der die Abfrage nie erhalten hat.

Prüfen Sie aus einem neuen Pod, nicht aus der Sitzung, die Sie repariert haben

Fragen Sie beide Zonen ausdrücklich ab, und wiederholen Sie die Abfrage, nachdem der Pod ersetzt wurde. Konfiguration, die nur in einem laufenden Container existiert, ist keine 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 +short

Wenn das erste Paar antwortet und das Paar nach dem Ersetzen nicht, ist die Corefile in Ordnung und die Pod-Spezifikation nicht. Wenn beide antworten und die Anwendung sich trotzdem nicht verbinden kann, hören Sie auf, DNS zu debuggen, und testen Sie den TCP-Pfad mit nc -vz db.svc.acme.internal 5432 von innerhalb des Containers. Behalten Sie die dig-Ausgabe im Ticket, denn sie ist das Artefakt, das ein Namensproblem von einem Routingproblem trennt.

Ähnliche Artikel