Kontener nie dociera do API dostępnego tylko przez VPN? Ustaw proxy na hoście
Tunel żyje na hoście, więc kontener nie ma trasy do adresu prywatnego. Opublikuj wąskie odwrotne proxy i wywołuj je jako host.docker.internal.
Twoja usługa Compose kończy się przekroczeniem czasu wobec API, które przeglądarka na tej samej maszynie otwiera bez skargi. Ta różnica jest wskazówką: żądanie, które działa, wychodzi z hosta z tunelem VPN, a żądanie, które wisi, wychodzi z kontenera z własnym stosem sieciowym. Nic wewnątrz kontenera nie zna tras tunelu, więc prywatny cel pozostaje nieosiągalny, jakkolwiek zapisać nazwę. Rozwiązaniem jest świadomy skok przez hosta, a nie sprytniejsza tabela tras.
Zacznij od tego, dlaczego dwie ścieżki się różnią, a potem zdecyduj, gdzie tunel powinien mieszkać.
Dlaczego host działa, a kontener nie
Podniesienie tunelu daje hostowi interfejs, zestaw tras i zwykle wpis resolvera dostarczany przez VPN. Kontener niczego z tego nie dziedziczy: startuje z jednym interfejsem na moście silnika i trasą domyślną wskazującą ten most. Trasy zainstalowane na hoście są niewidoczne dla wszystkiego, co działa w tej przestrzeni nazw.
Dlatego objawy wyglądają niekonsekwentnie, a nie po prostu na zepsute. Host rozwiązuje prywatną nazwę i łączy się, bo istnieje tam zarówno nazwa, jak i trasa. Kontener wysyła swój pakiet na prywatny adres, do którego nic na ścieżce mostu nie dotrze, i żądanie kończy się przekroczeniem czasu. Dodanie nazwy do pliku hosts kontenera nic nie zmienia, bo sam adres jest nieosiągalny z miejsca, w którym stoi kontener.
Dotrzyj do hosta z wnętrza kontenera
Proces w kontenerze, który wybiera localhost, wybiera kontener. Docker Desktop publikuje host.docker.internal jako nazwę wskazującą z powrotem na maszynę uruchamiającą silnik; silniki Linux pozostawiają ją niezdefiniowaną, chyba że usługa o nią poprosi, i właśnie to robi mapowanie poniżej. Dzięki temu aplikacja celuje w nazwę i port zamiast w prywatny adres.
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
INTERNAL_API_HOST: "host.docker.internal:8666"Tunel zostaje po stronie hosta tego skoku, a port w tej zmiennej to ten, na którym proxy będzie nasłuchiwać. Kontener, który traci tunel, teraz zawodzi na jednym znanym skoku zamiast próbować trasy, której nigdy nie miał.
Wybierz, gdzie mieszka tunel
Dla usługi Compose, która potrzebuje prywatnego API, możliwe są trzy kształty i różnią się tym, który proces posiada tunel. Klient VPN może działać w obrazie kontenera, co wymaga wystawienia urządzenia tunelu i przyznania związanej z nim zdolności administracji siecią. Aplikacja może współdzielić przestrzeń nazw sieci osobnego kontenera tunelu, dzięki czemu oba procesy widzą jeden stos i jedną tabelę tras. Albo tunel może zostać na hoście, z wąskim przekazywaczem z hosta do kontenera.
Dwie pierwsze wciągają do projektu pliki profilu, zachowanie ponownego łączenia i politykę resolvera oraz uzależniają każdą usługę w pliku od kondycji tunelu. To za dużo sprzężenia jak na jedno wywołanie API. Dla stosu deweloperskiego, który czyta jeden wewnętrzny punkt końcowy, przekazywacz po stronie hosta jest najmniejszą zmianą, która wciąż trzyma poświadczenia tam, gdzie zarządza nimi zespół platformy.
Opublikuj jedną wąską ścieżkę na hoście
Zacznij od tego, co dziedziczy przekazywacz: uruchom go na koncie, które posiada tunel, a jego gniazda wychodzące przejmą trasy hosta i resolver hosta. Jeden blok location nginx obejmuje jedną ścieżkę.
server {
listen 8666;
location /search/ {
proxy_pass http://10.20.30.40:8090/;
proxy_set_header Host internal-api.example;
proxy_read_timeout 60s;
}
}Traktuj adres i port jako zastępcze i wstaw parę, na której API naprawdę nasłuchuje. Każdy kolejny upstream zasługuje na własny blok location, a każdy blok powinien wpuszczać tylko ścieżki wywoływane przez tę usługę, aby proxy nie stało się ogólnymi drzwiami do sieci prywatnej. Powiąż nasłuchiwacz z adresem mostu dla klientów przychodzących przez host-gateway, z loopbackiem tylko wtedy, gdy klient współdzieli przestrzeń nazw sieci hosta, i nigdy z 0.0.0.0. Wybierz też port, który jest jeszcze wolny: zajęty numer pozostawia proxy bez nasłuchu, podczas gdy kontener dalej przekracza czas, czego nie da się odróżnić od awarii sieci, od której zacząłeś.
Porty, nazwy i decyzja o TLS
Rozwiązywanie nazw dzieli się tak samo jak routing. Publiczne wyszukiwania kontenera przechodzą przez resolver silnika, więc prywatna nazwa, która rozwiązuje się na hoście, nie rozwiąże się wewnątrz kontenera. Proxy jest komponentem, który rozwiązuje prywatną nazwę, bo dziedziczy resolver hosta.
TLS to decyzja, którą się odkłada, a potem wyjaśnia następną awarię. Jeśli proxy przekazuje bajty nietknięte, kontener widzi własny certyfikat API i potrzebuje wewnętrznego CA w swoim magazynie zaufania. Jeśli proxy kończy TLS, to zaufanie przechodzi na hosta i aplikacja zaczyna budować linki z otrzymanego nagłówka host. Wybierz jeden układ i stosuj go do każdego zasobu, bo przekierowanie wystawione przez drugi wskazuje nazwę hosta, której kontener nie potrafi rozwiązać.
Sprawdź z wnętrza usługi
Wyślij żądanie z kontenera, nie z hosta: zwykły curl na ścieżkę proxy, potem własne sprawdzenie kondycji aplikacji. Gdy proxy odpowiada, ale aplikacja nadal zawodzi, porównaj trzy rzeczy: bazowy URL, który buduje, dowolną zmienną proxy w jej środowisku i CA, któremu ufa. Wszystkie są w środowisku i logach kontenera, a naprawa którejkolwiek z nich zostawia VPN w spokoju.
Zapisz wybrany układ w README projektu, póki jest świeży. Inaczej następna osoba, która natrafi na ten limit czasu, spędzi to samo popołudnie odkrywając od nowa, że tunel żyje na hoście.