Dlaczego dwa kontenery na tym samym hoście domyślnie się nie widzą

Uruchamiasz Postgresa i swoje API zwykłym docker run, a API nie potrafi połączyć się z bazą danych, mimo że oba kontenery działają na tej samej maszynie.

1
2
docker run -d --name db postgres:16
docker run -d --name api myapp

Oba trafiają na domyślną sieć bridge, do której Docker podłącza każdy kontener Docker, jeśli nie skonfigurujesz tego inaczej. Dostają adresy IP, ale nie dostają sposobu na odnalezienie się po nazwie — API musiałoby mieć na sztywno wpisany wewnętrzny adres IP bazy danych, który zmienia się przy każdym restarcie kontenera.

Sieć Docker rozwiązuje ten problem. To wirtualny switch, do którego podłączają się kontenery, z własną pulą adresów IP i własnym DNS. Tworzysz jedną i nazwy kontenerów zaczynają działać jak hostname, a ty decydujesz, kto kogo widzi.

Który sterownik sieci wybrać: bridge, host czy overlay

Docker ma kilka wbudowanych sterowników sieci. Trzy z nich pokrywają niemal wszystko, z czym spotkasz się na pojedynczym hoście albo w swarmie:

SterownikZasięgKontenery mają własny IPDNS po nazwieTypowe zastosowanie
bridge (domyślny)jeden hosttaknie (tylko IP)to, co dostajesz bez flagi --network
bridge (user-defined)jeden hosttaktakdomyślny wybór dla lokalnego developmentu i wdrożeń na jednym hoście
hostjeden hostnie — dzieli stos sieciowy hostan/dusługi o dużej przepustowości, narzędzia potrzebujące realnego widoku sieci hosta
overlaywiele hostów (swarm)taktakusługi rozłożone na więcej niż jednym hoście Docker

Jest jeszcze none, który daje kontenerowi tylko interfejs loopback i nic więcej. Przydatne dla zadania wsadowego, które i tak nie korzysta z sieci.

Każdy nowy docker run trafia na domyślny bridge. Działa, ale routuje wyłącznie po IP; stara flaga --link istniała, żeby załatać brak nazw, a Docker uznaje ją dziś za przestarzałą. User-defined bridge robi to samo, tylko z wbudowanym DNS. To ta opcja, której potrzebujesz.

Jak utworzyć sieć Docker i podłączyć do niej kontenery

1
docker network create app-net

To prywatny bridge z własną podsiecią, odizolowany od domyślnego bridge i od każdej innej sieci user-defined, którą stworzysz. Podłącz kontenery przy starcie:

1
2
docker run -d --name db --network app-net -e POSTGRES_PASSWORD=devpass postgres:16
docker run -d --name api --network app-net -p 8080:8080 myapp

Wewnątrz api połączenie z db:5432 po prostu działa. Bez szukania IP, bez żonglowania zmiennymi środowiskowymi: wbudowany serwer DNS Docker rozwiązuje nazwę kontenera db na jego aktualny adres w app-net i aktualizuje to mapowanie, gdy kontener wraca po restarcie z nowym IP. Docker Compose działa na tym samym mechanizmie. docker compose up tworzy sieć dla stacku, a każda usługa dociera do pozostałych po nazwie usługi.

Możesz też podłączyć kontener, który już działa, bez jego restartowania:

1
2
docker network connect app-net some-container
docker network disconnect app-net some-container

Kontener może być podłączony do więcej niż jednej sieci naraz. Tak dajesz usłudze sieć wystawioną publicznie i osobną, prywatną, dzieloną tylko z kontenerami backendu stojącymi za nią.

Dlaczego publikowanie portu to nie to samo, co komunikacja między kontenerami

-p 8080:8080 mapuje port 8080 hosta na port kontenera, więc możesz otworzyć API w przeglądarce pod localhost:8080. To nie mówi nic o tym, czy api dociera do db. Ruch między kontenerami w tej samej sieci user-defined zostaje wewnątrz, niezależnie od tego, czy porty są opublikowane.

Publikuj tylko te porty, do których musi dotrzeć coś spoza hosta Docker. Baza danych, która rozmawia wyłącznie z API, nie potrzebuje -p 5432:5432: zostaw ją bez publikacji, a wtedy ani host, ani internet nie połączą się z nią bezpośrednio. api nadal do niej dotrze, bo obie dzielą app-net.

Kiedy używać –network host, a kiedy nie

1
docker run --rm -d --network host --name my_nginx nginx

Z --network host kontener pomija własny namespace sieciowy i wiąże się bezpośrednio z interfejsami hosta. Bez NAT, bez bridge, bez mapowania portów: -p i -P są ignorowane, a Docker wypisuje ostrzeżenie, jeśli mimo to je podasz. Zdejmuje to niewielki narzut na pakiet i daje kontenerowi realny widok interfejsów sieciowych hosta — dokładnie tego potrzebują narzędzia do monitorowania sieci i oprogramowanie wymagające szerokiego, ciągłego zakresu portów.

Zdejmuje też izolację, którą daje bridge. Kontener może zająć dowolny wolny port hosta i widzi wszystko, co dzieje się w sieci hosta. Na Linuksie działa to w pełni. Docker Desktop dla Mac i Windows wymaga jawnego włączenia (dostępne od Docker Desktop 4.34), bo sieć maszyny wirtualnej, w której działa Docker, nie jest siecią hosta. Kontenery Windows w ogóle tego nie obsługują. Dla większości usług user-defined bridge z opublikowanymi portami robi to samo, przy mniejszej ekspozycji.

Kiedy potrzebujesz sieci overlay zamiast bridge

Sieci bridge i host kończą się na granicy jednego hosta Docker. Uruchamiasz Docker Swarm na kilku maszynach, a usługa na hoście A potrzebuje sieci overlay, żeby dotrzeć do usługi na hoście B:

1
docker network create -d overlay --attachable app-overlay

Overlay hermetyzuje ruch kontenerów i routuje go między demonami Docker na każdym węźle swarma, więc kontenery na różnych maszynach fizycznych czy wirtualnych rozwiązują się po nazwie dokładnie tak samo, jak w bridge. Flaga --attachable pozwala dołączyć do niej też samodzielnym kontenerom; bez niej mogą się łączyć tylko usługi swarma. Jeśli nie uruchamiasz swarma ani innego orkiestratora multi-host, pomiń ten sterownik. Sieć bridge na jednym hoście wystarcza do stacków Compose i lokalnych ustawień.

Jak sprawdzić i zdebugować sieć Docker

1
docker network ls

Wszystkie sieci na hoście, w tym bridge, host i none, które Docker zawsze tworzy.

1
docker network inspect app-net

Podsieć, bramę i każdy aktualnie podłączony kontener z jego IP w tej sieci. To pierwsze miejsce, w które warto zajrzeć, gdy jeden kontener nie dociera do drugiego. Sprawdź sekcję Containers: jeśli jednego z nich brakuje, nie jest podłączony do tej sieci, a docker network connect go tam umieszcza.

Żeby przetestować rozwiązywanie DNS z wnętrza działającego kontenera:

1
docker exec api getent hosts db

Zwrócony adres IP oznacza, że rozwiązywanie nazw działa, a problem tkwi w aplikacji — zły port albo usługa jeszcze nie nasłuchuje. Brak odpowiedzi oznacza, że oba kontenery nie są w tej samej sieci. Potwierdź to przez docker network inspect albo sprawdź w każdym kontenerze, do czego jest podłączony:

1
docker inspect <container> --format '{{ .NetworkSettings.Networks }}'

Ile sieci potrzebujesz na jeden stack aplikacji

Domyślny bridge dostajesz za darmo. Nie na nim powinieneś budować. Uruchom docker network create <app>-net dla każdego stacku aplikacji, podłącz do niej każdy kontener, którego ten stack potrzebuje, a --network host zachowaj dla rzadkiej usługi, która naprawdę potrzebuje stosu sieciowego hosta.

Gdy zaczynasz ręcznie zarządzać więcej niż parą kontenerów, przestań to robić ręcznie. Docker Compose tworzy sieć i podłącza do niej kontenery za ciebie, na podstawie jednego pliku YAML. Woluminy Docker pokrywają drugą połowę problemu przetrwania restartu: dane, które te kontenery zapisują.

Powiązane artykuły