Netzwerkverwaltung unter Docker

Linux

Bei der Erstinstallation bringt Docker auch ein Setup für das Netzwerk mit. Auch wenn Container aus Sicht des Speicher- und Rechtemanagements etc. isoliert sein sollen, brauchen sie doch eine Schnittstelle zur Kommunikation nach außen oder untereinander – und das ist das Netzwerk. Dabei soll durchaus unterschieden werden, ob ein Container die Netzwerkeigenschaften des Hosts übernimmt , nur mit bestimmten Containern unter sich oder gar nicht mit der Außenwelt kommunizieren soll.

Grundsätzlich gibt es noch eine vierte Art der Netzwerkkommunikation, nämlich wenn mehrere Hosts in einem Docker-Schwarm kommunizieren sollen, aber das soll uns vorerst nicht weiter interessieren (Overlay-Netzwerk).

Doch zuerst betrachten wir uns die Netzwerkkonfiguration des Hosts ohne Docker:

root@Docker ~: ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:58:8b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 86315sec preferred_lft 86315sec
    inet6 fe80::a00:27ff:fe56:588b/64 scope link 
       valid_lft forever preferred_lft forever

root@Docker ~: ip route show
default via 10.0.2.2 dev enp0s3 
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 

root@Docker ~: ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=55 time=26.5 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=55 time=20.8 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=55 time=21.9 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 20.830/23.086/26.521/2.471 ms

Du solltest grundsätzlich 2 Netzwerkkarten sehen, das Loopback (interne Schnittstelle des Hosts) und eine physikalische Karte. In meinem Code-Schnipsel ist ein Auszug des Docker-Hosts aus meiner virtuellen Labor-Umgebung zu sehen, das erkennst du zum einen an den MAC-Adressen (Virtualbox) und ggf. auch an der IP-Adresse der „physikalischen“ Netzwerkkarte enp0s3 (Virtualbox NAT). Aber das es sich hier lediglich um eine Virtualbox-Umgebung handelt, ist nebensächlich. Auf einem physikalischen Server oder vServer im Internet ist das ähnlich. Die enp0s3 (kann bei dir anders heißen) ist hier die Netzwerkarte, die mit der Außenwelt (LAN/WAN) kommuniziert, sie hat derzeit die IP-Adresse 10.0.2.15/24 und eine funktionale Verbindung zum Internet. Als Default Gateway ist die 10.0.2.2 über enp0s3 eingetragen, das ist die IP-Adresse von NAT-Interface des Host, der natürlich auch eine funktionale Verbindung zum Internet haben muss, sonst wäre der Ping nicht erfolgreich.

Nach der Installation und dem automatischen Start von Docker ist eine weitere Netzwerkkarte verfügbar, die die Grundlage unseres virtuellen Netzwerkes bildet – docker0

root@Docker ~: ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:58:8b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 84994sec preferred_lft 84994sec
    inet6 fe80::a00:27ff:fe56:588b/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:13:41:bc:93 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

root@Docker ~: ip route show
default via 10.0.2.2 dev enp0s3 
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 

root@Docker ~: docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

root@Docker ~: docker network list
NETWORK ID     NAME      DRIVER    SCOPE
1a4af9aa8f95   bridge    bridge    local
1c1adf3d581b   host      host      local
23752a841ea3   none      null      local

docker0 ist die virtuelle Netzwerkschnittstelle der Docker-Instanz, an die sich weitere interne Netzwerkschnittstellen (bridge, host, none) der Docker-Instanz binden. Per default hat die Docker-Instanz die IP-Adresse 172.17.0.1/16. Ohne aktive Container ist das Interface down, die Route in dieses Subnetz inaktiv. Ein Container ist grundsätzlich Mitglied einer oder mehrerer Docker-Netzwerke (siehe docker network list), die Docker-Netzwerke kommunizieren mit docker0 und docker0 kann widerrum mit dem Host kommunizieren. Damit ungewollte Kommunikation unterbunden wird, installiert Docker bei der Erstinstallation gleich passende iptables-Regeln mit.

root@Docker ~: iptables -nvL
Chain INPUT (policy ACCEPT 14 packets, 732 bytes)
 pkts bytes target     prot opt in     out     source               destination         

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 OUTPUT (policy ACCEPT 18 packets, 892 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER (2 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 (2 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  

Um den Umgang mit dem Netzwerk näher erläutern zu können, werde ich die folgende Containertopologie nutzen:

    AUßENWELT 10.0.2.2/24
        |
        |
+-------+-------------------------------------------------------------------+
|      NAT   10.0.2.15/24                                       host Docker |
|                                                                           |
|    ---+-------------------------------------------+------------------     |
|       |                                           |                       |
|       |                                           |                       |
|       |                                           |                       |
|  +----+---------------------------+         +-----+------------------+    |
|  |    |                    bridge |         |     |           bridge2|    |
|  |  +-+--------+                  |         |  +--+-------+          |    |
|  |  |172.17.0.1|                  |         |  |172.18.0.1|          |    |
|  |  |docker0   |                  |         |  |br-b011...|          |    |
|  |  +--+-------+                  |         |  +--+-------+          |    |
|  |     |                          |         |     |                  |    |
|  |     |        172.17.0.0/16     |         |     |  172.18.0.0/16   |    |
|  |  ---+-+-----------------+---   |         |  ---+-+--------------  |    |
|  |       |                 |      |         |       |                |    |
|  |       |                 |      |         |       |                |    |
|  |       |                 |      |         |       |                |    |
|  |    +--+-------+ +-------+--+   |         |    +--+-------+        |    |
|  |    |172.17.0.2| |172.17.0.3|   |         |    |172.18.0.2|        |    |
|  |    |busybox1  | |busybox2  |   |         |    |busybox3  |        |    |
|  |    +----------+ +----------+   |         |    +----------+        |    |
|  |                                |         |                        |    |
|  +--------------------------------+         +------------------------+    |
|                                                                           |
+---------------------------------------------------------------------------+

Erläuterung: Der Host Docker hängt weiterhin mit seiner physikalischen Schnittstelle im LAN/WAN (IP-Adresse 10.0.2.15/24. Die Docker bringt per Default die virtuelle Netzwerkkarte docker0 mit, welche in einer Bridge eingebunden ist und die IP-Adresse 172.17.0.1/16 hat. Weiterhin sind an diese Bridge die Container busybox1 und busybox2 gebunden. Busybox ist ein Container, welcher ein minimalistisches embedded-Linux enthält. Somit sind wir in der Lage, später auch IP-Adressen zu pingen und das Netzwerk auf Funktion zu testen.
Später erzeugen wir eine weitere Bridge, binden Container busybox3 an diese und testen ebenfalls alle Netzwerkverbindungen. So erarbeiten wir uns Schritt für Schritt die Arbeitsweise des Docker-Netzwerkes. Die Farben der Container oben entspricht auch den Farben der Shell-Eingaben weiter unten – also achte darauf, in welchem Terminal du gerade Eingaben machst.

Bridge – das Standard-Netzwerk unter Docker

Starten wir den ersten Container busybox1 in der Standard-Bridge (Angabe von –net bridge) kann deshalb weggelassen werden.

root@Docker ~: docker run -it --net bridge --name busybox1 --rm busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

/ # ip route show
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2 

/ # ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=54 time=22.973 ms
64 bytes from 1.1.1.1: seq=1 ttl=54 time=21.182 ms
64 bytes from 1.1.1.1: seq=2 ttl=54 time=20.545 ms
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 20.545/21.566/22.973 ms
/ # 

Okay, das Netzwerk ist funktional. Wir haben eine IP, ein Gateway und erreichen auch das Internet (hier exemplarisch 1.1.1.1). Doch wie schaut es hinter den Kulissen aus, werfen wir doch mal ein Blick auf den Docker-Host (öffne dazu ein neues Terminal und beende die Session busybox1 nicht).

root@Docker ~: docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED         STATUS         PORTS     NAMES
4d0c23103472   busybox   "sh"      7 minutes ago   Up 7 minutes             busybox1

root@Docker ~: ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:58:8b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 57158sec preferred_lft 57158sec
    inet6 fe80::a00:27ff:fe56:588b/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:42:e3:6f:c2 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:42ff:fee3:6fc2/64 scope link 
       valid_lft forever preferred_lft forever

10: vethabc42a8@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether a2:fd:e5:9c:81:f8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::a0fd:e5ff:fe9c:81f8/64 scope link 
       valid_lft forever preferred_lft forever

root@Docker ~: ip route show
default via 10.0.2.2 dev enp0s3 
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
 
root@Docker ~: brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.024242e36fc2	no		vethabc42a

Wie du mit docker ps siehst, läuft jetzt die busybox1. War ja auch zu erwarten. Aber viel interessanter ist, das es eine weitere (virtuelle) Netzwerkkarte auf dem Host gibt – vethabc42a8@if9. In der ersten Zeile steht auch, das diese an master docker0 gebunden ist (ich habe beides orange markiert). Außerdem ist das Routing über 172.17.0.1 jetzt aktiv und mit dem letzten Kommando brctl show sieht du, das diese neue Netzwerkkarte tatsächlich an docker0 gebunden ist (inkl. bridge id). Falls das letzte Kommando bei dir nicht funktioniert, dann installiere mit apt install bridge-utils auf dem Host das passende Paket einfach nach.

Das du tatsächlich NAT nutzt, siehst du, wenn du dir die Tabelle NAT der iptables anschaust:

    root@Docker ~: iptables -nvL -t nat
Chain PREROUTING (policy ACCEPT 2 packets, 1152 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 13 packets, 692 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           
        

Chain OUTPUT (policy ACCEPT 13 packets, 692 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

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

So weit – so gut. Als nächstes starten wir Container busybox2 und schauen uns ebenfalls die Netzwerkfunktionalität an (auch hier am besten ein weiteres Terminal öffnen).

root@Docker ~: docker run -it --net bridge --name busybox2 --rm busybox
/ # ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

/ # ip route show
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0 scope link  src 172.17.0.3 

/ # ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=54 time=66.460 ms
64 bytes from 1.1.1.1: seq=1 ttl=54 time=20.838 ms
64 bytes from 1.1.1.1: seq=2 ttl=54 time=21.471 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 20.838/36.256/66.460 ms

/ # ping -c 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.070 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.077 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.099 ms

--- 172.17.0.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.070/0.082/0.099 ms
/ # 

Das auch dieser Container eine IP-Adresse, ein Gateway und eine Verbindung zum Internet hat, war ja bereits zu erwarten. Allerdings kann dieser Container auch mit dem anderen Container busybox1 auf derselben Bridge kommunizieren.

Das wird auch deutlich, wenn wir uns auf dem Host nochmal die Bridge näher ansehen, es gibt jetzt 2 virtuelle Netzwerkkarten vetha… und beide sind an docker0 gebunden.

root@Docker ~: ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:58:8b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 56141sec preferred_lft 56141sec
    inet6 fe80::a00:27ff:fe56:588b/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:42:e3:6f:c2 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:42ff:fee3:6fc2/64 scope link 
       valid_lft forever preferred_lft forever

10: vethabc42a8@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether a2:fd:e5:9c:81:f8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::a0fd:e5ff:fe9c:81f8/64 scope link 
       valid_lft forever preferred_lft forever
12: vetha3b5123@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 5e:87:63:e2:7e:86 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::5c87:63ff:fee2:7e86/64 scope link 
       valid_lft forever preferred_lft forever

root@Docker ~: brctl show
bridge name	bridge id		STP enabled	interfaces

docker0		8000.024242e36fc2	no		vetha3b5123
							vethabc42a8

Ansonsten hat sich nichts verändert. Also erstellen wir uns eine zweite Bridge und starten den dritten Container busybox3 auf dieser.

root@Docker ~: docker network create bridge2
root@Docker ~: docker run -it --net bridge2 --name busybox3 --rm busybox
/ # ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
       valid_lft forever preferred_lft forever

/ # ip route show
default via 172.19.0.1 dev eth0 
172.19.0.0/16 dev eth0 scope link  src 172.19.0.2 

/ # ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=54 time=20.876 ms
64 bytes from 1.1.1.1: seq=1 ttl=54 time=23.088 ms
64 bytes from 1.1.1.1: seq=2 ttl=54 time=20.846 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 20.846/21.603/23.088 ms

/ # ping -c 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes

--- 172.17.0.2 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
/ # 

Auch hier verhält sich der dritte Container wie die anderen beiden. Er hat eine IP- Adresse, eine Default Route und kann mit der Außenwelt kommunizieren. Aber er kann nicht mit den anderen beiden Containern „sprechen“. Dir fällt bestimmt auf, das er eine andere IP-Adresse hat – 172.19.0.2/16 und sich somit in einem anderen Subnetz befindet. Schauen wir uns den Host nochmal näher an:

root@Docker ~: docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS          PORTS     NAMES
25cb66f94185   busybox   "sh"      4 minutes ago    Up 4 minutes              busybox3
b8bb63b8a0b7   busybox   "sh"      25 minutes ago   Up 25 minutes             busybox2
4d0c23103472   busybox   "sh"      41 minutes ago   Up 41 minutes             busybox1

root@Docker ~: ip adress show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:58:8b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 55268sec preferred_lft 55268sec
    inet6 fe80::a00:27ff:fe56:588b/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:42:e3:6f:c2 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:42ff:fee3:6fc2/64 scope link 
       valid_lft forever preferred_lft forever
4: br-b011af4f4b33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:e3:fb:5f:29 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 brd 172.19.255.255 scope global br-b011af4f4b33
       valid_lft forever preferred_lft forever
    inet6 fe80::42:e3ff:fefb:5f29/64 scope link 
       valid_lft forever preferred_lft forever
10: vethabc42a8@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether a2:fd:e5:9c:81:f8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::a0fd:e5ff:fe9c:81f8/64 scope link 
       valid_lft forever preferred_lft forever
12: vetha3b5123@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 5e:87:63:e2:7e:86 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::5c87:63ff:fee2:7e86/64 scope link 
       valid_lft forever preferred_lft forever
14: veth4ad43b3@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-b011af4f4b33 state UP group default 
    link/ether 7e:96:65:b4:82:90 brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::7c96:65ff:feb4:8290/64 scope link 
       valid_lft forever preferred_lft forever

root@Docker ~: brctl show
bridge name		bridge id		STP enabled	interfaces
br-b011af4f4b33	8000.0242e3fb5f29	no		veth4ad43b3
docker0			8000.024242e36fc2	no		vetha3b5123
								vethabc42a8
root@Docker ~: ip route show
default via 10.0.2.2 dev enp0s3 
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
172.19.0.0/16 dev br-b011af4f4b33 proto kernel scope link src 172.19.0.1 

Wire du erkennst, laufen alle 3 Container, außerdem gibt es gleich 2 neue Netzwerkschnittstellen. Eine weitere Bridge br-b011af4f4b33 und ein virtuelles Kabel veth4ad43b3@if13, welches an diese Bridge gebunden ist. Das verdeutlicht auch nochmal das Kommando brctl show. Außerdem gibt es jetzt eine weitere Route, damit die Außenwelt mit der busybox3 kommunizieren kann.

Okay, vielleicht fragst du dich jetzt, wenn der Host doch ein Beinchen in allen 3 Netzen hat und gleichzeitig Gateway aller dieser Netze ist, warum routet er dann nicht einfach von einem Netz ins andere. Nun, dann schauen wir uns nochmal die Firewall iptables an:

root@Docker ~: iptables -nvL
Chain INPUT (policy ACCEPT 16 packets, 840 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    5   420 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    5   420 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           
    0     0 ACCEPT     all  --  *      br-b011af4f4b33  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      br-b011af4f4b33  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  br-b011af4f4b33 !br-b011af4f4b33  0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  br-b011af4f4b33 br-b011af4f4b33  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 20 packets, 1000 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER (2 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           
    5   420 DOCKER-ISOLATION-STAGE-2  all  --  br-b011af4f4b33 !br-b011af4f4b33  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 (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    5   420 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
    0     0 DROP       all  --  *      br-b011af4f4b33  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         
    5   420 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Wow! Was ist denn hier alles im Hintergrund passiert? Wenn du mit iptables ausreichend bewandert bist, wirst du wissen, das die FORWARD-Chain eine tragende Rolle für unser Setup hat. Alles was nicht geswitched (L2) wird, also geroutet (L3) werden muss, muss durch die FORWARD-Chain der interne Firewall.
DOCKER-USER können wir hier ignorieren, da liegen bei unserem Setup noch keine Einschränkungen vor. Aber schon die 2. Zeile der FORWARD-Chain schiebt alle Pakete in die Chain DOCKER-ISOLATION-STAGE-1 und diese prüft sowohl mit der ersten als auch zweiten Zeile, dass wenn ein Paket von einer Bridge auf eine andere Bridge will, dann wird es in die Chain DOCKER-ISOLATION-STAGE-2 verwiesen und dort dann verworfen. Das erklärt, warum eine Kommunikation von einer Bridge docker0 zu einer anderen Bridge br-b011af4f4b33 und umgekeht nicht möglich ist.

Werfen wir volllständigkeitshalber noch einen Blick auf die NAT-Tabelle der iptables:

root@Docker ~: iptables -nvL -t nat
Chain PREROUTING (policy ACCEPT 7 packets, 1572 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 15 packets, 800 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           
    0     0 MASQUERADE  all  --  *      !br-b011af4f4b33  172.19.0.0/16        0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 15 packets, 800 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
    0     0 RETURN     all  --  br-b011af4f4b33 *       0.0.0.0/0            0.0.0.0/0

Hier wurde lediglich für die neue Bridge ein weiteres MASQUERADE angelegt, alles schick sozusagen.

Host – gib mir einfach alles 😉

Als nächstes schauen wir uns an, was passiert, wenn wir als Netz statt einer Bridge den Host angeben. Dazu kannst du einfach in der Shell der busybox3 ein exit ausführen und diese erneut mit –net host starten (oder du startest eine 4. Busybox, du weiß spätestens jetzt , wie das geht).

/ # exit
root@Docker ~: docker run -it --net host --name busybox3 --rm busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
    link/ether 08:00:27:56:58:8b brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83292sec preferred_lft 83292sec
    inet6 fe80::a00:27ff:fe56:588b/64 scope link 
       valid_lft forever preferred_lft forever
3: br-b011af4f4b33: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue 
    link/ether 02:42:49:73:6b:5c brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.1/16 brd 172.19.255.255 scope global br-b011af4f4b33
       valid_lft forever preferred_lft forever
    inet6 fe80::42:49ff:fe73:6b5c/64 scope link 
       valid_lft forever preferred_lft forever
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue 
    link/ether 02:42:c2:ed:82:0b brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:c2ff:feed:820b/64 scope link 
       valid_lft forever preferred_lft forever
8: veth1feb0b5@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 
    link/ether 62:b2:33:6c:77:95 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::60b2:33ff:fe6c:7795/64 scope link 
       valid_lft forever preferred_lft forever
14: veth5b3db0c@if13: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 
    link/ether da:61:3c:7f:d1:ad brd ff:ff:ff:ff:ff:ff
    inet6 fe80::d861:3cff:fe7f:d1ad/64 scope link 
       valid_lft forever preferred_lft forever

/ # ip r
default via 10.0.2.2 dev enp0s3 
10.0.2.0/24 dev enp0s3 scope link  src 10.0.2.15 
172.17.0.0/16 dev docker0 scope link  src 172.17.0.1 
172.19.0.0/16 dev br-b011af4f4b33 scope link  src 172.19.0.1 

/ # ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=55 time=21.472 ms
64 bytes from 1.1.1.1: seq=1 ttl=55 time=21.158 ms
64 bytes from 1.1.1.1: seq=2 ttl=55 time=19.896 ms
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 19.896/20.842/21.472 ms
/ # 

/ # ping -c 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.067 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.249 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.096 ms
--- 172.17.0.2 ping statistics --
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.067/0.137/0.249 ms

Oh, das ist interessant. Alle Netzwerkschnittstellen, die der Host kennt, sind auch jetzt im Container verfügbar. Somit kann der Container dieselben Ziele wie der Host erreichen, inklusive der Container der anderen Bridge. An der Firewall iptables hat sich nichts geändert, denn die im Zusammenhang stehenden Settings werden schon beim anlegen/löschen einer neuen Bridge erzeugt und die Bridge haben wir ja nicht gelöscht – wir nutzen sie nur gerade nicht.

root@Docker ~: brctl show
bridge name		bridge id		STP enabled	interfaces
br-b011af4f4b33	8000.024249736b5c	no		
docker0			8000.0242c2ed820b	no		veth1feb0b5
								veth5b3db0c

None – mehr Isolation geht wirklich nicht

Und als letztes schauen wir, was passiert, wenn du statt –net host einfach gar kein Netzwerk auswählst (also –net none).


/ # exit
root@Docker ~: docker run -it --net none --name busybox3 --rm busybox
/ # ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
/ # ip route show
/ # 

Traurig, oder? Es existiert nicht mal eine Netzwerkkarte außer dem Loopback, da kann ich mir einen Ping außer auf localhost sparen.

So und mit den Erkenntnissen wünsche ich dir noch viel Docker-Netzwerk-Spaß 😉

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert