Docker - współdzielenie sieci

Mało znanym wariantem w Dockerze jest współdzielenie przestrzeni nazw interfejsów sieciowych. Mówiąc nieco prościej - sytuacja w której 2 różne kontenery od strony użytkownika używają tego samego “wirtualnego” interfejsu sieciowego.

To, że nie jest to wariant dobrze znany i opisany w sumie nie powinno dziwić - to, że możemy związać dowolny port w kontenerze z jakimś portem hosta w większości wypadków jest mechanizmem wystarczającym.

Do czego to w takim razie użyć? Pracując z sieciami wykorzystującymi Tailscale lub Netbird-a trafiłem jednak na sytuację w której było dla mnie niezbędne żeby uruchomiona w kontenerze usługa korzystała z tego samego interfejsu co Tailscale / Netbird (a tym samym była dostępne w ramach Tailscale / Netbirda), a korzystanie z sieci hosta (network_mode: host - co jest dośc popularnym rozwiązaniem) nie wchodziło w grę gdyż wymagane przez usługę porty były już zajęte.

Macvlan wraz z dodatkowymi opcjami, które daje network_mode uratowały sytuację - można w nich podać nazwę konkretnej usługi / kontenera z której interfejsu chcemy skorzystać. Wystarczy postąpić bardzo podobnie jak w przypadku trybu host, tyle że wpisując tam skąd “pożyczamy” interfejs sieciowy np.

network_mode: service:<NAZWA_USŁUGI>

Działa to bez problemu z plikami compose - co można zobaczyć np. na poniższym przykładzie (w którym darowałem już sobie macvlan):

networks:
  shared-net:

services:
  nginx1:
    container_name: nginx1
    image: nginx:latest
    environment:
      - NGINX_PORT=6080
      - SERVER_NAME=nginx1
    networks:
      - shared-net
    volumes:
      - ./nginx-conf.template:/etc/nginx/conf.d/nginx-conf.template
    command: /bin/bash -c "envsubst < /etc/nginx/conf.d/nginx-conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

  nginx2:
    container_name: nginx2
    image: nginx:latest
    environment:
      - NGINX_PORT=7080
      - SERVER_NAME=nginx2
    network_mode: service:nginx1
    volumes:
      - ./nginx-conf.template:/etc/nginx/conf.d/nginx-conf.template
    command: /bin/bash -c "envsubst < /etc/nginx/conf.d/nginx-conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

Kontener nginx2 korzysta w tym wypadku z interfejsu sieciowego usługi nginx1.

Chcąc uruchomić powyższy przykład i przetestować jak to działa należy stworzyć plik nginx-conf.template o np. takiej zawartości:

server {
    listen       ${NGINX_PORT};
    listen  [::]:${NGINX_PORT};
    server_name  ${SERVER_NAME};

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

Po uruchomieniu poprzez docker compose up -d można otworzyć bash-a w jednym z kontenerów poprzez:

docker container exec -it nginx2 bash

Do sprawdzenia czy w ramach localhost mamy dostępnego nginx zarówno na porcie 6080 (z kontenera nginx1) jak i 7080 (z kontenera nginx2) można skorzystać z curl-a:

curl http://localhost:6080
curl http://localhost:7080

Znacznie lepszą informację da jednak użycie docker container inspect z poziomu hosta:

docker container inspect nginx2

Warto zwrócić uwagę na sekcję “NetworkMode” i “NetworkSettings”. W moim wypadku w pierwszej było widać:

"NetworkMode": "container:cd15bb65e8af98ab0a383269277b7e3710b3d85189869742202884cce4f46f66"

Z kolei ta druga zawiała puste wpisy - co tym lepiej pokazuje, że faktycznie kontener nginx2 nie ma “własnego” interfejsu sieciowego.

Odnośniki

https://docs.docker.com/reference/compose-file/services/

https://docs.docker.com/reference/compose-file/services/#network_mode

Obrazek(-ki):

Alina Grubnyak @Unsplash

Przemek