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…