Gluetun: komunikacja z hostem i innymi kontenerami
Dowiedz się, jak poprawnie skonfigurować porty i dostęp do sieci dla kontenerów korzystających z Gluetun. Rozwiązujemy problem braku własnych interfejsów sieciowych w VPN.
Co dzieje się, gdy kontener dołącza do sieci gluetun
Kontener, w którym ustawiono network_mode: service:gluetun, nie posiada własnych interfejsów sieciowych. Dołącza on do przestrzeni nazw sieciowej (network namespace) usługi gluetun, dlatego publikowanie portów i reguły zapory sieciowej przestają być właściwościami tego kontenera, a stają się właściwościami usługi gluetun. Każda odpowiedź poniżej wynika z tego jednego faktu.
Przestrzeń nazw sieciowych to prywatna kopia stosu sieciowego w jądrze systemu: posiada własne interfejsy, własną tablicę routingu, własne reguły zapory sieciowej oraz własne gniazda nasłuchujące. Docker domyślnie przydziela każdemu kontenerowi taką przestrzeń. Gdy wpiszesz network_mode: service:gluetun, Docker pomija ten krok i umieszcza nowy kontener wewnątrz przestrzeni nazw, która już należy do gluetun. Kontener zachowuje własny system plików oraz własny plik /etc/hosts, a ten drugi będzie istotny w dalszej części.
Można to sprawdzić bezpośrednio.
docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrentPolecenie to wyświetli container:, a po nim identyfikator kontenera gluetun, podczas gdy zwykły kontener wyświetliłby bridge. Ten przewodnik kontynuuje temat w miejscu, w którym kończy się kierowanie ruchu Docker przez VPN za pomocą gluetun: tunel działa, a teraz nic nie może połączyć się z kontenerem.
Publikacja portu w gluetun, a nie w aplikacji
Pozostawienie bloku ports: w usłudze, która ustawia network_mode, powoduje, że Docker odmawia utworzenia kontenera:
Error response from daemon: conflicting options: port publishing and the container type network modePrzyczyna jest bezpośrednia. Publikacja portu oznacza dodanie reguły NAT (network address translation), która przekierowuje port hosta do przestrzeni nazw sieciowej kontenera, a ten kontener jej nie posiada. Przenieś mapowanie do usługi gluetun. Numer portu nie ulega zmianie, ponieważ aplikacja nadal nasłuchuje na tym porcie wewnątrz współdzielonej przestrzeni nazw.
services:
gluetun:
ports:
- "8080:8080/tcp" # qBittorrent web UI
qbittorrent:
network_mode: "service:gluetun"
# no ports: block hereBlok expose: w usłudze zależnej jest równie bezcelowy, a blok networks: w tym miejscu stanowi twardą blokadę: Compose zgłasza, że usługa deklaruje wzajemnie wykluczające się network_mode oraz networks i całkowicie odmawia wczytania pliku.
Jedna konsekwencja ujawnia się później. Każdy kontener w przestrzeni nazw współdzieli jedną przestrzeń portów, więc dwie aplikacje, z których obie domyślnie korzystają z 8080, wchodzą w konflikt, a druga uruchamiana usługa kończy działanie z błędem adresu już w użyciu (address already in use). Zmień numer portu w konfiguracji jednej z nich, na przykład za pomocą zmiennej WEBUI_PORT w obrazie qBittorrent od LinuxServer, a następnie opublikuj nowy numer w gluetun.
W jaki sposób kontenery znajdujące się za gluetun komunikują się ze sobą?
Wewnątrz przestrzeni nazw współdzielą one interfejs loopback. Kontener znajdujący się za gluetun łączy się ze swoim odpowiednikiem pod adresem 127.0.0.1:<port> bez udziału sieci Docker.
Spoza przestrzeni nazw kontener nie posiada nazwy. Wbudowany serwer DNS Dockera rozwiązuje nazwę usługi na adres tej usługi w sieci zdefiniowanej przez użytkownika, a ten kontener nie posiada adresu w żadnej sieci. Dlatego standardowy kontener, taki jak Sonarr, nie łączy się z klientem torrent pod adresem http://qbittorrent:8080. Łączy się z nim pod adresem http://gluetun:8080, ponieważ gniazdo nasłuchuje w przestrzeni nazw gluetun, na adresie gluetun. Jest to zaskakujące dla osób, które znają zasady działania sieci Docker Compose i nazw usług i oczekują zastosowania standardowego nazewnictwa. Działa to również bez publikowania czegokolwiek na hoście, ponieważ oba kontenery znajdują się w tej samej sieci Compose.
Przed przystąpieniem do jakiegokolwiek debugowania należy sprawdzić DNS. Gluetun uruchamia własny resolver i nadpisuje plik /etc/resolv.conf we własnym kontenerze, jednak /etc/resolv.conf jest plikiem przypisanym do każdego kontenera z osobna, więc plik zapisany przez gluetun nie jest tym, który odczytuje Twoja aplikacja.
docker exec qbittorrent cat /etc/resolv.confJak uzyskać dostęp do usługi działającej na hoście Docker?
Należy użyć host.docker.internal. Wymaga to wprowadzenia dwóch ustawień w dwóch różnych miejscach, ponieważ przyczyną problemu są dwie odrębne kwestie.
Najpierw nazwa. /etc/hosts jest przypisane do konkretnego kontenera, więc wpis extra_hosts musi znaleźć się w konfiguracji kontenera z aplikacją, a nie w gluetun.
prowlarr:
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"host-gateway to specjalna wartość, którą Docker zastępuje wewnętrznym adresem samego hosta. W standardowej instalacji Docker na systemie Linux jest to adres mostu docker0, zazwyczaj 172.17.0.1. Wartość tę można potwierdzić za pomocą ip -4 addr show docker0 na serwerze VPS. Docker Desktop samodzielnie rozwiązuje tę nazwę, dlatego poradniki pisane na potrzeby laptopów pomijają linię extra_hosts, co powoduje, że ten sam plik konfiguracyjny nie działa na serwerze.
Następnie trasa. Dodanie nazwy informuje kontener jedynie o tym, którego adresu użyć. Pakiet nadal opuszcza kontener domyślną trasą gluetun, czyli tunelem, a firewall gluetun go odrzuca. Objawem jest połączenie, które zawiesza się, a następnie przekracza limit czasu, zamiast zostać odrzucone. Odmowa oznacza, że pakiet dotarł do celu i otrzymał odpowiedź negatywną. Przekroczenie limitu czasu oznacza, że pakiet nigdy nie dotarł do celu.
gluetun:
environment:
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32Następnie należy sprawdzić, czy usługa na hoście faktycznie nasłuchuje na tym adresie. Serwer PostgreSQL powiązany wyłącznie z 127.0.0.1 jest nieosiągalny z poziomu jakiegokolwiek kontenera, niezależnie od tunelu, ponieważ 127.0.0.1 wewnątrz przestrzeni nazw jest jej własnym interfejsem zwrotnym (loopback). Należy powiązać go z 172.17.0.1: pozwoli to na akceptowanie połączeń z kontenerów przy jednoczesnym pozostaniu poza interfejsem publicznym. Weryfikację można przeprowadzić za pomocą ss -lntp | grep 5432 na hoście.
Co faktycznie zmienia zmienna FIREWALL_OUTBOUND_SUBNETS
Dokumentacja gluetun opisuje tę zmienną jako listę podsieci rozdzielonych przecinkami, do których gluetun oraz kontenery współdzielące jego stos sieciowy mają dostęp. Zaznacza również, że wiąże się to ze zmianami w zaporze sieciowej oraz tablicy routingu. Obie te kwestie są istotne. Gluetun dodaje trasę dla każdej wymienionej podsieci przez bramę mostu Docker, dzięki czemu pakiety dla tych adresów opuszczają system przez eth0, a nie przez tunel. Zmienna ta otwiera również zaporę sieciową dla tych podsieci, ponieważ w przeciwnym razie gluetun odrzuca ruch wychodzący, który nie jest kierowany do serwera VPN.
Wartość należy wpisać bez spacji po przecinkach.
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32Dwie właściwości są często pomijane. Jest to ustawienie na poziomie przestrzeni nazw, więc dotyczy każdego kontenera znajdującego się za gluetun, a nie tylko tego, który był zamierzony. Ponadto dotyczy ono wyłącznie ruchu wychodzącego: reguluje połączenia inicjowane przez kontener. Połączenia przychodzące na opublikowany port korzystają z innej ścieżki i nie wymagają wpisu w tym miejscu.
Dostęp do interfejsu WWW z węzła Tailscale
Tailscale przydziela każdej maszynie adres z zakresu 100.64.0.0/10, zarezerwowanego dla NAT klasy operatorskiej. W przypadku osobistego tailnetu nie wiąże się to z żadnymi kosztami, ale limity użytkowników i urządzeń w bezpłatnym planie decydują o tym, czy pozostanie to bezpłatne, gdy inne osoby będą potrzebować dostępu do tych samych interfejsów. Ponieważ rozliczenia obejmują użytkowników, a nie maszyny, rzeczywisty koszt płatnego tailnetu zależy od liczby zaproszonych osób, a nie od liczby udostępnianych im kontenerów. Te dwa kierunki wymagają odmiennych działań.
Ruch przychodzący jest prostszy. Publikacja 8080:8080 w gluetun powoduje powiązanie tego portu ze wszystkimi adresami hosta, a interfejs tailscale0 jest jednym z nich. W rezultacie węzeł otwiera http://<machine-name>:8080 i uzyskuje dostęp do kontenera. Gluetun nie bierze udziału w tym procesie, ponieważ reguła NAT Dockera znajduje się na hoście, poza przestrzenią nazw kontenera.
Aby interfejs był dostępny wyłącznie przez tailnet, należy powiązać opublikowany port z adresem Tailscale hosta, zamiast ze wszystkimi dostępnymi adresami.
ports:
- "100.101.102.103:8080:8080/tcp"Adres ten można sprawdzić za pomocą tailscale ip -4 na hoście. Powiązanie z konkretnym adresem jest skuteczniejszą metodą kontroli niż reguła firewalla, ponieważ port w ogóle nie jest otwierany na publicznym interfejsie. Jeśli zamiast adresu hosta i portu preferowany jest dostęp do interfejsu przez nazwę HTTPS, tailscale serve może pełnić rolę frontendu dla tego opublikowanego portu, choć funkcje serve i funnel różnią się zakresem dostępności i tylko jedna z nich utrzymuje interfejs wewnątrz sieci tailnet. Rozwiązuje to również problem opisany w publikowaniu portów przez Dockera z pominięciem ufw.
Ruch wychodzący to obszar, w którym powraca FIREWALL_OUTBOUND_SUBNETS. Jeśli kontener musi nawiązać połączenie z węzłem, należy dodać adres tego węzła, preferując /32 dla każdego węzła zamiast całego /10. Jeśli wywoływana maszyna znajduje się w sieci prywatnej dostępnej przez VPS rozgłaszający tę podsieć do Twojego tailnetu, należy podać zakres rozgłaszany zamiast adresu 100.x samego routera oraz upewnić się, że host zaakceptował te trasy. Nazwy MagicDNS nie będą rozwiązywane wewnątrz kontenera, ponieważ kontener nie korzysta z resolvera hosta. Należy zatem użyć numerycznego adresu 100.x lub przypisać go na stałe za pomocą linii extra_hosts. Ta sama zasada obowiązuje w przypadku korzystania z własnego serwera kontrolnego Tailscale za pomocą Headscale.
Kompletny plik compose dla typowej konfiguracji
Klient pobierający dane za VPN, dwa interfejsy WWW dostępne wyłącznie w sieci tailnet oraz jeden kontener odczytujący bazę danych PostgreSQL działającą na hoście.
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- "127.0.0.1:8000:8000/tcp" # gluetun control server, host only
- "100.101.102.103:8080:8080/tcp" # qBittorrent UI, tailnet only
- "100.101.102.103:9696:9696/tcp" # Prowlarr UI, tailnet only
volumes:
- ./gluetun:/gluetun
environment:
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
- SERVER_CITIES=Amsterdam
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,100.64.7.9/32
- TZ=Europe/Amsterdam
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- WEBUI_PORT=8080
volumes:
- ./qbittorrent:/config
- /srv/downloads:/downloads
depends_on:
gluetun:
condition: service_healthy
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- PROWLARR__POSTGRES__HOST=host.docker.internal
- PROWLARR__POSTGRES__PORT=5432
- PROWLARR__POSTGRES__USER=prowlarr
- PROWLARR__POSTGRES__PASSWORD=${PROWLARR_DB_PASSWORD}
- PROWLARR__POSTGRES__MAINDB=prowlarr-main
- PROWLARR__POSTGRES__LOGDB=prowlarr-log
volumes:
- ./prowlarr:/config
depends_on:
gluetun:
condition: service_healthy
restart: unless-stoppedNależy analizować schemat pliku, a nie konkretne nazwy produktów. Oba interfejsy WWW są publikowane przez gluetun i powiązane z adresem tailnet hosta, dzięki czemu odpowiadają wyłącznie w sieci Tailscale. Tylko Prowlarr zawiera linię extra_hosts, ponieważ jest to kontener rozwiązujący host.docker.internal. FIREWALL_OUTBOUND_SUBNETS wskazuje dwa pojedyncze adresy: adres mostu Docker hosta, umożliwiający Prowlarr nawiązanie połączenia z bazą danych, oraz jeden węzeł sieci tailnet.
Serwer PostgreSQL celowo pominięto w pliku. Działa on na VPS jako zwykła usługa systemowa nasłuchująca na 172.17.0.1:5432. Jest to ten sam model warstwowy, co w przypadku stosów arr w Docker Compose, przy czym baza danych została przeniesiona poza środowisko Docker.
Klucz prywatny WireGuard należy przechowywać poza plikiem compose. ${WIREGUARD_PRIVATE_KEY} odczytuje go z pliku .env znajdującego się w tym samym katalogu; schemat ten opisano w plikach env i sekretach dla Docker Compose. Klauzula condition: service_healthy wykorzystuje mechanizm healthcheck dostarczany przez obraz gluetun, dzięki czemu żadna usługa nie uruchomi się, dopóki tunel nie zgłosi gotowości. Healthchecki w Compose wyjaśniają ogólną zasadę działania.
Publikowanie na wszystkich adresach zamiast tylko w sieci tailnet
Należy usunąć prefiks adresu oraz powiązania portów w 0.0.0.0, co obejmuje publiczny adres IP serwera VPS. Działanie to należy wykonywać wyłącznie za kontrolowanym firewallem, pamiętając o wcześniejszym zapoznaniu się z uwagami dotyczącymi ufw.
ports:
- "8080:8080/tcp"Potwierdzenie przesyłania ruchu przez tunel
Wykonaj dwukrotnie to samo żądanie: raz z wnętrza przestrzeni nazw (namespace), a raz z hosta, a następnie porównaj wyniki.
docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.orgPierwsze polecenie powinno wyświetlić adres wyjściowy dostawcy VPN. Drugie powinno wyświetlić adres VPS. Jeśli adresy są identyczne, ruch kontenera nie przechodzi przez tunel, a wszelkie poprawki opisane w tym przewodniku są bezcelowe, dopóki ten stan nie zostanie naprawiony.
Tablica routingu wskazuje, który ruch jest kierowany przez tunel, a który nie.
docker run --rm --network=container:gluetun alpine:3.22 ip route showDomyślna trasa powinna wskazywać na interfejs tunelu, tun0. Poniżej powinna znajdować się jedna trasa dla każdego wpisu w FIREWALL_OUTBOUND_SUBNETS, wskazująca na bramę mostu Docker. Każda inna trasa wychodząca przez eth0 oznacza ruch, który omija VPN.
Serwer sterujący Gluetun raportuje ten sam publiczny adres IP na porcie 8000, pod adresem /v1/publicip/ip. Nowsze wersje wymagają skonfigurowania uwierzytelniania dla tras serwera sterującego, więc należy to skonfigurować przed poleganiem na tej metodzie.
Wyciek spowodowany błędną podsiecią
FIREWALL_OUTBOUND_SUBNETS to celowo wykonany otwór w zaporze sieciowej, zatem rozmiar ryzyka odpowiada rozmiarowi tego otworu. Oto cztery sposoby na jego nadmierne powiększenie:
0.0.0.0/0wysyła cały ruch poza tunel. Dwie powyższe kontrole IP wykryją to przy pierwszym uruchomieniu, ponieważ zwrócą ten sam adres.- Zakres szerszy niż docelowy. Otwarcie
10.0.0.0/8w celu uzyskania dostępu do jednej maszyny pod adresem10.0.1.7otwiera również każdy adres, który może zostać rozgłoszony przez peerów sieci torrent w tym zakresie. Należy wpisać10.0.1.7/32. - Zakres pokrywający się z adresami samego tunelu. Dokumentacja gluetun ostrzega, że powoduje to wysyłanie ruchu VPN przez most zamiast przez tunel, co przerywa przekierowanie portów. Przed otwarciem jakiegokolwiek prywatnego zakresu należy sprawdzić wartość
WIREGUARD_ADDRESSES. 100.64.0.0/10dla Tailscale. Oznacza to otwarcie około czterech milionów adresów, aby uzyskać dostęp do jednego peera. Wymagane peery należy wymieniać jako wpisy/32.
Należy pamiętać, że to ustawienie obejmuje całą przestrzeń nazw. Otwarcie podsieci, aby indeksator mógł uzyskać dostęp do usługi hosta, otwiera tę samą podsieć dla klienta torrent współdzielącego tę przestrzeń nazw. Po każdej zmianie tej zmiennej należy ponownie wykonać test publicznego adresu IP, ponieważ jest to jedyny test potwierdzający, czy zmiana przyniosła zamierzony skutek.
Co ulega awarii podczas restartu gluetun
Gluetun zarządza przestrzenią nazw (namespace), więc cykl życia gluetun jest tożsamy z cyklem życia tej przestrzeni. Uruchomienie kontenera zależnego w czasie, gdy gluetun nie działa, kończy się natychmiastowym niepowodzeniem:
Error response from daemon: cannot join network of a non running containerRestartowanie gluetun w miejscu jest mniej oczywistą awarią. Kontenery zależne działają nadal, podczas gdy przestrzeń nazw, do której są przypisane, jest przebudowywana pod nimi, więc docker ps raportuje poprawny stan, mimo że żadna usługa nie odpowiada. Po każdej zmianie w usłudze gluetun należy odtworzyć całą grupę, zamiast restartować tylko jej część.
docker compose up -d --force-recreateTo samo dotyczy aktualizacji obrazów. Pobranie nowego obrazu gluetun i odtworzenie tylko tej jednej usługi powoduje, że pozostałe kontenery wskazują na przestrzeń nazw, która już nie istnieje.
FAQ
Dlaczego Docker zgłasza błąd dotyczący publikowania portów i trybu sieci kontenera?
Ponieważ blok ports: znajduje się w usłudze, która ma również ustawione network_mode: service:gluetun. Publikowanie portu dodaje regułę NAT przekierowującą port hosta do własnej przestrzeni nazw sieciowej kontenera, a kontener w tym trybie jej nie posiada. Należy usunąć blok ports: z tej usługi i dodać identyczne mapowanie do usługi gluetun. Numer portu pozostaje bez zmian, ponieważ aplikacja nadal nasłuchuje na nim wewnątrz współdzielonej przestrzeni nazw.
Jak inne kontenery mogą połączyć się z usługą działającą za gluetun?
Kontenery wewnątrz tej samej przestrzeni nazw komunikują się ze sobą poprzez 127.0.0.1. Kontenery znajdujące się poza nią używają nazwy usługi gluetun, dlatego http://gluetun:8080 działa tam, gdzie http://qbittorrent:8080 nie działa. Kontener aplikacji nie posiada adresu w żadnej sieci Docker, więc wbudowany serwer DNS nie ma czego rozwiązać dla jego nazwy. Publikowanie portów nie jest do tego wymagane, o ile oba kontenery współdzielą sieć Compose.
Co należy wpisać w FIREWALL_OUTBOUND_SUBNETS?
Wyłącznie adresy, z którymi kontener za gluetun musi nawiązać połączenie, zapisane w sposób jak najbardziej precyzyjny. Pojedyncza maszyna to /32. Dwa typowe wpisy to host Docker pod adresem 172.17.0.1/32 oraz jeden /32 dla każdego węzła Tailscale, z którym nawiązywane jest połączenie. Nigdy nie należy dodawać 0.0.0.0/0 ani zakresów pokrywających się z adresami tunelu VPN. Połączenia przychodzące na opublikowany port nie wymagają tutaj żadnego wpisu.
Dlaczego kontener nie może rozwiązać nazw Tailscale MagicDNS?
MagicDNS działa poprzez wskazanie serwera nazw hosta na serwer DNS Tailscale, a kontener nie korzysta z resolvera hosta. Używa on ustawień z własnego pliku /etc/resolv.conf, które za gluetun są konfiguracją DNS tego narzędzia. Należy to potwierdzić za pomocą docker exec <container> cat /etc/resolv.conf. Należy użyć numerycznego adresu 100.x węzła lub przypisać nazwę za pomocą wpisu extra_hosts w tym kontenerze.
Jak potwierdzić, że ruch nadal przechodzi przez VPN?
Należy wykonać jedno zapytanie z wnętrza przestrzeni nazw oraz to samo zapytanie z poziomu hosta, a następnie porównać wyniki. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org powinno zwrócić adres wyjściowy dostawcy VPN, podczas gdy curl -s https://api.ipify.org na VPS zwróci adres VPS. Dwie identyczne odpowiedzi oznaczają, że tunel nie obsługuje ruchu kontenera. Test należy powtórzyć po każdej zmianie w FIREWALL_OUTBOUND_SUBNETS.