Docker

von Lars Kumbier | Screenguide #31, Technologie

Alle Jahre wieder beschert uns eine Firma eine neue Technologie, um die Softwareentwicklung einfacher und effizienter zu gestalten – nach VMs und Cloud nun Container mit Docker. Aber was genau bringt Docker an Vorteilen gegenüber den bestehenden Technologien?

Illustration von vielen Container, in der Mitte der Schriftzug »Docker« 

Webworker arbeiten mit unterschiedlichen Systemen, sodass das typische Entwicklersystem von unterschiedlichen Datenbanksystemen, verschiedenen Applikationsservices und Compilern mitsamt Bibliotheken verlangsamt wird. Häufig wird die Software nur für ein Projekt gebraucht und könnte danach wieder deinstalliert werden – was wegen des Aufwands meist doch nicht gemacht wird. Und hat der Webworker doch endlich mal sein System aufgeräumt, meldet sich der Kunde vom letzten Jahr mit einem Performancebug – also installiert man alles wieder. Der Bug ist auf dem eigenen System nicht reproduzierbar und nach mehreren Stunden Krisensitzung mit der Operations-Abteilung des Kunden stellt der Webworker fest, dass ein Systemupdate eine Library aktualisiert hat, die nun den Fehler verursacht.

Das Problem

Die Probleme sind bekannt: Projektspezifische Softwareanforderungen, Inkompatibilitäten zwischen Bibliotheken unterschiedlicher Projekte, Inkonsistenzen zwischen Entwicklungs- und Produktivsystem und aufwendige Skalierung durch Codeoptimierung. Eine mögliche Lösung dafür ist die Einrichtung von projektspezifischen, virtuellen Maschinen. Mit einer Virtualisierung gibt es ein Hostsystem mit einem Hypervisor – einem Programm, das die Host-Schnittstellen zu CPU, RAM etc. virtuell bereitstellt – und eine oder mehrere Virtuelle Maschinen (VMs). Letztere haben wiederum ein komplett installiertes Betriebssystem, das die virtuellen Schnittstellen des Hostsystems nutzt. Innerhalb der VM können Sie das Produktivsystem mit den projektspezifischen Anforderungen samt Bibliotheken abbilden und vermeiden dadurch einige der zuvor genannten Probleme. Leider braucht die händische Installation einer VM sehr lange, die VMs sind sehr groß und schwergewichtig, und auch wenn die VM das Produktivsystem abbildet, kann sie nicht einfach in den Produktivbetrieb überführt werden.

Für die Stammleser klingt das nach einem Fall für das bereits in Screenguide 24 beschriebene System Vagrant [vagrantup.com]. Über Vagrant können Sie automatisch die für ein Projekt notwendigen VMs provisionieren und nach erfolgreichem Abschluss wieder löschen. Da Vagrant auch Softwareprovisionierungstools wie Puppet oder Chef unterstützt, ist Vagrant in Kombination mit einer komplexen OpenStack-Infrastruktur auch gut geeignet – aber im Gegensatz zu Containerisierungslösungen extrem langsam.

Docker

Docker ist eine neue Klasse von Virtualisierung – eine Containerisierungslösung [docker.com]. Die Docker Engine besteht dabei aus einem Service, der – analog zum Hypervisor einer VM – auf dem Host-System läuft und für die Verwaltung der Container zuständig ist. Im Gegensatz zu VMs läuft in einem Container kein komplettes Betriebssystem, sondern nur ein winziger Bootloa-der, der über eine API mit dem Docker-Service des Host-Systems kommuniziert. Dadurch sind die Container deutlich kleiner, deutlich schneller und können wie in einer Cloud einfach zwischen unterschiedlichen Docker Hosts verschoben werden (Abb. 1).

Virtuelle Maschine vs. Docker
Abb. 1: Virtuelle Maschine vs. Docker

Als weitere Innovation bestehen Docker-Container aus verschiedenen Dateisystem-Schichten, die – ähnlich einem Stapel Overheadfolien – die Dateien einer darunterliegenden Schicht verändern können. Die jeweiligen Schichten liegen als sogenannte Images vor und stellen jeweils einen Aspekt des Systems dar. Dadurch können je nach Anforderung unterschiedliche, vorkonfigurierte Images in einen Container zusammengeführt werden.

Da jedes Image von einem anderen Image abhängen kann, ergibt sich so ein Abhängigkeitsbaum – die jeweiligen Abhängigkeiten müssen dafür auf dem Host-System nur einmal vorgehalten werden. Das spart Speicherplatz und macht Aktualisierungen oder den Austausch einzelner Schichten sehr einfach. Jede Schicht ist dabei schreibgeschützt und kann nicht verändert werden.

Wenn aber keine Schicht verändert werden kann, stellt sich die Frage, wo ein Container Dateien und Änderungen speichert. Dafür hat ein Container eine besondere Schicht, die immer zuletzt erzeugt wird und in der Analogie der Overheadfolien ganz oben liegt. Diese Schicht ist beschreibbar und persistiert Änderungen auch zwischen Neustarts der Container. Ein beispielhafter Aufbau für einen Webservice-Container besteht beispielsweise aus

  • dem Docker-Bootstrap-Layer,
  • einem Alpine Linux Basisimage,
  • darüber einem Client für die An- und Abmeldung bei einer Service-Discovery,
  • einem PHP-Layer für die Applikation,
  • einem Apache-Layer für den Webserver und
  • einem eigenen Layer für die Applikation selbst sowie
  • dem beschreibbaren Docker-Layer.

Möchten Sie nun auf einen NGinx umstellen, wird einfach der Apache-Layer mit einem NGinx ausgetauscht – fertig (Abb. 2).

Beispiel für ein Layer-Setup
Abb. 2: Beispiel für ein Layer-Setup

Von Development und Operations ...

In einem klassischen Entwicklungsunternehmen gibt es ab einer gewissen Größe zwei direkt beteiligte Abteilungen: Die Entwicklungsabteilung („Development”) kümmert sich um das Abarbeiten von Projekten und gibt fertige Releases an die Administration („Operations”) für den Betrieb weiter. Im Sinne der Spezialisierung ist diese Aufteilung auch sinnvoll, führt aber zu einigen Problemen. Entwickler und Administratoren haben in vielen Bereichen diametrale Ziele: Entwickler möchten möglichst viele Features, bevorzugen Bleeding-edge-Bibliotheken, wollen so häufig wie möglich deployen, schnell Änderungen einspielen und haben wenig Verständnis für komplexe Sicherheitsaspekte, die sie in ihrer Arbeit behindern. Administratoren möchten möglichst stabile Systeme und bevorzugen erprobte Long-Time-Support- Releases. Durch den Betrieb müssen sie nicht nur für Sicherheit und Monitoring sorgen, sondern auch für die Geschwindigkeit. Da sie meist auch einen direkten Draht zum First-Level-Support haben, sind sie häufig der erste Empfänger für Beschwerden. Weil sie von der eigentlichen Applikation meist keine Ahnung haben, wird eine Sicherheitsaktualisierung zu einem Blindflug.

Bei Problemen kommt es so oft zu einer Schuldzuweisung zwischen den Abteilungen – gerade bei Performanceproblemen können viele unterschiedliche Faktoren eine Rolle spielen, sodass es schwierig ist, das eigentliche Problem zu finden. Besser wäre es, wenn sich Administratoren stärker um den Betrieb und die Entwickler stärker um die Softwareentwicklung kümmern könnten.

... zu Devops

Das Zauberwort lautet DevOps – eine Verschiebung der Aufgaben. Da ein Container aus unterschiedlichen Layern zusammengesetzt ist, kann Operations ein Basisimage mit den für den Betrieb notwendigen Komponenten bereitstellen. Auf dem Basisimage selbst werden Sicherheitsupdates vorgenommen, ein weiterer Layer meldet den Container beim Monitoringsystem und (wiederum ein weiterer Layer) beim LoadBalancer an. Mit der eigentlichen Applikation und den dafür notwendigen Bibliotheken hat die Administration nichts mehr am Hut.

Entwickler wiederum können die für die jeweilige Applikation notwendigen Abhängigkeiten installieren. Bei Software- und Bibliothek-Aktualisierungen können sie als Projektvertraute die Auswirkungen erst auf ihrem eigenen System testen und Kompatibilitätsprobleme so frühzeitig ausräumen. Auch das Schreiben von Anforderungen an Operations entfällt, da die fertigen Releases einfach in die unternehmenseigene Registry gepushed und über ein Continuous Deployment veröffentlicht werden können.

Da Container nach einer zentralen Steuerungsdatei aufgebaut werden, gibt es auch keine Unterschiede oder Probleme mehr durch Inkonsistenzen zwischen Entwickler- und Produktivsystemen. Zuletzt können Sie Performanceprobleme durch eine einfache horizontale Skalierung durch das Starten von mehr Containern anstatt einer komplexeren vertikalen Optimierung beheben.

Das Ökosystem Docker

Wenn Entwickler von Docker sprechen, meinen sie meistens die Docker Engine – also die Applikation, die die Containerisierung ermöglicht. Hinter Docker stehen aber zum einen die Firma Docker, Inc. und zum anderen ein ganzes Ökosystem an Programmen und Dienstleistungen:

Docker Toolbox ist das Schnellstartpaket von Docker und installiert unter allen großen Betriebssystemen die notwendigen Programme, um mit Docker loszulegen. Dazu gehören die Docker Engine, Docker Machine und Docker Compose – hinzu kommt noch Oracle Virtualbox.

Docker Engine besteht aus einem Daemon, der auf einem Host läuft und über eine API angesprochen werden kann. Die Docker Engine verwaltet die Container, die auf dem jeweiligen Hostsystem laufen. Dabei kann die Docker Engine von verschiedenen Clients angesprochen werden – der am häufigsten anzutreffende ist das Docker CLI, das auch von der Docker Toolbox installiert wird.

Docker Machine: Für das automatisierte Aufsetzen von einer Docker-Umgebung benötigen Sie aktuell noch ein Linux-System als Docker Host. Mit Docker Machine können Sie schnell und einfach Docker Hosts aufsetzen und verwalten („provisionieren”). Über ein minimales Image namens Boot2Docker wird mithilfe von Virtualbox ein Docker Host aufgesetzt. Docker Machine verwaltet auch entfernte Docker Hosts und sorgt dafür, dass das Docker CLI mit dem richtigen Docker Host kommuniziert.

Docker Compose: Was das Dockerfile für die Erzeugung einzelner Container ist, das ist Docker Compose für ein Multi-Container-Setup. Mittels Docker Compose können Sie nicht nur die einzelnen Services einer Applikation wie Frontend-Server, Middleware- und Persistenzschicht miteinander verknüpfen – Docker Compose erlaubt auch das einfache horizontale Skalieren einer Anwendung durch das Starten und Einbinden neuer Container.

Docker Swarm ist ein Abstraktionslayer über Docker Machine und sorgt für die Cloud-Fähigkeit von Docker – also das automatische Provisionieren von Docker Containern auf einer Reihe von Docker Hosts. Dafür hat Docker Swarm automatische Netzwerkkonfiguration, flexibles Scheduling von Containern (regelbasiertes Verteilen von Containern im Swarm) und Hochverfügbarkeit mit Lastverteilung auf der Featureliste. Docker Swarm bildet dabei einfach die normale Docker-Engine-Schnittstelle ab, sodass Sie transparent von einem einzelnen Docker Host auf ein Multi-Host- System mit Docker Swarm upgraden können.

Docker Registry, Docker Hub und Docker Cloud: Die Docker Registry nimmt eine zentrale Stelle im Docker-Ökosystem ein und verwaltet die verfügbaren Container. Der Docker Hub ist die öffentliche Standard Registry, ohne die praktisch nichts geht.

Docker Inc. bietet noch weitere Produkte an, die speziell auf Unternehmen zugeschnitten sind. Dazu zählt das Docker Datacenter, eine vollständige Lösung, die alle bisher genannten Produkte unter einer UI vereint. Hinzu kommen diverse Plugins für die Anbindung des Datacenters an die bestehende Infrastruktur. Seit Kurzem mit dabei ist auch Docker Notary, das mittels Signierung von Containern einen wichtigen Sicherheitsaspekt im Docker-Ökosystem einnehmen wird.

Loslegen

Um mit Docker am eigenen Rechner loszulegen, installieren Sie zunächst die Docker Toolbox. Nach dem Setup verfügen Sie zum einen über eine Oracle Virtualbox mit einer speziellen virtuellen Maschine, auf der die Docker Engine läuft, und zum anderen über eine Docker-Kommandozeile, in der Sie über den Docker Client mit der Docker Engine kommunizieren können. Hier ein „Hello World” als kleines Beispiel:

Konsole
  1. $> docker run hello-world
  2. Unable to find image 'hello-world:latest' locally
  3. latest: Pulling from library/hello-world
  4. 03f4658f8b78: Pull complete
  5. a3ed95caeb02: Pull complete
  6. Digest: sha256:8be990ef2aeb16dbc…
  7. Status: Downloaded newer image for hello-world:latest
  8. Hello from Docker.
  9. This message shows that your installation appears to be working correctly…

Dieser Befehl startet einen Container aus einem Image namens „Hello World”. Da die Docker Engine dieses Image noch nicht lokal vorliegen hat, lädt sie sich das Image (mit der ID a3ed95) mit allen Abhängigkeiten – in diesem Fall nur eine einzige mit der ID 03f465 – von der öffentlichen Docker Registry namens Docker-Hub herunter. Das anschließende „Hello from Docker” ist die Ausgabe vom Hello-World-Container.

Der angelegte Container beendet sich nach der Ausgabe selbst, wird aber von der Docker Engine nicht explizit gelöscht. Führen Sie den Befehl erneut aus, müssen die notwendigen Images nicht mehr heruntergezogen werden, und ein neuer Container wird aus dem Image erzeugt und gestartet – und das innerhalb von wenigen Millisekunden. Zu beachten ist, dass Sie nun zwei getrennte Docker- Container haben, die Sie sich über den Befehl „docker ps -a” anzeigen lassen können. Einen Container können Sie natürlich auch wieder benutzen – dafür gibt es den Befehl „docker start -a ”, mit dem Sie wieder die „Hello World”-Ausgabe erhalten. Zum Schluss können Sie Ihre Container einzeln durch „docker rm ” entfernen.

Ein reales Beispiel mit Docker Compose

Nachdem Sie nun die Grundkonzepte von Docker ausprobiert haben, wird es Zeit für ein Anwendungsbeispiel. Wir nehmen an, dass Sie ein neues Wordpress-Theme entwickeln und dafür Docker nutzen möchten. Als Erstes ist es sinnvoll, sich auf dem Docker Hub nach einem offiziellen Image umzuschauen. Über die Suche erhalten Sie die Info, dass es ein offizielles Wordpress Image gibt, das von der dahinterstehenden Entität (hier: Wordpress Inc.) gepflegt wird. Wie bei jeder von außen in ein Projekt gezogenen Abhängigkeit sollten Sie darauf achten, dass ein gewisses Vertrauen in die Abhängigkeit besteht – ohne regelmäßige Pflege und ohne Prüfung kann es sonst schnell passieren, dass Sie sich eine Sicherheitslücke ins Projekt holen.

Auf der Projektseite des Wordpress-Images finden Sie die Dokumentation für das Image – insbesondere die Info, dass sich in diesem Image keine Mariadb-Datenbank befindet. Prinzipiell spricht zwar nichts dagegen, ein Docker-Image ähnlich einer VM mit allem Notwendigen auszustatten – da Container aber sehr leichtgewichtig und schnell zu starten sind, hat sich in der Dockercommunity das Prinzip „ein Container – eine Aufgabe” durchgesetzt. Als Konsequenz benötigen Sie also neben dem Wordpress- Container auch einen ansprechbaren Mariadb-Container. Docker bietet dafür ein automatisches und damit sehr komfortables Verbinden von Containern an – jeder Containername ist automatisch von anderen Containern auf derselben Engine aus ansprechbar, wenn diese Container über den Link-Befehl miteinander kommunizieren dürfen. Damit Sie sich nun nicht dauernd einzelne Kommandozeilenbefehle merken müssen, nutzen wir ein weiteres Tool aus dem Docker-Universum namens Docker- Compose. Über dieses Tool lassen sich sehr einfach auch komplexe Containersetups mittels einer Konfigurationsdatei erstellen:

docker-compose.yml
  1. web:
  2. image: wordpress
  3. links:
  4.   - db:mysql
  5. ports:
  6.   - 1234:80
  7. volumes:
  8.   - ./wp-content/:/var/www/html/wp-content
  9. db:
  10. image: mariadb
  11. environment:
  12.   - MYSQL_ROOT_PASSWORD=supersecret
  13.   - MYSQL_DATABASE=wordpress

In der Beispielkonfiguration definieren Sie zwei Maschinen – web und db – und weisen die passenden Images zu – in diesem Fall wordpress und mariadb. In Zeile 3 verbinden Sie den web-container mit dem mariadb-container – der Wordpress-Container ist so weit vorbereitet, dass keine weiteren Einstellungen notwendig sind. Als Nächstes müssen Sie irgendwie auf Wordpress zugreifen können. Durch den Ports-Eintrag weisen Sie Docker an, den Container-Port 80 auf den Host-Port 1234 weiterzuleiten – letzterer sollte natürlich frei sein. Die nächsten zwei Zeilen bauen ein sogenanntes Volume auf – im Prinzip wird das Container- Verzeichnis /var/www/html/wp-content transparent auf das Host-Verzeichnis ./wp-content gemappt.

Aber Achtung: Das gemappte Verzeichnis befindet sich erst mal nur zwischen dem Docker-Container und dem Host-System. Letzteres ist aber nicht Ihre Workstation, sondern die Virtuelle Maschine, auf dem die Docker Engine läuft. Damit Sie auch produktiv arbeiten können, müssen Sie noch über die Virtualbox-Einstellungen den geteilten Ordner zusätzlich mit einem Ordner auf unserem Computer verbinden. Dazu starten Sie das UserInterface von Virtualbox und bearbeiten die Docker-VM mit dem Namen „default”. Unter den Einstellungen finden Sie den Bereich „geteilte Ordner” und können dort ein beliebiges Verzeichnis vom Computer auf das Verzeichnis /root/wp-content der VM mappen. Nach diesem Schritt können Sie nun Ihr Wordpress-System starten. Vorher merken Sie sich die IP-Adresse der Docker-VM, die Sie über „docker-machine ip” herausfinden. Das eigentliche Anlegen der Container starten Sie mit „docker-compose up”. Dies startet die Container im Vordergrund – alternativ können Sie die Container auch mittels „docker-compose start” im Hintergrund starten.

Wie Sie in den Log-Dateien sehen können, lädt sich der web- Container automatisch die neueste Wordpress-Version aus dem Internet herunter und entpackt sie in das Verzeichnis /var/www/ html auf dem web-Container. Verbinden Sie sich nun über einen Webbrowser mit dem (weitergeleiteten) Port 1234 auf dem Docker Host, bekommen Sie das bekannte 2-min-Setup von Wordpress zu sehen: http://:1234/. Die Datenbankkonfiguration, die vom Wordpress-Container automatisch aus Ihren Umgebungsvariablen aus der docker-compose.yml geladen wurde, wird selbsttätig eingetragen und dementsprechend im Setup übersprungen.

Da Sie das Verzeichnis /var/www/html/wp-content aus dem Container über den Docker Host an Ihren Computer weiterleiten, haben Sie nun in Ihrem Entwicklungsverzeichnis die bekannte Struktur von Wordpress wie themes und plugins und können mit der Entwicklung loslegen. Nach dem Ende der Entwicklung können Sie mit STRG+C die Container wieder beenden und zu einem späteren Zeitpunkt wieder hochfahren, ohne dass Sie in der Zwischenzeit Ressourcen verbrauchen.

Fazit

In diesem Artikel haben Sie eine kurze Einführung in Docker bekommen und können mit der Entwicklung loslegen. In der nächsten Ausgabe von Screenguide schauen wir uns das Deployment nach der Entwicklung und die Skalierung an.

Lars Kumbier 

Lars Kumbier

ist Master of Computer Science und arbeitet als freiberuflicher IT-Consultant. Er beschäftigt sich seit 2015 mit Docker und entwickelt mit einem eigenen Team Cross-Plattform-Apps.

Twitter: @LarsKumbier

Neuen Kommentar schreiben