Docker: Einen Container durch ein VPN leiten und einen anderen direkt lassen
Gib dem Tunnel einen eigenen Container, lass nur die Dienste, die ihn brauchen, diesen Namensraum teilen, und rechne dann mit dem, was dem Eigentümer folgt: Ports, localhost, DNS.
Ein Host, zwei Workloads und eine Anforderung, die sie trennt: ein Download-Client, der über ein kommerzielles VPN herauskommen muss, und eine Verwaltungsoberfläche, die weiterhin die Verbindung des Hosts nutzen muss. Den VPN-Client auf dem Host zu installieren verletzt die Anforderung sofort, weil dann beide Workloads einen Ausgang teilen. Das Muster, das tatsächlich funktioniert, besteht darin, den Tunnel in einen eigenen Container zu verlegen und nur die Workloads, die ihn brauchen, dem Netzwerk-Namensraum dieses Containers beitreten zu lassen. Alles andere behält den gewöhnlichen Docker-Weg, und die Details, die folgen, sind Konsequenzen davon, wer den Namensraum besitzt.
Warum ein geteilter Namensraum die Regeln ändert
Ein Container, der dem Namensraum eines anderen beitritt, bekommt keinen eigenen Netzwerk-Stack. Er sieht dieselben Schnittstellen, dieselbe Routing-Tabelle und dasselbe Loopback wie der Container, dem er beigetreten ist, und genau deshalb ist das Muster nützlich: Der Workload löst auf und verbindet sich normal, und der Tunnel darunter fängt den Verkehr ab, ohne dass die Anwendung davon weiß. Der Preis ist, dass jede netzwerkartige Einstellung jetzt einen Eigentümer hat, und der beitretende Container ist es nicht.
Diese Eigentumsregel beantwortet die meisten Konfigurationsfragen, bevor sie gestellt werden. Wenn eine Einstellung im Netzwerk-Stack lebt, gehört sie zum Sidecar. Wenn sie den Prozess beschreibt, bleibt sie beim Workload.
Loopback ist der Teil, der Leute überrascht. Im beitretenden Container wird localhost zum Loopback des Sidecars aufgelöst, nicht zu einem eigenen privaten, sodass ein Dienst, der sich an 127.0.0.1 bindet und Isolation erwartet, diese Adresse nun mit dem Tunnel-Container teilt.
Veröffentliche Ports auf dem Container, der den Namensraum besitzt
Die Portzuordnung gehört dem Eigentümer des Namensraums, auch wenn der lauschende Prozess im anderen Container läuft. Deklariere die Zuordnung am Sidecar und lass den Workload seine eigene Prozesskonfiguration unangetastet. Eine Zuordnung am beitretenden Dienst kann nicht wirksam werden, ob die Engine sie nun ablehnt oder sie je nach Startart des Containers still ignoriert, und deshalb verschwindet eine Oberfläche, die eben noch erreichbar war, sobald das Muster angewendet wird.
services:
gluetun:
image: qmcgaw/gluetun
cap_add: [NET_ADMIN]
ports: ["8080:8080"]
client-vpn:
image: example/client
network_mode: "service:gluetun"
client-direct:
image: example/client
ports: ["8081:8080"]Lies diesen Block als drei Entscheidungen statt als drei Dienste. Das Sidecar besitzt den Tunnel und den veröffentlichten Port; der beitretende Client deklariert überhaupt kein Netzwerk; der direkte Client behält eine eigene Zuordnung im Standardnetzwerk. Die Fähigkeit NET_ADMIN ist das, was dem Sidecar erlaubt, Schnittstellen zu erstellen und Routen zu ändern, und sie ist der Grund, warum dieser Container mehr Rechte braucht als die Workloads, denen er dient.
Der Dienst, der direkt bleibt, behält sein eigenes Netzwerk
Der Workload, der nicht durch den Tunnel gehen darf, behält die Standard-Bridge oder ein eigenes benutzerdefiniertes Netzwerk, mit eigenem veröffentlichten Port und eigenem Ausgang. Es ist verlockend, ihn an das Netzwerk des Sidecars anzuhängen, damit die beiden Container über den Namen miteinander sprechen können, aber dieser eine Schritt verschiebt seinen Verkehr still in den Tunnel. Wenn sie sich gegenseitig erreichen müssen, baue ein separates benutzerdefiniertes Netzwerk, hänge beide daran an und lass das Sidecar den einzigen Weg nach draußen sein.
Halte die beiden Fragen getrennt: Welche Container sich gegenseitig erreichen können, und welcher wo hinausgeht. Sie zu verschmelzen ist der häufigste Weg, auf dem dieses Design wieder zu einem hostweiten VPN wird.
Eine Fehlermatrix für die erste Stunde
Die meisten ersten Versuche scheitern auf eine von vier Arten, und jede hat eine andere Ursache. Prüfe sie in dieser Reihenfolge, denn die ersten beiden erklären fast jeden Bericht.
| Symptom | Likely cause |
|---|---|
| Web UI unreachable after the change | Port mapping declared on the joining container instead of the sidecar |
| Both containers show the provider's exit address | The direct service was attached to the sidecar network |
| Internal names stop resolving | Resolver settings live on the host, not in the shared namespace |
| Health check fails but the app works | The check probes localhost, which now means the sidecar |
Die vierte Zeile ist die, die Leute fälschlich als Anwendungsfehler diagnostizieren. Eine Prüfung, die gegen das eigene Loopback des Workloads geschrieben wurde, testet jetzt den Tunnel-Container, also schlägt sie entweder fehl oder meldet den völlig falschen Dienst.
Wenn ein einzelner Hostname den Tunnel braucht
Manchmal ist ein voller Tunnel zu viel: Ein interner Name muss durch ein Firmen-VPN, während ein lokaler Socket oder eine Display-Verbindung weiter funktioniert. Ein voller Tunnel im Container bricht diesen Weg und alles, was an den Host adressiert ist, also route nur das Präfix, das ihn braucht. Ein Split-Routing-Wrapper um den Client tut das, indem er den genannten Host durch den Tunnel schickt und die vorherige Route für alles andere unangetastet lässt.
Wähle das, wenn du nur einen Dienst erreichen musst und nicht versuchst, den gesamten Verkehr des Containers zu verschieben. Es ist ein anderes Werkzeug als das Sidecar und beantwortet eine andere Frage.
Prüfe, und mach es dann neustartfest
Frage jeden Container, von welcher Adresse er zu kommen scheint. Der getunnelte sollte die Adresse des Anbieters ausgeben und der direkte den Ausgang des Hosts. Identische Antworten bedeuten, dass der direkte Dienst den falschen Namensraum geerbt hat oder dass ein VPN auf Host-Ebene noch alles abfängt, und diese beiden Korrekturen sind verschieden. Führe die Prüfung nach jeder Compose-Änderung erneut aus, denn ein kopierter Dienstblock genügt, um das Problem wieder einzuführen.
Drei Dinge gehen später gern kaputt. DNS folgt dem Eigentümer des Namensraums, also gehören Resolver- und Suchdomänen-Einstellungen zum Sidecar statt zum Host. Ein VPN mit Portweiterleitung ändert beim Wiederverbinden seinen Lauschport, also müssen der weitergeleitete Port des Anbieters und der Lauschport auf der Containerseite zurück in die Clientkonfiguration oder die Portzuordnung geschrieben werden, meist durch einen kleinen Aktualisierer, der die API des Anbieters liest. Und jede Prüfung, die gegen localhost geschrieben wurde, fragt jetzt den Tunnel-Container statt den Workload.
Schreib das Muster auf, einschließlich dessen, welcher Container den Namensraum besitzt und welche Ports dazugehören. Die nächste Person fügt einen Dienst hinzu, kopiert den falschen Block und reproduziert den ersten Fehler aus diesem Artikel.