VPN w Dockerze: jak tunelować ruch kontenerów i nie zepsuć sieci

0
40
Rate this post

Nawigacja:

O co w ogóle chodzi: po co pakować VPN do Dockera?

Typowe scenariusze: kiedy w ogóle myśleć o VPN w kontenerach?

Zanim pojawi się jakakolwiek konfiguracja, dobrze odpowiedzieć sobie na pytanie: po co chcesz tunelować ruch kontenerów przez VPN? Od tego zależy absolutnie wszystko – wybór modelu, poziom skomplikowania i ryzyko, że „zepsujesz” sieć.

Najczęstsze scenariusze, w których VPN w Dockerze ma sens:

  • Dostęp do zasobów firmowych – chcesz, aby mikroserwisy (np. aplikacja backendowa, integra z ERP, monitoring) łączyły się z bazami, API lub plikami dostępnymi wyłącznie przez VPN firmowy.
  • Scrapowanie lub automatyzacja zza granicy – boty, scrapery czy integracje muszą wychodzić do internetu z konkretnego kraju lub adresu IP, więc kontenery mają korzystać z komercyjnego VPN.
  • Ukrywanie IP serwisów zewnętrznych – chcesz, by np. wszystkie wyjścia HTTP z aplikacji szły jednym „publicznym” IP (np. regulamin usług), a sam host pozostawał widoczny z innego adresu.
  • Lab / testy bezpieczeństwa – symulacja sieci firmowej, ruchu między „lokacjami”, testowanie aplikacji za różnymi bramami VPN.

Zdarza się też odwrotna sytuacja: host ma już globalny VPN (np. klient na serwerze), a ty chcesz, żeby tylko część kontenerów korzystała z tunelu, a reszta wychodziła normalnie przez internet lub LAN. Tutaj zaczynają się prawdziwe schody.

VPN hosta vs VPN per kontener – dwa zupełnie różne światy

Jeśli teraz zastanawiasz się: „czy nie wystarczy zainstalować VPN na serwerze, a Docker sam to ogarnie?”, odpowiedz sobie szczerze: czy tunel ma obowiązywać wszystkie usługi, czy tylko wybrane kontenery?

Dwa podstawowe modele:

  • VPN na hoście – instalujesz klienta OpenVPN/WireGuard na systemie, zmieniasz routing całego hosta. Wszystko, w tym Docker i jego kontenery, wychodzi przez tunel, chyba że ręcznie pokombinujesz z trasami.
  • VPN per kontener (lub grupa kontenerów) – uruchamiasz klienta VPN wewnątrz kontenera, albo jako osobny „gateway”, a inne kontenery używają jego stosu sieciowego.

Różnica jest zasadnicza: VPN na hoście dotyka wszystkiego, łącznie z SSH, aktualizacjami, monitoringiem, dostępem do sieci lokalnej. VPN w kontenerze możesz ograniczyć do jednej aplikacji, jednej sieci Docker, jednego projektu docker-compose.

VPN a routing, DNS i dostęp do sieci lokalnej

VPN nie jest „magicznym kablem”. Żeby działał, dodaje nowy interfejs sieciowy (np. tun0), zmienia trasy i często ustawia inne serwery DNS. Tu pojawia się klasyczny problem: „odpaliłem VPN, nagle nic w sieci lokalnej nie działa, a kontenery przestały się dogadywać”.

Zastanów się, co jest dla ciebie ważniejsze:

  • czy kontenery muszą widzieć urządzenia w twoim LAN (drukarki, NAS, inne serwery),
  • czy tylko wychodzący ruch (np. HTTP na zewnątrz) ma iść przez tunel,
  • czy chcesz split tunneling (część ruchu przez VPN, część „normalnie”).

Każda z tych odpowiedzi prowadzi do innego projektu sieci Docker + VPN. Im więcej wyjątków (np. „to idzie przez VPN, ale to do LAN, a to bez tunelu”), tym większa szansa, że bez planu zrobisz nieprzejrzysty bałagan.

Jakiego ruchu naprawdę potrzebujesz przez VPN?

Dobre pytanie startowe dla ciebie: jaki masz główny cel i jaki konkretny ruch chcesz tunelować? HTTP do konkretnego API? Cały ruch TCP/UDP? Tylko DNS? A może pojedynczą aplikację kliencką w kontenerze?

Zapisz jedno zdanie: „Kontener X ma móc łączyć się z Y przez VPN, ale Z ma pozostać dostępne bez tunelu”. Takie proste zdanie projektowe bardzo ładnie ujawnia, czy wystarczy prosty VPN na hoście, czy trzeba myśleć o per-kontenerowej bramce.

Fundamenty: jak działa sieć Dockera i co robi VPN z ruchem

Modele sieci w Dockerze: szybkie odświeżenie

Jeśli masz za sobą pierwsze projekty z Dockerem, znasz na pewno przynajmniej domyślną sieć bridge. W kontekście VPN dobrze rozumieć kilka trybów:

  • bridge – domyślny tryb; kontenery mają adresy np. 172.17.0.0/16, ruch do świata idzie przez NAT hosta. Świetny punkt startowy pod tunelowanie.
  • host – kontener używa stosu sieciowego hosta, bez osobnego IP w podsieci Docker. Tu VPN na hoście działa 1:1 na kontener.
  • none – kontener nie ma sieci; używane, jeśli sam tworzysz namespace, veth, podczepiasz do innych rozwiązań.
  • overlay – sieci między hostami (Swarm, Kubernetes). Przy VPN trzeba szczególnie uważać na routing między nodami.
  • macvlan – każdy kontener dostaje własny adres IP w twojej fizycznej sieci (np. 192.168.1.x), jakby był oddzielną maszyną.

Którego używasz teraz? Jeśli nie jesteś pewien, najpewniej jest to bridge. Przy planowaniu ruchu przez VPN to dobrze, bo daje przewidywalny NAT i prosty routing.

Jak kontener widzi świat: namespace’y, veth, NAT

Kontener ma swój network namespace – własny zestaw interfejsów, tras, tablic ARP. Łączy się z hostem parą interfejsów veth (w kontenerze np. eth0, na hoście coś w stylu vethXXXX). Ten interfejs na hoście jest wpięty w mostek docker0 lub inną sieć bridge.

Ruch kontenera:

  1. wychodzi z eth0 w kontenerze,
  2. wpada do veth na hoście,
  3. przechodzi przez docker0,
  4. jest NATowany przez iptables hosta na jego adres IP,
  5. wychodzi fizycznym interfejsem (np. eth0, ens160) albo przez tunel VPN (np. tun0).

Czyli w prostych słowach: kontenery korzystają z domyślnej bramy hosta. Jeśli ta brama stanie się interfejsem VPN, kontenery również pójdą przez tunel – chyba że zmienisz im trasy.

Co robi VPN w systemie operacyjnym

Standardowy klient OpenVPN lub WireGuard robi trzy kluczowe rzeczy:

  • tworzy interfejs tun/tap (np. tun0),
  • dodaje trasę domyślną przez ten interfejs (np. 0.0.0.0/0 dev tun0),
  • często wymusza użycie określonych DNS-ów (np. poprzez resolvconf lub systemd-resolved).

Jeśli to robisz na hoście z Dockerem, efektem ubocznym jest to, że domyślna trasa kontenerów też staje się VPN-em. Jeżeli to właśnie chciałeś osiągnąć – super. Jeśli nie, szykuj się na grzebanie w ip route i iptables.

Dlaczego „odpalę VPN w kontenerze i będzie działać” bywa złudne

Teoretycznie brzmi to idealnie: uruchamiasz OpenVPN w kontenerze, aplikację w tym samym kontenerze, sprawa zamknięta. W praktyce pojawiają się niespodzianki:

  • kontener musi mieć dostęp do /dev/net/tun i uprawnienie CAP_NET_ADMIN,
  • VPN zmienia trasy w sieci kontenera, ale niekoniecznie w hoście,
  • potrzebujesz kill switcha (blokady wyjścia poza VPN), inaczej przy awarii tunelu ruch może pójść „na żywca”.

A teraz pomnóż to przez 10–20 kontenerów, które trzeba utrzymać i zdebugować. Zastanów się, czy chcesz mieć VPN w każdym, czy lepiej jeden kontener-bramkę, do którego podepniesz pozostałe.

Jak wybrać strategię: VPN na hoście, bramka w kontenerze czy VPN w każdym?

Trzy główne modele architektury VPN w środowisku Docker

Całość sprowadza się do trzech podstawowych strategii. Zobacz, w którym z nich widzisz siebie.

ModelOpisZłożonośćKontrola nad ruchem
VPN na hościeKlient VPN działa w systemie, wszystkie kontenery dziedziczą tunelNiskaNiska – trudno zrobić selektywny tunel
Kontener-bramka VPNJeden kontener zestawia VPN, inne używają jego stacku sieciowegoŚredniaŚrednia/Wysoka – grupy kontenerów przez tunel
VPN w każdym kontenerzeKażdy kontener sam zestawia własne połączenie VPNWysokaBardzo wysoka – pełna izolacja IP per kontener

Każdy model ma swoje uzasadnienie. Pytanie brzmi: jak bardzo różnorodne są wymagania sieciowe twoich usług?

Plusy i minusy: izolacja, bezpieczeństwo, debugowanie

Jeżeli głównym kryterium jest prostota, VPN na hoście wygrywa. Jedna konfiguracja, jedno logowanie, mniej ruchomych elementów. W zamian dostajesz:

  • brak granularnej kontroli,
  • potencjalne kłopoty z dostępem do LAN,
  • problemy, gdy host jest jednocześnie serwerem innych usług (np. jest w czyimś VLAN-ie).

Model „kontener jako bramka VPN” to złoty środek: masz jedno miejsce, gdzie zestawiasz VPN, a jednocześnie możesz zdecydować, które kontenery przez niego przechodzą, a które nie. Debugowanie jest też relatywnie proste – patrzysz w logi jednego kontenera.

VPN wewnątrz każdego kontenera daje maksymalną izolację: różne serwery VPN, różne kraje, różne trasy. Jednocześnie:

  • utrzymanie konfiguracji skaluje się liniowo z liczbą kontenerów,
  • każdy kontener wymaga CAP_NET_ADMIN i tun,
  • monitoring i awarie multiplikują się.

Zanim pójdziesz w ten model, zapytaj się uczciwie: czy naprawdę potrzebujesz osobnego IP / kraju dla każdego kontenera, czy wystarczy jeden gateway na grupę?

Kryteria wyboru strategii

Kilka pytań kontrolnych, które bardzo ułatwiają wybór:

  • Ile kontenerów ma korzystać z VPN teraz, a ile przewidujesz za pół roku?
  • Czy wszystkie mogą iść „w pakiecie” przez jeden adres IP, czy muszą mieć różne lokalizacje?
  • Czy środowisko jest produkcyjne (wymóg stabilności, SLA), czy bardziej lab / hobby?
  • Czy wymagany jest split tunneling (np. tylko ruch do określonych adresów przez VPN)?

Jeśli odpowiadasz: „kilka usług, jeden VPN, brak specjalnych wymagań split”, najczęściej idealny będzie kontener-bramka. Jeżeli mówisz: „jeden mały serwer, wszystko ma iść przez VPN, to tylko lab” – wystarczy VPN na hoście. Gdy dochodzisz do: „po kilka różnych lokalizacji, różne IP, compliance” – wtedy wchodzisz w per-kontenerowe VPN lub kombinację par kontener-aplikacja + kontener-VPN.

Model 1 – VPN na hoście: najprostsze, ale nie zawsze pożądane

Jak działa Docker, gdy host ma aktywny VPN

Załóżmy, że masz serwer z Debianem lub Ubuntu. Instalujesz OpenVPN, uruchamiasz klienta, pojawia się interfejs tun0, a tablica routingu ustawia go jako bramę domyślną. Co się dzieje z Dockerem?

Kontenery nadal wychodzą w świat przez NAT hosta. Ale domyślna trasa hosta prowadzi teraz przez VPN. To oznacza, że:

  • każdy pakiet z kontenera wychodzący do internetu zostanie opakowany i wysłany przez tunel,
  • zewnętrzne serwisy zobaczą adres IP twojego serwera VPN, nie fizycznego hosta,
  • przychodzące połączenia na publiczny adres hosta mogą przestać działać, jeśli VPN „przejmie” routing, albo operator VPN blokuje porty przychodzące.

Typowe pułapki przy VPN na hoście

Jeżeli wpinasz VPN na hoście, a Docker ma tam produkcję, pierwsze zgrzyty pojawiają się szybko. Gdzie konkretnie?

  • Utrata dostępu do hosta po SSH – jeśli zestawiasz VPN zdalnie, nowa trasa domyślna potrafi „przeciąć” działające połączenie SSH. Przy kolejnym logowaniu łączysz się już przez VPN, ale reguły serwera mogą to odrzucać.
  • Brak dostępu do LAN – host i kontenery nagle nie widzą drukarek, NAS-a czy innych serwerów w lokalnej sieci. Dlaczego? Bo VPN wciągnął trasę 0.0.0.0/0 i nie przywrócił routingu do twojej podsieci LAN.
  • Nieoczekiwany brak otwartych portów – ekspozycja portów Dockera (np. -p 80:80) działa „na sucho”, ale z zewnątrz usługa nie odpowiada. Połączenia przychodzą na adres publiczny hosta, ale odpowiedzi wychodzą już przez tunel z innym IP.

Jeżeli masz wrażenie, że „wczoraj działało, dziś nic nie słucha na porcie 443”, zacznij od pytania: czy na hoście zmienił się routing lub konfiguracja VPN?

Jak częściowo wyłączyć VPN dla Dockera

Czasem chcesz, żeby sam host korzystał z VPN, ale kilka kontenerów wychodziło bezpośrednio. Jak to osiągnąć bez przepinania wszystkiego?

Najprostsza metoda to manipulacja trasami na hoście. Przykładowy scenariusz:

  • OpenVPN dodaje domyślną trasę przez tun0,
  • ty ręcznie dodajesz trasę dla podsieci Docker (np. 172.18.0.0/16) i docelowych adresów/AS-ów poza VPN-em.
# przykład: podsieć dockera 172.18.0.0/16 ma wychodzić poza VPN
ip route add 0.0.0.0/0 via <brama_fizyczna> table 100
ip rule add from 172.18.0.0/16 lookup 100

Efekt: ruch z kontenerów trafia do osobnej tablicy routingu i omija VPN. Brzmi kusząco? Zastanów się, czy będziesz ten układ utrzymywać po pół roku, czy za każdym razem od nowa „doklepywać” trasy.

Jeżeli konfiguracja ma żyć dłużej, wygodniej przejść na model z kontenerem-bramką, a VPN na hoście zostawić tylko na potrzeby administracyjne.

Kiedy VPN na hoście ma sens

Są sytuacje, w których ten model wygrywa prostotą. Kiedy?

  • Masz jeden mały serwer, na którym działają 2–3 usługi, wszystkie mają iść przez ten sam VPN.
  • Całą maszynę traktujesz jako „jedno pudełko za VPN-em”, bez potrzeby finezyjnego routingu.
  • Nie zależy ci na utrzymaniu publicznej ekspozycji usług (serwer nie musi przyjmować połączeń z internetu lub z LAN).

Jeżeli siedzisz na VPS-ie za NAT-em dostawcy VPN i chcesz tam mieć tylko downloadera w Dockerze – nie komplikuj. Tu naprawdę wystarczy klient VPN na hoście.

Model 2 – kontener jako bramka VPN dla innych kontenerów

Na czym polega kontener-bramka?

Zamiast pakować klienta VPN w każdego kontenera, tworzysz jeden dedykowany kontener:

  • ma uprawnienia sieciowe (np. CAP_NET_ADMIN, /dev/net/tun),
  • zestawia połączenie z serwerem VPN (OpenVPN, WireGuard),
  • inne kontenery używają jego stosu sieciowego (Docker: --network container:nazwa_bramki) lub łączą się przez dedykowaną sieć i iptables.

Pytanie do ciebie: chcesz, by kontener-bramka był „routerem” z NAT-em, czy raczej wspólnym namespace’em sieciowym? To dwie różne strategie.

Strategia 1: współdzielony namespace sieciowy

Docker pozwala wskazać, że kontener ma używać sieci innego kontenera:

docker run -d --name vpn-gateway 
  --cap-add=NET_ADMIN 
  --device=/dev/net/tun 
  my-openvpn-image

docker run -d --name app1 
  --network container:vpn-gateway 
  my-app-image

Co to daje?

  • Jeden namespace sieciowyvpn-gateway i app1 współdzielą interfejsy, IP i trasy.
  • Ruch app1 wychodzi dokładnie tak, jak ruch bramki – przez tun0.
  • Nie ma NAT-u pomiędzy nimi, po prostu siedzą w tej samej „sieci logicznej”.

To podejście jest bardzo czyste koncepcyjnie: budujesz „paczki” aplikacja + VPN. Zastanów się tylko, ilu takich partnerów potrzebujesz. Dla kilku kluczowych usług – świetne. Dla kilkudziesięciu – może zrobić się tłoczno.

Strategia 2: bramka jako router/NAT dla podsieci

Drugi wariant: bramka jest osobnym kontenerem w sieci bridge, a inne kontenery wysyłają ruch do niej jak do routera. Schemat:

  1. Tworzysz własną sieć bridge w Dockerze.
  2. Podłączasz do niej bramkę i kontenery-aplikacje.
  3. W bramce włączasz ip_forward i NAT z tej podsieci na interfejs VPN (tun0).
  4. W kontenerach-aplikacjach ustawiasz bramkę domyślną na IP bramki.
docker network create --subnet 172.20.0.0/16 vpn_net

docker run -d --name vpn-gateway 
  --cap-add=NET_ADMIN 
  --device=/dev/net/tun 
  --network vpn_net 
  --ip 172.20.0.2 
  my-openvpn-image

docker run -d --name app1 
  --network vpn_net 
  --ip 172.20.0.10 
  my-app-image

Wewnątrz app1 wskazujesz ręcznie trasę domyślną (np. przez ip route del default && ip route add default via 172.20.0.2) lub robisz to przez własny entrypoint. VPN-gateway robi NAT z 172.20.0.0/16 na tun0.

Tutaj pojawia się pytanie: wolisz prostotę namespace’u, czy większą elastyczność routera z NAT-em? Jeśli nie potrzebujesz osobnych IP widocznych po stronie VPN dla każdego kontenera, współdzielony namespace jest zwykle lżejszy w utrzymaniu.

Zalety kontenera-bramki

Ten model ma kilka praktycznych plusów, które docenisz przy pierwszej awarii:

  • Jeden punkt konfiguracji VPN – certyfikaty, klucze, konfiguracja DNS siedzą w jednym kontenerze.
  • Łatwe grupowanie usług – np. kontenery z torrentami idą przez bramkę, reszta korzysta z normalnego wyjścia.
  • Prosty re-deploy – zmiana serwera VPN to podmiana jednego obrazu / konfiguracji, a nie dotykanie kilkunastu kontenerów.
  • Lepsza kontrola logów – masz jedno miejsce, gdzie widzisz logi tunelu i statystyki połączeń.

Jeżeli szukasz kompromisu między „jeden globalny VPN” a „VPN wszędzie”, to tu zwykle lądujesz.

Ryzyka i ograniczenia bramki VPN

Nie ma jednak darmowych obiadów. Co może pójść nie tak?

  • Single point of failure – padnie bramka, padają wszystkie kontenery z nią związane (albo tracą tunel).
  • Potrzeba dodatkowych uprawnień – kontener-bramka musi mieć więcej możliwości niż typowy kontener, co w środowiskach z twardymi politykami bezpieczeństwa bywa wyzwaniem.
  • Trudniejszy dostęp do bramki z zewnątrz – jeśli chcesz do niej wejść z LAN lub Internetu, routing robi się ciekawy, szczególnie przy masce NAT-u po obu stronach.

Warto zapytać sam siebie: czy akceptujesz pojedynczy punkt awarii w zamian za dużo prostsze zarządzanie VPN-em? Jeśli tak, bramka jest dobrym kandydatem.

Model 3 – VPN wewnątrz pojedynczego kontenera: pełna izolacja ruchu

Kiedy izolacja per kontener ma sens

Wydaje się to przesadą, ale są scenariusze, gdzie nie ma lepszego wyjścia:

  • Potrzebujesz różnych krajów / IP dla kilku aplikacji jednocześnie (np. testowanie geo-blokad).
  • Masz wymogi compliance – konkretna usługa musi używać konkretnego tunelu, logowanego osobno.
  • Chcesz izolować ryzykowne aplikacje tak, by nawet w razie błędu konfiguracji nie miały drogi na zewnątrz poza VPN.

Jeśli zadajesz sobie pytanie „czy naprawdę muszę mieć inny VPN per kontener?”, odpowiedź najczęściej brzmi: nie. Jeżeli jednak pracujesz nad bezpieczeństwem lub testami, odpowiedź szybko zmienia się na „tak, inaczej się nie da”.

Jak wygląda kontener z własnym VPN od środka

Taki kontener zwykle:

  • ma w obrazie klienta VPN (OpenVPN/WireGuard),
  • ma w tym samym obrazie lub w tym samym kontenerze aplikację właściwą (np. przeglądarkę headless, scrapera, klienta API),
  • ma uruchamiany supervisor/entrypoint, który odpala najpierw VPN, czeka na zestawienie tunelu, a potem dopiero startuje aplikację.
docker run -d --name app-vpn 
  --cap-add=NET_ADMIN 
  --device=/dev/net/tun 
  my-app-with-openvpn

Ruch:

  1. aplikacja gada przez eth0 w swoim namespace’ie,
  2. OpenVPN zmienia trasę domyślną na tun0,
  3. kontener wychodzi na świat przez tunel, niezależnie od tego, czy host używa VPN, czy nie.

Kill switch w kontenerze: jak nie wypuścić ruchu poza tunel

Bez kill switcha przy zerwaniu tunelu ruch wróci na zwykłe wyjście. Jeśli zależy ci na prywatności, to niedopuszczalne. Jak tego uniknąć wewnątrz kontenera?

Typowy schemat to:

  1. Na starcie blokujesz cały ruch wychodzący (np. iptables -P OUTPUT DROP).
  2. Dopuszczasz tylko ruch przez tunel (reguły na interfejs tun0) oraz do serwera VPN (konkretne IP/port na eth0).
  3. Po zakończeniu działania aplikacji kontener jest niszczony, więc stan iptables znika.
# przykładowy kill switch wewnątrz kontenera
iptables -P OUTPUT DROP
iptables -A OUTPUT -o tun0 -j ACCEPT
iptables -A OUTPUT -d <IP_serwera_VPN> -p udp --dport 1194 -j ACCEPT
iptables -A OUTPUT -d 127.0.0.1/8 -j ACCEPT

Jeżeli tunel padnie, ruch aplikacji nie ma którędy wyjść. Zadaj sobie pytanie: czy twoja aplikacja powinna w takiej sytuacji cicho czekać, czy raczej się wywalić? Od tego zależy logika twojego entrypointu.

Wyzwania operacyjne przy VPN per kontener

Technicznie to działa, ale operacyjnie bywa bolesne:

  • Skalowanie konfiguracji – każdy kontener ma własny zestaw certyfikatów/konfiguracji, które trzeba aktualizować (rotacja kluczy, certyfikaty wygasają w tym samym czasie).
  • Debugowanie – masz tyle miejsc potencjalnej awarii, ile kontenerów. Logi trzeba zbierać z każdego osobno.
  • Zużycie zasobów – każdy klient VPN konsumuje CPU/RAM, szyfruje własny strumień. Przy kilkudziesięciu kontenerach może to być zauważalne.

Dlatego zanim pójdziesz w ten model, jasno odpowiedz: czy zyskujesz coś, czego bramka VPN nie jest w stanie dać?

Praktyka: OpenVPN w roli kontenera-bramki (model routera)

Założenia przykładowej konfiguracji

Żeby nie błądzić, ustalmy konkretny scenariusz:

  • Host: Linux (Debian/Ubuntu), Docker z domyślną konfiguracją.
  • Chcesz mieć sieć kontenerów, które zawsze idą przez VPN (np. usługi typu „media / download”).
  • Inne kontenery mają wychodzić normalnie, bez tunelu.
  • VPN: OpenVPN klient, konfiguracja dostarczona przez dostawcę lub własny serwer.

Gdzie jesteś teraz? Masz już gotowy plik .ovpn, czy dopiero wybierasz dostawcę? Od tego zależy, czy w obrazie bramki musisz generować config, czy tylko go podmontować.

Tworzenie sieci Docker dla ruchu przez VPN

Definicja sieci bridge dla kontenerów „za VPN-em”

Na początek przyda się osobna sieć dla kontenerów, które mają wychodzić przez bramkę. Dzięki temu od razu widzisz, kto jest „za VPN-em”, a kto nie.

# sieć dla ruchu tunelowanego
docker network create 
  --driver bridge 
  --subnet 172.30.0.0/16 
  vpn_net

Masz już jakieś inne sieci bridge z ustawionymi podsieciami? Sprawdź, czy nie nachodzisz na istniejące adresacje:

docker network ls
docker network inspect <nazwa_sieci>

Jeśli masz VPN na hoście, upewnij się też, że podsieć VPN nie pokrywa się z podsiecią vpn_net, bo inaczej routing szybko zrobi się nieczytelny.

Budowa obrazu bramki OpenVPN

Kolejny krok to obraz, który po uruchomieniu:

  • startuje klienta OpenVPN na podstawie podanego pliku .ovpn,
  • włącza przekazywanie IP (ip_forward),
  • konfiguruje NAT z podsieci Docker na interfejs tunelu (tun0),
  • utrzymuje się przy życiu tak długo, jak jest potrzebny.

Jeżeli masz już gotowy obraz z klientem OpenVPN – możesz go użyć. Jeżeli nie, prosty szkic może wyglądać tak:

FROM debian:stable-slim

RUN apt-get update && 
    apt-get install -y --no-install-recommends 
      openvpn iproute2 iptables ca-certificates && 
    rm -rf /var/lib/apt/lists/*

# katalog na konfiguracje i klucze
RUN mkdir -p /etc/openvpn

# prosty entrypoint
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

Entry point dla bramki: OpenVPN + NAT

Skrypt startowy bramki odpowiada za „magiczne” rzeczy: tunel, NAT i trasy. Jak go zbudować, żeby nie wpaść w pułapki?

#!/bin/sh
set -e

# 1. Włącz przekazywanie IP w kontenerze
echo 1 > /proc/sys/net/ipv4/ip_forward

# 2. Ustaw NAT z naszej podsieci na tunel VPN
# Zakładamy, że kontenery są w podsieci 172.30.0.0/16
iptables -t nat -A POSTROUTING -s 172.30.0.0/16 -o tun0 -j MASQUERADE

# (opcjonalnie) Zabezpiecz przychodzący ruch do bramki,
# np. akceptuj tylko z sieci dockera
iptables -A INPUT -s 172.30.0.0/16 -j ACCEPT

# 3. Start OpenVPN z przekazaną konfiguracją
# Oczekujemy, że config będzie podmontowany jako /etc/openvpn/client.ovpn
openvpn --config /etc/openvpn/client.ovpn

Czy twoja podsieć to na pewno 172.30.0.0/16? Jeżeli zmienisz ją przy tworzeniu sieci, dopasuj ją tutaj. Jeżeli planujesz kilka takich sieci, rozważ parametryzację skryptu przez zmienne środowiskowe, np. VPN_SUBNET.

Uruchomienie bramki z konfiguracją OpenVPN

Gdy masz już obraz (nazwijmy go my-openvpn-gateway) i plik client.ovpn od dostawcy, możesz zestawić bramkę:

docker run -d --name vpn-gateway 
  --cap-add=NET_ADMIN 
  --device=/dev/net/tun 
  --network vpn_net 
  --ip 172.30.0.2 
  -v /path/to/client.ovpn:/etc/openvpn/client.ovpn:ro 
  my-openvpn-gateway

Jeśli twoja konfiguracja używa dodatkowych plików (certyfikaty, klucze), podmontuj cały katalog, w którym leżą:

-v /path/to/openvpn-config-dir:/etc/openvpn:ro

Sprawdź, czy OpenVPN wystartował poprawnie:

docker logs -f vpn-gateway

W logach powinny pojawić się linie o zestawieniu tunelu (Initialization Sequence Completed). Nie widzisz ich? Zatrzymaj się w tym miejscu i dociągnij konfigurację VPN do końca, zanim dołączysz kolejne kontenery.

Podłączanie kontenerów-aplikacji do bramki

Masz już bramkę w sieci vpn_net. Teraz kolejne pytanie: chcesz, żeby wszystkie nowe kontenery w tej sieci same z siebie używały bramki, czy wolisz mieć pełną kontrolę nad routingiem ręcznie?

Wariant A: dedykowany entrypoint w kontenerze-aplikacji

Najbardziej przewidywalne podejście to własny entrypoint, który przestawia trasę domyślną na IP bramki. Przykład prostego entrypointu:

#!/bin/sh
set -e

GATEWAY_IP="${GATEWAY_IP:-172.30.0.2}"

# Usuń starą trasę domyślną, jeśli istnieje
ip route del default || true

# Ustaw domyślną trasę przez bramkę VPN
ip route add default via "$GATEWAY_IP"

# (opcjonalnie) sprawdź, czy trasa działa
ip route

# Start właściwej aplikacji
exec "$@"

Przy takim podejściu uruchamiasz kontener mniej więcej tak:

docker run -d --name downloader 
  --network vpn_net 
  --ip 172.30.0.10 
  --cap-add=NET_ADMIN 
  -e GATEWAY_IP=172.30.0.2 
  my-downloader-image

Zauważ dodatkową zdolność NET_ADMIN – aplikacja musi mieć możliwość zmiany tras. Czy możesz z tym żyć w swoim modelu bezpieczeństwa? Jeśli nie, spójrz na kolejny wariant.

Wariant B: kontrola tras „z boku” (nsenter / init skrypt)

Czasem nie chcesz dawać kontenerowi uprawnień sieciowych, a jednocześnie zależy ci na trasie przez bramkę. Wtedy możesz zmieniać routing z poziomu hosta, używając nsenter:

# id procesu PID (np. 1) w kontenerze
PID=$(docker inspect -f '{{.State.Pid}}' downloader)

# wejdź do namespace'u sieci kontenera
nsenter -t "$PID" -n sh -c "
  ip route del default || true
  ip route add default via 172.30.0.2
"

Masz już u siebie nsenter (pakiet util-linux)? Jeśli nie – doinstaluj go na hoście. Takie podejście dobrze sprawdza się w skryptach automatyzujących: kontener wstaje, a potem osobny proces konfiguruje mu trasę.

Testowanie ruchu przez bramkę

Gdy pierwszy kontener jest podłączony do vpn_net i ma ustawioną trasę przez bramkę, warto go „przepytać”. Czego chcesz się dowiedzieć jako pierwszego? Zazwyczaj:

  • czy kontener w ogóle ma dostęp do Internetu,
  • jakie widzi IP zewnętrzne,
  • czy DNS działa po tunelu.

Najprościej w kontenerze-aplikacji odpalić narzędzia diagnostyczne (jeżeli ich nie ma, podepnij się tymczasowo kontenerem pomocniczym):

docker run --rm -it 
  --network vpn_net 
  --ip 172.30.0.99 
  byrnedo/alpine-curl 
  sh

# wewnątrz
ip route
curl https://ifconfig.io
nslookup google.com 8.8.8.8

Adres IP w curl powinien odpowiadać temu, co daje ci dostawca VPN, a nie IP twojego hosta. Jeśli widzisz IP hosta – coś jest nie tak z NAT-em lub trasami.

Konfiguracja DNS dla kontenerów za VPN-em

Jeśli dostawca VPN podaje własne serwery DNS, dobrze jest, by kontenery za bramką korzystały z tych samych. Jak do tego podejść?

Jedna ścieżka to wymuszenie serwera DNS per kontener:

docker run -d --name downloader 
  --network vpn_net 
  --dns 10.8.0.1 
  my-downloader-image

Tu 10.8.0.1 to przykładowy DNS wewnątrz sieci VPN (sprawdź, co podaje twój dostawca). Druga ścieżka to postawienie małego forwardera DNS (np. dnsmasq) wewnątrz bramki albo obok niej w tej samej sieci i wskazywanie go jako DNS w kontenerach.

Masz już gdzieś centralny serwer DNS, z którym chcesz to połączyć? Jeżeli tak, rozważ, czy cały jego ruch też powinien iść przez VPN, czy ma być rozdzielony (split-DNS).

Split-tunneling w modelu bramki

Dotąd szliśmy w kierunku „wszystko przez tunel”. Bywa jednak, że nie chcesz, by cały ruch kontenerów przechodził przez VPN. Np. aplikacja ma łączyć się przez tunel tylko do jednego API, a resztę ruchu kierować zwykłą drogą.

Jak to osiągnąć w modelu bramki?

  • zeznać w kontenerze (ip route), które podsieci lub hosty mają iść przez bramkę,
  • w bramce zadbać, by NAT dotyczył tylko tych podsieci, które faktycznie chcesz tunelować.

Przykład: tylko ruch do 203.0.113.10 ma iść przez VPN, reszta bezpośrednio.

# w kontenerze-aplikacji

# zostaw domyślną trasę przez docker0 (host)
# dodaj trasę do konkretnego IP przez bramkę VPN
ip route add 203.0.113.10 via 172.30.0.2

W bramce możesz wtedy zawęzić NAT:

# tylko ruch do 203.0.113.10 ma być maskowany na tun0
iptables -t nat -A POSTROUTING -d 203.0.113.10 -o tun0 -j MASQUERADE

Czy potrzebujesz takiego rozdzielenia ruchu, czy raczej scenariusza „idź przez tunel albo wcale”? To pytanie często rozstrzyga, ile logiki wkładasz w routing.

Ograniczanie „wycieku” ruchu poza VPN na poziomie bramki

Jeżeli zależy ci na tym, by kontenery z sieci vpn_net nigdy nie wychodziły na świat bez tunelu, możesz zrobić „kill switch” na poziomie bramki, podobnie jak w modelu per kontener.

Idea jest prosta: bramka nie powinna routować ruchu z 172.30.0.0/16 inną drogą niż przez tun0. Jak to wymusić?

# w bramce, po starcie ip_forward

# domyślnie drop wszystko z sieci vpn_net na zewnątrz
iptables -P FORWARD DROP

# akceptuj tylko ruch z vpn_net, który wychodzi na tun0
iptables -A FORWARD -s 172.30.0.0/16 -o tun0 -j ACCEPT
iptables -A FORWARD -d 172.30.0.0/16 -i tun0 -m state 
  --state ESTABLISHED,RELATED -j ACCEPT

# (opcjonalnie) pozwól na ruch lokalny w sieci vpn_net,
# jeżeli kontenery mają się widzieć nawzajem
iptables -A FORWARD -s 172.30.0.0/16 -d 172.30.0.0/16 -j ACCEPT

Jeżeli tun0 zniknie (tunel padnie), reguły zostaną, ale ruch nie będzie miał interfejsu wyjściowego – w praktyce stanie. Lepsze to niż nagły powrót na normalne wyjście hosta.

Integracja z docker-compose: opis całej topologii w jednym pliku

Jeśli już wiesz, że to nie jest jednorazowy eksperyment, a infrastruktura ma żyć dłużej, rozsądnie jest opisać wszystko w docker-compose.yml. Czy już go używasz, czy dopiero się przymierzasz?

Prosty szkic może wyglądać tak:

version: "3.8"

services:
  vpn-gateway:
    image: my-openvpn-gateway
    container_name: vpn-gateway
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    networks:
      vpn_net:
        ipv4_address: 172.30.0.2
    volumes:
      - ./openvpn:/etc/openvpn:ro
    restart: unless-stopped

  downloader:
    image: my-downloader-image
    container_name: downloader
    depends_on:
      - vpn-gateway
    networks:
      vpn_net:
        ipv4_address: 172.30.0.10
    cap_add:
      - NET_ADMIN
    environment:
      - GATEWAY_IP=172.30.0.2
    restart: unless-stopped

networks:
  vpn_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/16

W takim układzie bramka i aplikacja startują razem, a sieć vpn_net jest deklaratywnie opisana. W entrypointach kontenerów możesz dodać prostą pętlę, która czeka, aż tun0 w bramce będzie gotowy (np. przez prosty ping do zewnętrznego hosta).

Monitorowanie stanu tunelu VPN

Jak najszybciej dowiedzieć się, że tunel padł, zanim aplikacja narobi szkód lub zacznie się dziwnie zachowywać? Masz kilka opcji:

  • sprawdzanie procesu OpenVPN w bramce (czy w ogóle żyje),
  • monitoring trasy do znanego hosta (np. ping do IP dostawcy),
  • prosty healthcheck dockera, który uznasz za „OK”, jeśli tunel działa.