So wundert man sich auch nicht, dass ein neu gebauter Container über Netzwerkzugriff ins Internet verfügt, um zum Beispiel Pakete zu installieren. Der folgende Artikel beschreibt, mit welchen Mitteln diese Magie funktioniert, wie sich Container verbinden lassen und welche Leistungen Docker im Bereich des Netzwerks aktuell bietet.
Der Erfolg bei der Kooperation von Docker-Containern mit dem Netz stellt sich schnell ein. Im Hintergrund zeichnet der Docker Daemon dafür verantwortlich, dass die Verbindungen im Netzwerk hergestellt sind. Die Container können mühelos aus dem Internet Pakete nachladen und untereinander Netzwerkservices nutzen. Das Publizieren von Ports der eigenen Services ist geregelt. Spätestens, wenn die eigene Anwendung in die Produktion soll oder der Zugriff auf verschiedenen Docker Hosts geregelt werden muss, stellt sich die Frage, wie Docker denn die Dienste genau bereitstellt? Die Entzauberung auf der Basis eines Linux Ubuntu 14.04 Docker Hosts wird im Folgenden erläutert. Auf der Basis einer Vagrant- und Virtualbox-Installation in Verbindung mit dem Git-Projekt kann die Übung praktisch nachvollzogen werden.
Das Netzwerk im Docker-Container
Wenn man in einem einfachen Docker-Container mit dem Befehl docker run -ti –rm ubuntu:14.04 eine Shell startet, kann man sofort überprüfen, welche Netzwerkeinstellungen vorliegen. Im Container ist auf der eth0-Schnittstelle die IP-Adresse 172.17.0.xxx/16 gebunden. Mit dem Befehl ip ro show sieht man alle IP-Pakete, die über die Default-Brücke 172.17.42.1 geroutet werden. Über diese Brücke werden auch alle IP-Pakete nach außen ins Netz bzw. Internet zugestellt (Listing 1).
Listing 1
$ sudo docker run -ti —rm ubuntu:14.04 /bin/bash root@4de56414033f:/# ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default 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 37: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether e2:47:24:55:de:d4 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::e047:24ff:fe55:ded4/64 scope link valid_lft forever preferred_lft forever root@4de56414033f:/# ip ro show default via 172.17.42.1 dev ether 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 root@4de56414033f:/# ping -c 3 www.infrabricks.de PING github.map.fastly.net (185.31.17.133) 56(84) bytes of data. 64 bytes from github.map.fastly.net (185.31.17.133): icmp_seq=1 ttl=61 time=46.2 ms 64 bytes from github.map.fastly.net (185.31.17.133): icmp_seq=2 ttl=61 time=46.8 ms 64 bytes from github.map.fastly.net (185.31.17.133): icmp_seq=3 ttl=61 time=44.5 ms --- github.map.fastly.net ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 44.555/45.891/46.823/0.968 ms
Auf dem Docker Host kümmert sich der Docker Daemon um die Magie. Bei der Installation von Docker wird eine Linux-Netzwerkbrücke docker0 angelegt. Eine Brücke erscheint wie eine eigene Netzwerkschnittstelle, im Fall von docker0 besitzt diese Brücke sogar eine eigene IP-Adresse. Man kann sich eine Brücke vorstellen wie das virtuelle Äquivalent eines Hardware-Switches, aber ohne die eigene Logik, die ein Switch natürlich hätte. Andere Netzwerkschnittstellen können an eine Brücke angeschlossen werden, und der Kernel (das Modul bridge) leitet Pakete, die auf beliebigen Schnittstellen ankommen, an alle angeschlossenen Schnittstellen weiter (Abb. 1).
Die Netzwerkmagie, sprich die Tatsache, dass ein Docker-Container auch direkt Pakete ins Internet verschicken kann, entsteht, indem der Docker Host eine entsprechende iptables-Regel erzeugt. In der Postrouting Chain gibt es einen Masquerade-Eintrag. Dabei setzt der Host in den Paketen, die für die Außenwelt bestimmt sind, die eigene IP der ausgehenden Schnittstelle ein, merkt sich diese Verbindung, sodass die Antworten später auch zur Schnittstelle eth0 des Docker-Containers zurückgeroutet werden (Abb. 1, Listing 2). Im Artikel „ Besuch im Maschinenraum“ wird beschrieben, wie eine solche Linux Bridge mit Bordmitteln angelegt wird.
Listing 2
$ sudo /sbin/iptables -L -n -t nat [....] Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
In Listing 3 wird mit dem IP-Kommando die Bridge docker0 als Schnittstelle mit ihrer IP angezeigt. Das Werkzeug brctl zeigt die Schnittstellen an, die an die Bridge angeschlossen sind. Es stammt aus dem Paket bridge-utils. In der letzten, rechten Spalte werden die an die Bridge angeschlossenen Schnittstellen angezeigt. Es sieht etwas seltsam aus, dass hier eine veth-Schnittstelle auftaucht. Man kann sich die Details zu dieser Schnittstelle mit dem Befehl ip addr show vethc3cd anzeigen lassen (Listing 3).
Listing 3
$ sudo ip addr show docker0 4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 16:69:e4:75:45:86 brd ff:ff:ff:ff:ff:ff inet 172.17.42.1/16 scope global docker0 valid_lft forever preferred_lft forever $ sudo brctl show bridge name bridge id STP enabled interfaces docker0 8000.1669e4754586 no vethc3cd $ sudo ip addr show vethc3cd 37: vethc3cd: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master docker0 state UP group default qlen 1000 link/ether 76:c0:d8:ea:50:4d brd ff:ff:ff:ff:ff:ff inet6 fe80::74c0:d8ff:feea:504d/64 scope link valid_lft forever preferred_lft forever
Es handelt sich quasi um ein virtuelles Kabel, das an der Bridge hängt (in der ersten Ausgabezeile zu sehen, master docker0). Diese Schnittstelle ist damit die Gegenstelle der eth0-Schnittstelle des Containers. Das lässt sich auch mit der Statistikfunktion des Werkzeugs ethtool herausfinden:
$ sudo ethtool -S vethc3cd NIC statistics: peer_ifindex: 37
ethtool zeigt an, dass der Index des Peers zu vethc3cd den Identifier 37 trägt. Genau das ist die ID, die im Container selbst beim eth0 angezeigt wird (Abb. 1).
Docker-Container mit dem Netzwerk verbinden
Docker vergibt beim Start des Containers also eine neue IP und verbindet sie mit der docker0-Bridge. Natürlich kann man umfangreichen Einfluss auf diese Netzwerkeinstellung nehmen (Tabelle 1). Der Daemon kann seine Container an eine andere Bridge anschließen oder die Kommunikation der Container nach außen oder untereinander beschränken. Die Vergabe der IP oder des Hostnames des Docker-Containers kann auf einem DHCP- bzw. DNS-Dienst beruhen oder statisch erfolgen. Seit der Docker-Version 1.2 kann man die Dateien /etc/hosts, /etc/hostname und /etc/resolv.conf auch im laufenden Container schreiben.
Die beiden wichtigsten Optionen beim Starten eines Containers sind aber sicherlich Publish und Link. Mit der Option –publish=<IP>:<PORT HOST>:<PORT CONTAINER> wird ein Port eines Containers durch eine Portweiterleitung an die docker0-Bridge realisiert. Damit wird es also möglich, dass der Dienst im Container von außen erreichbar wird. Mit der Option –link=<CONTAINER>:<ALIAS at CONTAINER> wird ein anderer Container auf demselben Host „sichtbar“. Die IP- und Host-Zuordnung des anderen Containers werden in die Datei /etc/hosts aufgenommen, und es werden Umgebungsvariablen generiert, die den Zugriff auf die Dienste des anderen Containers erleichtern. In Listing 4 sieht man, dass der www-tools-Container die entsprechende Names-Auflösung und Umgebungsvariable für den Zugriff auf den lighttpd-Container besitzt. Wenn der Docker Daemon mit einer Netzwerkisolierung in der Datei /etc/default/docker, beispielsweise DOCKER_OPTS= „—icc=false —iptables=true“ gestartet wurde, wird die entsprechende iptables-Regel im Host generiert. So kann direkt bestimmt werden, welcher Container mit welchem kommunizieren kann.


Gamification: A Strategy for Enterprises to enable Product Practices
with Garima Bajpai | capital carbon consulting

Cluster-API: One Kubernetes to rule them all (+ArgoCD)
with Erkan Yanar | linsenraum.de
Am Beispiel eines lighttpd-Servers wird in Listing 4 deutlich, wie auf den Service eines anderen Containers zugriffen wird. Da sich die IP-Adresse eines Containers beim erneuten Start eventuell verändert, werden der Client und der Server am besten über Namen miteinander bekannt gemacht. Die Startreihenfolge ist relevant und kann beispielsweise einfach mit den Werkzeug fig, aber besser mit upstart oder systemd auf einem Host vereinbart werden. Hier wird schnell klar, dass in einer Produktion weitere Festlegungen via DNS oder besser noch eine Orchestrierung mit einer Service-Discovery bereitgestellt werden muss. Im Artikel „CoreOS“ ist eine Lösungsvariante für diese Aufgabe beschrieben. Für die Entwicklung empfiehlt es sich, das Projekt dnsmasq einzusetzen. Damit lässt sich ein einfacher lokaler DNS-Server aufbauen. Natürlich sollte auch dieser besser als Docker-Container bereitgestellt werden.
Listing 4
# start lighttpd container $ docker run -d --name=lighttpd rossbachp/lighttpd # link and access lighttpd container $ docker run -it --rm --link lighttpd:lighttpd rossbachp/www-tools root@8152f401d0a0: curl http://$LIGHTTPD_PORT_80_TCP_ADDR/index.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Welcome page</title> </head> <body> <h1>Welcome to this tiny httpserver</h1> </body> </html> root@8152f401d0a0: ping -c 1 lighttpd PING lighttpd (172.17.0.42) 56(84) bytes of data. 64 bytes from lighttpd (172.17.0.42): icmp_seq=1 ttl=64 time=0.162 ms --- lighttpd ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.162/0.162/0.162/0.000 ms $ cat /etc/hosts 172.17.0.43 8152f401d0a0 127.0.0.1 localhost […] 172.17.0.42 lighttpd root@8152f401d0a0:/# env | grep LIG LIGHTTPD_PORT_80_TCP_PORT=80 LIGHTTPD_NAME=/cocky_ritchie/lighttpd LIGHTTPD_PORT_80_TCP_ADDR=172.17.0.42 LIGHTTPD_PORT_80_TCP_PROTO=tcp LIGHTTPD_PORT=tcp://172.17.0.42:80 LIGHTTPD_PORT_80_TCP=tcp://172.17.0.42:80
Nicht immer möchte man, dass ein Docker-Container überhaupt mit der Docker Bridge verbunden ist. Dazu dient die Option –net=none beim Start des Containers (Tabelle 1). Datentransformatoren, Numbercruncher oder einfache Batch-Jobs können ihre Aufgaben auch auf Basis einfacher Dateien durchführen. Manchmal möchte man die Netzwerkanbindung vielleicht gar nicht mit Docker-Mitteln erzeugen.
Eine Bridge hat geringfügigen negativen Einfluß auf die maximale Transferleistung des Services. Wenn Ihre Services also 10-Gigabyte-Ethernet, 20-Gigabyte-Thunderbold oder gar Infiniband-Anschlüsse auslasten, können sie die Latenz der Brücke vermeiden und den Container direkt mit dem Hostnet mit der Option –net=host an die Netzwerkschnittstelle des Hosts anbinden. Weiterhin können sie mehrere unabhängige Container mit derselben IP binden. Die Option –net=container:<CONTAINER | ID> ermöglicht die Referenz auf die IP des befreundeten Containers. Es ist also nicht notwendig, dass mehrere Prozesse in einem Container laufen müssen, damit sie unter einer IP erreichbar sind. Docker hat alle Möglichkeiten, jeden Prozess einzeln laufen zu lassen und auf einem Host trotzdem als gemeinschaftlicher Verbund wahrgenommen zu werden (Abb. 2). Das Projekt „Docker in Docker“ oder die Definition von Pods aus dem Google-Kubernetes-Projekt sind eine gute Anregungen für die Gestaltung der eigenen Servicebereitstellung.
Ein Verbund von Docker Host ermöglichen
Nicht alle Docker-Container können auf einem Host ablaufen. Da stellt sich die Frage: Wie können die Docker-Container über Host-Grenzen hinweg miteinander und sicher kommunizieren? Natürlich gibt es in der Welt der Netze hierfür mehr als eine Lösung. Direktes Routing, SSH-Tunnel oder VPN-Netze sind hier meist die Wahl. Das Thema Software Defined Networking (SDN) kann allerdings schnell unheimlich werden. Verstrickungen sind leider wahrscheinlich. Die Anwendung des Projekts Open vSwitch wird hier für komplexe Installationen empfohlen. Eine Integration der verschiedenen Möglichkeiten bietet das Projekt Docker und sein Ökosystem leider noch nicht. Verschiedene Projekte und Konzeptionen rangeln hier um Aufmerksamkeit. Im Kern ist eine Verbindung durch einen abstrakten Tunnel schnell beschrieben und schon mit Linux-Bordmitteln problemlos zwischen mehreren Hosts oder gar Standorten realisierbar, aber der Teufel liegt hier im Detail der Anforderungen, wie Abbildung 3 zeigt.
Der Einstieg in diese „interessante“ IT-Küche ist nicht schwer. Sehr hilfreich erweist sich hier das Projekt Pipework von Jérôme Petazzoni. Pipework ist ein Wrapper-Skript, um verschiedene virtuelle Netzwerkinfrastrukturen auf dem Host und im Docker-Container zu erzeugen. Schnell sind die neuen Brücken br1 auf jedem Host gebaut und die Container jedes Hosts daran gebunden, wie zum Beispiel in Abbildung 3 zu sehen ist. Wenn man dann die verschiedenen Hosts mit ihren externen Schnittstellen an die jeweiligen Brücken bindet, können die Container auf den verschiedenen Hosts miteinander Pakete austauschen. Ein nachvollziehbares Beispielprojekt zu diesem Ansatz befindet sich auf unserem infrabricks-Blog.
Verschiedene Projekte versuchen den Umgang mit einfachen und komplexen Netzwerksetups zu lösen. Aktuell sind die Projekte Wire, Weave und Flanel im Spätsommer 2014 mit ersten Betareleases erschienen. Das Projekt libswarm von Docker Inc soll helfen, die verschiedenen Ideen und Standards zu vereinheitlichen, macht aber bisher keine sichtbaren Fortschritte. Im Infokasten „Anforderungen und Status der Docker-Netzwerkeben“ befinden sich ein paar Gedanken zu Anforderungen an eine Lösung. Je nach Ihren konkreten Projektanforderungen oder dem Umfeld sollte die Lösung beurteilt, ausgewählt und angepasst werden.
Anforderungen und Status der Docker-Netzwerkebene
Die Docker-Community wartet gespannt darauf, wie sich das Thema Netzwerk und Docker entwickelt. Mit dem Projekt libswarm hat Docker Inc ein Projekt gegründet, das sich um die Entwicklung eines gemeinsam nutzbaren Modells kümmert. Am Markt gibt es aber schon eine Vielzahl von ersten Implementierungen dieser Aufgabe. Eine Integration ins große Ganze wird aber wohl noch dauern.
Wem die grundlegenden Netzwerkfähigkeiten von Docker nicht ausreichen, muss weiterführende Werkzeuge einsetzen, besonders wenn es darum geht, Container über mehrere Hosts zu verbinden. Weave ist eine in Go implementierte Switch/Router/Overlay-Kombination, die eine Art privates Netzwerk zwischen Containern schaltet. Flannel ist ebenfalls ein Overlay-Netzwerk, das im Zusammenhang mit Kubernetes Cluster zusammenschaltet werden kann. Wire hingegen zielt darauf ab, Container und Docker-Hosts in bestehende Netzwerkarchitekturen und -tools zu integrieren.
Hier eine Liste mit Vorschlägen für Anforderungen an ein zukünftiges Docker-Netzwerk:1. Wir brauchen eine transparente Netzwerkschicht über Host-Grenzen hinweg
2. Die Vermittlung der Ports/Services muss so einfach wie ein Link einsetzbar sein
3. Die Container müssen in Gruppen und in verschiedenen Netzen isolierbar sein
4. Ein Container muss sich an verschiedene Netze anschließen lassen
5. Ports und Netze müssen gezielt „veröffentlicht“ werden können und der Zugang zu den Services muss reguliert sein
6. Die Container eines Service müssen bei verschiedenen Providern bzw. Plattformen laufen können
7. Verlagerung, Änderungen oder Erneuerung eines oder mehrerer Container müssen transparent für den Client werden
8. Der Netzwerk-Traffic zwischen Containern eines Netzes muss verschlüsselt übertragbar sein
9. Skalierbarkeit und Bandbreite des Netzwerks müssen einstellbar und messbar sein
10. Es muss eine Kontrolle über die Zugehörigkeit eines Netzwerks und seiner Container geben
Fazit
Nicht jeder von uns wird das Thema Netzwerke in seiner ganzen Tiefe ausschöpfen müssen, aber für den Einsatz produktiver Software und die Gestaltung effektiver Systeme ist Basiswissen unerlässlich. Das Docker-Ökosystem schafft neue Lösungen und einen eleganteren Umgang mit dem Thema Netzwerke. In Zeiten von Clouds und unerlaubtem bzw. kriminellem Zugriff auf die eigenen Daten zeichnen sich große Herausforderungen im Design und der Bereitstellung von Software ab. Jeder, der sich mit dem Thema Microservices auseinandersetzt, kommt nicht um die Gestaltung einer brauchbaren Netzwerktopologie und dynamischeren Orchestrierung der Services herum. Wir dürfen darauf gespannt sein, welche interessanten neuen Ideen schon bald unseren Alltag bestimmen werden.
Aufmacherbild: Container terminal at night, working in the bridge crane von Shutterstock / Urheberrecht: gyn9037
[…] eine Hardware-Virtualisierungsschicht auf Betriebssystemebene (z.B. Linux KVM) einzuziehen und auf Netzwerkebene durch VLANs unabhängige Netzwerkumgebungen zu schaffen. Daten unterschiedlicher Mandanten werden […]