Docker Overlay Network und Updates bei der Installation von Docker 1.10.x

Im letzten Artikel habe ich beschrieben, wie Docker Swarm auf KVM-basierten virtuellen Maschinen eingerichtet wurde. Von da an ist es nur noch ein kleiner Schritt zur Konfiguration eines Overlay Networks.

Mit einem Overlay Network ist es möglich, mehrere Docker Hosts so zu verbinden, dass der Eindruck entsteht, sie befinden sich im selben Netzwerk. Üblicherweise stehen die Docker Hosts für sich, auch wenn sie mittels Docker Swarm angesprochen werden, handelt es sich zunächst einmal um einzelne Komponenten, die ihre eigenen Netzwerkressourcen besitzen. Wenn nun ein Docker Container auf einem Host gestartet wird, ist es zwar möglich, dass Ports nach aussen hin frei gegeben werden, dies muss aber nicht so sein. Falls nicht, ist die Sichtbarkeit auf den jeweiligen Host beschränkt, d.h. das Verlinken mittels „–link“-Option ist auf einen einzelnen Docker-Host beschränkt.

Das Overlay Network löst dieses Problem, indem es ein gemeinsames Multi-Host-Network aufbaut. Damit wird es erreicht, dass sich Docker Container, die auf verschiedenen Hosts laufen, „sehen“. Zum Beispiel könnte ein Container unter dem Namen „db01“ als Datenbank-Container auf einem Host gestartet werden, während ein Container namens „web01“ auf einem anderen Host läuft. Das Overlay Network legt ein eigenes virtuelles Netz an, in dem die jeweiligen Container einfach über ihren Namen angesprochen werden können. Man könnte es insofern auch als „Linken“ über mehrere Hosts hinweg bezeichnen. Die Option „--link“ ist beim Start von Container jedoch nicht notwendig bzw. in dem Fall nicht erlaubt, jedoch muss das zu verwendende Netz mit „--net“ angegeben werden.

Wesentlich detaillierter, mit Skizzen etc. werden die Netzwerk-Fähigkeiten von Docker wie üblich in der Docker-Dokumentation beschrieben, es lohnt sich auch allein deshalb, einen Blick zu riskieren, da jene Features in der letzten Zeit intensiv ausgebaut wurden, so dass beinahe mit jeder neuen Version von Docker auch weitere Netzwerk-Optionen hinzu gekommen sind.

Des Weiteren gehe ich im Folgenden davon aus, dass sich alle Hosts bereits in einem Netzwerk befinden und sich alle untereinander auf beliebigen Ports ansprechen können. Sollte die Konfiguration z.B. unter Amazon AWS auf EC2-Maschinen stattfinden, müssen einige Ports in der jeweiligen Security Group zur internen Verwendung freigegeben werden. Diese Ports (Data Plane VXLAN 4789/udp, Control Plane 7946/tcp/udp) dienen zur internen Kommunikation und sollten somit nicht frei zugänglich für die Welt sein. Da sich das Beispiel auf ein internes, abgesichertes Netzwerk bezieht, sind derartige Vorarbeiten jedoch nicht notwendig.

Genug der Vorrede, denn die Einrichtung selbst ist tatsächlich mit einem einigen Kommando sehr schnell geschehen:

geschke@connewitz:~$ docker $(docker-machine config --swarm miltitz) network create gnswarm
c97a5ab729[...|bf4a6e2be

Wir befinden uns auf dem Host, von dem der gesamte Swarm eingerichtet wurde, d.h. es besteht Zugang zu den Docker Hosts, der Swarm hat den Namen „miltitz“, das Kommando lautet einfach „network create <overlaynetwork>

Ferner kann die Option „--driver overlay“ angegeben werden, in der verwendeten Docker Version war dies noch nicht nötig, aber vermutlich wird es in den nächsten Releases wieder einige Erweiterungen geben, so dass die genaue Angabe ratsam erscheint.

Der (gekürzte) Hash zeigt an, dass die Einrichtung erfolgreich war.

 

Zur Kontrolle lassen sich alle Netzwerke anzeigen:

docker $(docker-machine config --swarm miltitz) network ls
NETWORK ID          NAME                DRIVER
e8921249159a        tolkewitz/host      host
b45257801901        lindenau/none       null
69257cf1bc11        miltitz/bridge      bridge
be8ff913b1ee        miltitz/host        host
8dacc39eb557        kaditz/none         null
8a54cd599413        tolkewitz/none      null
c166262a668f        kaditz/bridge       bridge
631895894f99        tolkewitz/bridge    bridge
c97a5ab729bf        gnswarm             overlay
555139efc811        lindenau/bridge     bridge
b93f353a5949        lindenau/host       host
3c333e339ba8        miltitz/none        null
3c930fd3969f        kaditz/host         host

Natürlich will dies noch genauer getestet werden. Dazu werden zwei Container erzeugt, einerseits ein Web-Server Nginx aus dem Standard-Image, andererseits eine Shell, von der aus der Web-Container erreichbar sein soll.

Nginx:

geschke@connewitz:~$ docker $(docker-machine config --swarm miltitz) run -d --name web --net gnswarm nginx
10daa9a6df3ce54ddf1a1ec95e738d623ac9d0922203265b288c163c7f7af874

Shell

geschke@connewitz:~$ docker $(docker-machine config --swarm miltitz) run -itd --name shell1 --net gnswarm alpine /bin/sh
7597d7953140e92e115047cc0781eb07751c5061e336af2e2bc2249f7f233237

Zugegebenermaßen habe ich mich in dem Beispiel an diversen Blog-Beiträgen und der Docker-Dokumentation bedient. Als nächstes wird geprüft, auf welchen Hosts die soeben gestarteten Container laufen. Zwar hätte sich auch der Host über Constraints spezifizieren lassen (--env="constraint:node==kaditz"), aber die im Swarm per Default agierende Spread-Strategie hat möglicherweise bereits dafür gesorgt, dass eine Verteilung der Container auf die Hosts stattfindet.

So auch hier:

geschke@connewitz:~$ docker $(docker-machine config --swarm miltitz) ps
CONTAINER ID        IMAGE         COMMAND                  CREATED             STATUS              PORTS                  NAMES
7597d7953140        alpine        "/bin/sh"                3 minutes ago       Up 3 minutes                               miltitz/shell1
10daa9a6df3c        nginx         "nginx -g 'daemon off"   7 minutes ago       Up 7 minutes        80/tcp, 443/tcp        tolkewitz/web

Wie leicht zu erkennen ist, wurde der „web“-Container auf dem Host „tolkewitz“ gestartet, während „shell1“ auf „miltitz“ läuft.

Für den Test kann man sich mit dem Shell-Container verbinden:

docker $(docker-machine config --swarm miltitz) attach shell1

Von dort aus sollte der andere Container erreichbar sein, und zwar einfach über dessen Namen:

/ # ping web
PING web (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.821 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.877 ms
64 bytes from 10.0.0.2: seq=2 ttl=64 time=0.974 ms
^C

Natürlich können auch die im Web-Container gestarteten Dienste erreicht werden, in dem Fall Nginx:

/ # wget -O- http://web
Connecting to web (10.0.0.2:80)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
[...|

Noch eine Anmerkung zur Weiterentwicklung von Docker: Bis zur Version 1.9 wurde in den jeweiligen Containern die Datei /etc/hosts verwendet, um die Zuordnung der Namen zu den im Overlay Network verwendeten IP-Adressen zu erreichen. Dies sah z.B. so aus:

0.0.0.3    8112a9e34fd6
127.0.0.1    localhost
::1    localhost ip6-localhost ip6-loopback
fe00::0    ip6-localnet
ff00::0    ip6-mcastprefix
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters
10.0.0.2    web
10.0.0.2    web.gnswarm

Je mehr Container sich im Overlay Network befanden, desto länger wurde die Datei, des Weiteren musste diese beim Start oder beim Beenden von Containern jedes Mal aktualisiert werden.

Mit der Version 1.10 ist nun ein in Docker eingebetteter DNS-Server hinzu gekommen, der für die Namensauflösung zuständig ist. Damit bleibt die /etc/hosts von Änderungen befreit:

/ # cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
10.0.0.3	7597d7953140
172.18.0.2	7597d7953140

Auch darauf weist Docker in der entsprechenden Dokumentation hin, nur könnte es u.U. beim Umstieg von Versionen vor 1.10 zur Verwunderung führen, dass keine Einträge in der hosts-Datei mehr vorhanden sind, die Verbindung jedoch dennoch funktioniert.

Natürlich war dies nur ein kleines, vielleicht wenig praxisrelevantes Beispiel eines Overlay-Networks mit Docker. Tatsächlich sind die Netzwerk-Features inzwischen sehr mächtig geworden und werden ständig weiter ausgebaut. Die auf einzelnen Docker-Hosts mögliche Verknüpfung via „Linken“ ist dabei fast komplett ersetzt worden. In der Praxis setzen wir momentan eine Docker-Installation ein, die aus mehreren Datenbank-Container, mehreren Application-Containern, mehreren Worker-Containern und einem Redis-Container besteht, dazu kommen noch diverse Container für die Management-Funktionen, nebst Docker Swarm inkl. Consul betrifft dies auch Container für das Monitoring, Logging und UIs wie z.B. resque-web. Mit Docker Swarm werden die Container auf die jeweiligen Hosts verteilt, teilweise mittels Constraints auf die für den jeweiligen Zweck vorgesehene Maschine. Bei anderen ist es hingegen irrelevant, auf welchem Host sie laufen, denn dank der Ansprechbarkeit über ihren Namen innerhalb des Overlay-Networks benötigen sie keine strikte Abhängigkeit wie eine IP-Adresse. Die gesamte Installation läuft auf einem EC2-Cluster innerhalb von Amazon AWS, siehe dazu die Anmerkungen zur Security Group. Evtl. gehe ich in einem späteren Artikel noch genauer auf die Konfiguration ein, auch hier gilt, dass Docker grundsätzlich noch relativ neu ist und es noch einige Änderungen geben wird, mit denen man rechnen und auf die man reagieren muss.

Docker Engine 1.10 mit Systemd unter Ubuntu

Eine der Änderungen ist z.B. die nun vereinfachte Installation unter Ubuntu. Im Artikel über die Einrichtung des Docker Swarm hatte ich den Workaround beschrieben, der notwendig war, damit die Docker Engine unter Ubuntu 15.10 mit Systemd läuft.

Die gute Nachricht – dies ist ab Docker 1.10 nicht mehr notwendig. So bringt Docker alles mit, was für eine funktionierende Startsequenz benötigt wird. Die Optionen sind nun in der Datei /etc/systemd/system/docker.service vorhanden, diese Datei wird auch aus dem Docker-Paket angelegt bzw. bei der Einrichtung  mittels Docker Machine konfiguriert.

Die schlechte Nachricht – falls bereits der Workaround oder andere Wege eingesetzt wurden, damit Docker unter Systemd gestartet wird, sorgt die neue Service-Datei für einen Konflikt, so dass Docker evtl. gar nicht mehr gestartet wird.

Mit Docker Machine 0.6 ist auch eine neue Option hinzu gekommen, und zwar das Kommando „provision“. Damit lässt sich die Provisionierung, also Einrichtung von Docker Swarm etc. erneut auf einem bestehenden Docker Host durchführen. In der Praxis ist dies ein sehr sinnvolles Kommando, tatsächlich habe ich durch diverse Experimente mit Consul mal einen laufenden Docker Swarm so „zerschossen“, dass nahezu nichts mehr lief und ein neuer Aufbau des Swarm Clusters notwendig war. Die erneute Provisionierung hat dabei einigen Aufwand erspart.

Bei Ausführung der Provisionierungs-Kommandos oder auch durch das Generieren neuer TLS-Zertifikate versucht docker-machine Docker so einzurichten, dass die neue Systemd-Service-Datei benutzt wird. Sollte dabei der Workaround vorhanden sein, geht dies schief.

 

Um Abhilfe zu schaffen, sollte zunächst das Kommando ausgeführt werden, auch wenn der nicht erfolgreiche Abschluss bereits bekannt ist:

geschke@connewitz:~$ docker-machine provision tolkewitz
Waiting for SSH to be available...
Detecting the provisioner...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Unable to verify the Docker daemon is listening: Maximum number of retries (10) exceeded

Das heißt nichts anderes, als dass der Docker-Daemon nicht (mehr) läuft, da es beim Restart zu Problemen kam.

Die entsprechende Systemd-Service-Datei wurde jedoch bereits erzeugt. Daher kann man sich nun auf dem Docker-Host einloggen und den Workaround aufräumen:

geschke@connewitz:~$ docker-machine ssh tolkewitz
[...]

geschke@tolkewitz:~$ cd /etc/systesmd/system
geschke@tolkewitz:/etc/systemd/system$ ls
default.target.wants    halt.target.wants        poweroff.target.wants  sysinit.target.wants
docker.service          kexec.target.wants       reboot.target.wants    syslog.service
docker.service.d        multi-user.target.wants  shutdown.target.wants
getty.target.wants      plymouth-log.service     sockets.target.wants
graphical.target.wants  plymouth.service         sshd.service

geschke@tolkewitz:/etc/systemd/system$ sudo rm -Rf docker.service.d/

geschke@tolkewitz:/etc/systemd/system$ sudo systemctl daemon-reload

geschke@tolkewitz:/etc/systemd/system$ sudo /etc/init.d/docker restart

Da docker-machine dafür gesorgt hat, dass die Konfiguration in der neuen Datei „docker.service“ abgelegt wurde, sollte der Restart genügen, um Docker wieder zum Laufen zu bringen. Danach laufen jedoch zunächst die Container, die zuvor ebenfalls gestartet wurden. Bei der Provisionierung hingegen erzeugt docker-machine normalerweise neue swarm- bzw. neue swarm-master-Container. Falls die alten noch laufen, bricht der Vorgang ab – docker-machine erkennt somit nicht, dass man evtl. gerne den bestehenden Swarm neu aufbauen möchte:

Error while creating container: 409 Conflict: Conflict. The name "/swarm-agent" is already in use by container 11ae0fe446cfcc22d69435eac1691d7414a185c1c6d68c4096af4dc33ae37f2b. You have to remove (or rename) that container to be able to reuse that name.

Einerseits eine aus Docker-Engine-Sicht logische Verfahrensweise, da derselbe Name „swarm-agent“ ja bereits auf dem Docker Host existiert, andererseits mutet es auch ein wenig merkwürdig an, da man die Provisionierung ja gerade aus dem Grund durchführen könnte, den Swarm neu aufzubauen. Evtl. wäre hier eine Abfrage des gewünschten Verhaltens eine sinnvolle Option. Natürlich ist die Lösung einfach – es genügt, den swarm-agent auf dem jeweiligen Host zu stoppen und zu entfernen, womit der Namenskonflikt gelöst ist. Danach kann docker-machine wieder aktiv werden.

Soviel für heute mit den Geschichten aus dem Docker-Land, wobei das Buch noch lange nicht geschlossen ist und noch Platz für einige mehr oder minder spannende Episoden bietet…

Schreibe einen Kommentar

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

Tags: