Container erreicht die reine VPN-API nicht? Proxy auf dem Host
Der Tunnel lebt auf dem Host, daher hat der Container keine Route zur privaten Adresse. Veröffentlichen Sie einen schmalen Reverse-Proxy und rufen Sie ihn als host.docker.internal auf.
Ihr Compose-Dienst läuft in eine Zeitüberschreitung gegen eine API, die der Browser auf derselben Maschine ohne Beschwerden öffnet. Dieser Unterschied ist der Hinweis: Die Anfrage, die funktioniert, verlässt einen Host mit VPN-Tunnel, und die Anfrage, die hängt, verlässt einen Container mit eigenem Netzwerk-Stack. Nichts im Container kennt die Routen des Tunnels, daher bleibt das private Ziel unerreichbar, wie der Name auch geschrieben wird. Die Lösung ist ein bewusster Sprung über den Host, keine klügere Routing-Tabelle.
Beginnen Sie damit, warum sich die beiden Wege unterscheiden, und entscheiden Sie dann, wo der Tunnel leben soll.
Warum der Host funktioniert und der Container nicht
Der Aufbau des Tunnels gibt dem Host eine Schnittstelle, eine Reihe von Routen und meist einen Resolver-Eintrag, den das VPN bereitstellt. Ein Container erbt nichts davon: Er startet mit einer Schnittstelle auf der Bridge der Engine und einer Standardroute, die auf diese Bridge zeigt. Auf dem Host installierte Routen sind für alles unsichtbar, was in diesem Namensraum läuft.
Deshalb wirken die Symptome inkonsistent statt einfach kaputt. Der Host löst den privaten Namen auf und verbindet sich, weil Name und Route dort beide existieren. Der Container schickt sein Paket an eine private Adresse, die nichts auf dem Bridge-Weg erreichen kann, und die Anfrage läuft in die Zeitüberschreitung. Den Namen in die hosts-Datei des Containers einzutragen ändert nichts, weil die Adresse selbst von dort, wo der Container sitzt, unerreichbar ist.
Den Host von innen im Container erreichen
Ein Prozess im Container, der localhost wählt, wählt den Container. Docker Desktop veröffentlicht host.docker.internal als Namen, der auf die Maschine zurückzeigt, die die Engine ausführt; Linux-Engines lassen ihn undefiniert, sofern der Dienst ihn nicht anfordert, und genau das tut die Zuordnung unten. Damit zielt die Anwendung auf einen Namen und einen Port statt auf die private Adresse.
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
INTERNAL_API_HOST: "host.docker.internal:8666"Der Tunnel bleibt auf der Host-Seite des Sprungs, und der Port in dieser Variable ist der, auf dem der Proxy lauschen wird. Ein Container, der den Tunnel verliert, scheitert nun an einem einzigen bekannten Sprung, statt eine Route zu versuchen, die er nie hatte.
Wählen Sie, wo der Tunnel lebt
Für einen Compose-Dienst, der eine private API braucht, sind drei Formen möglich, und sie unterscheiden sich darin, welcher Prozess den Tunnel besitzt. Ein VPN-Client kann im Container-Image laufen, was das Bereitstellen eines Tunnelgeräts und die Vergabe der dazugehörigen Netzwerkadministrationsfähigkeit erfordert. Die Anwendung kann den Netzwerk-Namensraum eines separaten Tunnel-Containers teilen, sodass beide Prozesse einen Stack und eine Routing-Tabelle sehen. Oder der Tunnel bleibt auf dem Host, mit einem schmalen Weiterleiter vom Host in den Container.
Die ersten beiden ziehen Profildateien, Wiederverbindungsverhalten und Resolver-Richtlinie in das Projekt und machen jeden Dienst in der Datei vom Zustand des Tunnels abhängig. Das ist viel Kopplung für einen einzigen API-Aufruf. Für einen Entwicklungs-Stack, der einen einzigen internen Endpunkt liest, ist der Weiterleiter auf der Host-Seite die kleinste Änderung, die die Zugangsdaten dennoch dort lässt, wo das Plattformteam sie verwaltet.
Veröffentlichen Sie einen schmalen Pfad auf dem Host
Beginnen Sie mit dem, was der Weiterleiter erbt: Führen Sie ihn unter dem Konto aus, dem der Tunnel gehört, und seine ausgehenden Sockets übernehmen die Routen des Hosts und den Resolver des Hosts. Ein nginx-location-Block deckt einen Pfad ab.
server {
listen 8666;
location /search/ {
proxy_pass http://10.20.30.40:8090/;
proxy_set_header Host internal-api.example;
proxy_read_timeout 60s;
}
}Behandeln Sie Adresse und Port als Platzhalter und setzen Sie das Paar ein, auf dem die API tatsächlich lauscht. Jeder weitere Upstream verdient einen eigenen location-Block, und jeder Block sollte nur die Pfade zulassen, die dieser Dienst aufruft, damit der Proxy keine allgemeine Tür in das private Netzwerk wird. Binden Sie den Listener an die Bridge-Adresse für Clients, die über host-gateway kommen, an Loopback nur, wenn der Client den Netzwerk-Namensraum des Hosts teilt, und niemals an 0.0.0.0. Wählen Sie außerdem einen Port, der noch frei ist: Eine bereits belegte Nummer lässt den Proxy nicht lauschen, während der Container weiter in Zeitüberschreitungen läuft, was von dem Netzwerkfehler, mit dem Sie begannen, nicht zu unterscheiden ist.
Ports, Namen und die TLS-Entscheidung
Die Namensauflösung teilt sich genauso wie das Routing. Die öffentlichen Abfragen des Containers laufen über den Resolver der Engine, daher wird ein privater Name, der auf dem Host aufgelöst wird, im Container nicht aufgelöst. Der Proxy ist die Komponente, die den privaten Namen auflöst, weil er den Resolver des Hosts erbt.
TLS ist die Entscheidung, die aufgeschoben wird und dann den nächsten Fehler erklärt. Leitet der Proxy Bytes unverändert weiter, sieht der Container das eigene Zertifikat der API und braucht die interne CA in seinem Truststore. Beendet der Proxy TLS, wandert dieses Vertrauen auf den Host, und die Anwendung beginnt, Links aus dem empfangenen host-Header zu bauen. Wählen Sie eine Anordnung und wenden Sie sie auf jede Ressource an, denn eine Weiterleitung der anderen zeigt auf einen Hostnamen, den der Container nicht auflösen kann.
Prüfen Sie aus dem Dienst heraus
Senden Sie die Anfrage aus dem Container, nicht vom Host: ein einfaches curl gegen den Proxy-Pfad, dann die eigene Zustandsprüfung der Anwendung. Wenn der Proxy antwortet, die Anwendung aber weiterhin scheitert, vergleichen Sie drei Dinge: die Basis-URL, die sie baut, jede Proxy-Variable in ihrer Umgebung und die CA, der sie vertraut. Alle liegen in der Umgebung und den Protokollen des Containers, und die Behebung einer davon lässt das VPN unberührt.
Schreiben Sie die gewählte Anordnung in die README des Projekts, solange sie frisch ist. Sonst verbringt die nächste Person, die diese Zeitüberschreitung trifft, denselben Nachmittag damit, neu zu entdecken, dass der Tunnel auf dem Host lebt.