Docker Networking mit und ohne Isolation

Docker Networking kann isolieren, muss aber nicht. Beispiele für verschiedene Anwendungsfälle.

Docker Networking mit und ohne Isolation

In Docker können verschiedene Netzwerk-Konfigurationen vorgenommen werden. Je nach Anwendungsfall bieten diese Vor- und Nachteile, die hier genauer beleuchtet werden sollen. Folgende Szenarien werden hier behandelt (Teilmenge der Möglichkeiten!):

  • Host-Network (unisoliert)
  • default Bridge "docker0" (isoliert)
  • User-defined bridge networks "brX" (isoliert)

Die hier getätigten Aussagen gelten für Standard-Einstellungen und können bei Änderungen an Docker / Firewall (oder Betriebssystem) durchaus abweichen!


Host Network (unisoliert)

Bei Verwendung des Host Networks wird der Container nicht isoliert und erhält den Zugriff zur Netzwerk-Umgebung des Host-Systems. Anwendungen innerhalb des Containers können somit direkt an den verfügbaren Ports des Hosts lauschen.
Ein Docker-Proxy entfällt hierbei, kann und braucht nicht konfiguriert werden!

Run-Command

docker run --network host --name mariadb --restart unless-stopped -v /srv/mariadb/:/var/lib/mysql mariadb:latest

Docker-Compose

File: docker-copose.yml

version: "3"
services:
  mariadb:
    image: mariadb:latest
    container_name: mariadb
    network_mode: host
    volumes:
      - /srv/docker/containers/mariadb/data:/var/lib/mysql
    restart: unless-stopped

Docker default Bridge docker0 (isoliert)

Erstellt man einen Docker-Container ohne genauere Netzwerk-Angaben, wird dieser in der Regel in die default Bridge (docker0) eingehangen und ist dort als isolierter Container in der Lage Netzwerkverbindungen ausgehend aufzubauen, selbst aber nicht von außen erreichbar. Möchte man Netzwerk-Zugriffe auf die Anwendung im Container ermöglichen, kann der Docker-Proxy den Traffic in den Container leiten. Hier Beispiele für die diverse Konfigurationen des Docker Proxies. Das Port-Mapping (-p) kann mehrfach konfiguriert werden!

Bindung Docker-Proxy an :3306
Der Proxy lauscht auf allen Interfaces und kann auch von externen Clients kontaktiert werden!

docker run -p 3306:3306 --name mariadb --restart unless-stopped -v /srv/mariadb/:/var/lib/mysql mariadb:latest

Bindung Docker-Proxy an 127.0.0.1:3306
Der Proxy lauscht nur lokal und lokale System Anwendungen können diesen erreichen.

docker run -p 127.0.0.1:3306:3306 --name mariadb --restart unless-stopped -v /srv/mariadb/:/var/lib/mysql mariadb:latest

Bindung Docker-Proxy an 192.168.111.5:3306 und 192.168.111.5:13306
Clients mit Zugriff auf die IP-Adresse des Hosts (192.168.111.5) und lokale System-Anwendungen können den Proxy erreichen.

docker run -p 192.168.111.5:13306:3306 -p 192.168.111.5:3306:3306 --name mariadb --restart unless-stopped -v /srv/mariadb/:/var/lib/mysql mariadb:latest

Den Zugang zum Internet erhält der Container mittels iptables Firewall Regeln die das Forwarding von docker0 ausgehend erlauben und mittels SNAT (masquerading) die Absender-IP in die des Hosts umschreibt (siehe Unten).

Forwarding (Routing)

Fowarding beschreibt Traffic der nicht für lokale Interfaces (IP-Adressen) bestimmt ist und geroutet werden muss. Docker setzt diese Regeln und das Standardverhalten auf DROP! Dies kann in manchen Fällen funktionierendes Routing unterbrechen!

> iptables -nvL

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
    
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Masquerading (Source NAT)

Hiermit werden alle Pakete dessen Absender-Adresse aus dem Netz 172.17.0.0/16 kommen und nicht an Container (docker0) geschickt werden, maskiert. Die hierbei verwendete IP-Adresse entspricht der ersten auf dem ausgehenden Interface. Dies kann sowohl eine öffentliche zum Internet, als auch eine private im internen Netz sein! Letztlich wird der Empfänger nicht die IP-Adresse des Containers, sondern des Hosts sehen.

> iptables -t nat -nvL

Chain POSTROUTING (policy ACCEPT 9 packets, 977 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0

User-defined bridge networks (isoliert)

Möchte man Container auf Ethernet-Ebene (Layer2) voneinander isolieren, kann es ratsam sein, ein neues Docker Netzwerk anzulegen. Dies resultiert in eine neue Bridge und neuen IP-Adressbereich, aus dem Container eine IP-Adresse zugewiesen bekommen.

Im Gegensatz zur default Bridge kann jeder Container eine vorab festgelegte IP-Adresse erhalten. Dies bietet die möglichkeit die Firewall genauer zu definieren und Kommunikation zwischen Containern individuell einzustellen.

Anlegen des Docker Networks

Der Host erhält die IP-Adresse 192.168.111.1 und bindet diese auf der neuen Bridge br_docker. Der Name der Bridge und des Docker Networks können unterschiedlich lauten!

docker network  create --gateway 192.168.111.1 --subnet 192.168.111.0/24 --opt com.docker.network.bridge.name=br_docker my_cust_net

Docker Container mit spezifischer IP-Adresse

Die gewählte IP-Adresse muss sich hierbei im Subnetz des Docker Networks befinden!

Run-Command

docker run --name test --network my_cust_net --ip 192.168.111.99 -ti --rm --entrypoint /bin/bash ubuntu:latest

Docker-Compose

Der Netzwerkname (default) ist für die Referenzierung innerhalb eines Services und kann beliebig gewählt werden. Aufgrund des external Keys ist docker-compose klar, dass es kein eigenes Netzwerk anlegen und das vorhandene verwenden soll. Deshalb muss dieses vorab angelegt werden!

File: docker-copose.yml

version: "3"
services:
  mariadb:
    image: mariadb:latest
    container_name: mariadb
    volumes:
      - /srv/docker/containers/mariadb/data:/var/lib/mysql
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 192.168.111.99

  mariadb2:
    image: mariadb:latest
    container_name: mariadb2
    volumes:
      - /srv/docker/containers/mariadb/data:/var/lib/mysql
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 192.168.111.250
        
networks:
  default:
    external:
      name: my_cust_net

Quellen / Dokumentation

https://www.docker.com/
https://docs.docker.com/network/