Wie die Erfahrung zeigt, ist das Aufsetzen von Systemumgebungen in Projekten häufig ein enorm zeitintensives Unterfangen. Es führt nicht selten zu langwierigen Projektvorlaufzeiten und bremst die sonst so agilen Projekten aus. Aber auch im späteren Projektverlauf kann es zum Hindernis werden, wenn beispielsweise kurzfristig eine Umgebung für die Abnahme eines neuen Features beim Fachbereich benötigt wird. Hier kann das Virtualisierungswerkzeug Docker in Verbindung mit Cloud Services Abhilfe schaffen.
Docker und die Cloud – das Dynamische Duo
Mit dem Einzug Agiler Methoden und Prozesse hat sich das Prinzip der kontinuierlichen Integration (Continuous Integration) als “Best Practice ” in vielen Entwicklungsteams etabliert. Durch den Einsatz entsprechender Werkzeuge wie Jenkins oder Bamboo können so Fehler und Probleme früh erkannt und mit geringerem Aufwand als zu einem späteren Zeitpunkt im Entwicklungsprozess behoben werden.
Diese Automatisierung der Software-Fertigungsstrecke wird mit dem durch das gleichnamige Buch von Jez Humble populär gewordenen Continuous Delivery-Ansatz bis hin zum Betrieb der Applikation ausgedehnt. Martin Fowler bringt den zentralen dahinterstehenden Gedanken in seiner Definition des Begriffes wie folgt auf den Punkt:
You can perform push-button deployments of any version of the software to any environment on demand.
Das Ziel dabei ist eine deutliche Reduktion der Deploymentrisiken sowie eine Verkürzung der Feedbackschleifen für die Produktentwicklung.
Die Verbreitung von Continuous Delivery verlief jedoch bisher eher zögerlich, galten doch die Aufwände für die Einführung der hierfür erforderlichen Configuration Management Systeme wie Chef oder Puppet aufgrund ihrer Komplexität als hoch. Das Bild änderte sich jedoch als im März 2013 die erste Version von Docker erschien, das sich durch die folgenden Eigenschaften auszeichnete:
- Flache Lernkurve
Anders als bei den zuvor genannten Werkzeugen kommt Docker mit einer extrem reduzierten DSL aus. Diese lehnt sich zudem an bekannte Prinzipien, wie man sie von Shellskripten und dem Debian/Ubuntu-Paketmanagement kennt, und erfordert auch keine zusätzliche Einarbeitung in eine neue Programmiersprache. - Minimale Requirements
Außer einem aktuellen Kernel (>= 3.10) gibt es nur wenige benötigte Bibliotheken, um Docker einsetzen zu können. - Image Repository
Mit Dockerhub steht ein riesiger Fundus fertiger Images zur Verfügung, die sich mit einem Kommandozeilenbefehl laden und starten lassen.
Zusammen mit den mittlerweile einfach und günstig verfügbaren Cloud Services verschiedener Anbieter eröffnet sich so ein effizienter und schneller Weg, Systemumgebungen aufzusetzen.
Die ersten Schritte
Am Beispiel einer typischen Continuous Integration Umgebung für Java Projekte möchte ich demonstrieren, wie dieser Ansatz funktioniert.
1 2 3 4 |
$ curl -L https://github.com/docker/machine/releases/download/v0.2.0/docker-machine_darwin-amd64 > /usr/local/bin/docker-machine $ chmod +x /usr/local/bin/docker-machine $ curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose $ chmod +x /usr/local/bin/docker-compose |
Zum aktuellen Zeitpunkt existiert leider keine native Windows-Version von docker-compose.
Den Anfang macht das erst Ende Februar als Beta-Version erschienene Kommandozeilenwerkzeug docker-machine, mit dem sich auf einfache Art und Weise eine Docker Engine auf verschiedenen Hostsystemen erzeugen läßt, angefangen von einer lokalen Virtualbox bis hin zu Instanzen diverser Cloud-Anbieter.
Mit Hilfe dieses Programms erzeugen wir nun eine neue Instanz auf der Google Compute Engine und installieren auf dieser die Docker Engine, welche die Umgebung für die später zu installierenden Service-Container bildet.
1 2 3 4 5 6 7 |
docker-machine create \ (1) --driver google \ (2) --google-disk-size 50 \ (3) --google-machine-type g1-small \ (4) --google-zone europe-west1-b \ (5) --google-project colenet_demo \ (6) dev (7) |
1 | Kommando zum Erzeugen einer neuen Docker-Instanz |
2 | Auswahl des Instanztyps. Neben diversen Treibern für Cloud-Dienste kann auch eine lokale Virtualbox verwendet werden. |
3 | Größe der Festplatte |
4 | Maschinentyp (Google spezifisch) |
5 | Zone, in der die Instanz laufen soll |
6 | Projektname in der GCE-Umgebung |
7 | Name, unter dem Docker diese Instanz verwalten soll. |
Direkt nach dem Absetzen dieses Befehls erscheint im zuvor geöffneten Browser ein Dialog, über den man sich bei Google anmelden und die von docker-machine angefragten Rechte bestätigen muss. Abschließend wird dann ein Authentication Token angezeigt, den man per Copy & Paste in die Eingabeaufforderung des Terminals übernimmt. Nach Abschluss der restlichen, automatisch ablaufenden Einrichtungsschritte kann man sich mit docker-machine ls die angelegte Instanz anzeigen lassen.
1 2 |
NAME ACTIVE DRIVER STATE URL SWARM dev * google Running tcp://<IP der Cloudinstanz>:2376 |
Damit nun alle folgenden docker-Befehle an diese Instanz gesendet werden, muss man mit $(docker-machine env dev)
die notwendigen Umgebungsvariablen setzen. Nach diesen wenigen Schritten können wir nun schon direkt beginnen, Applikationen in Form von Docker-Containern auf der neuen Instanz zu installieren. Um beispielsweise das CI-System Jenkins zu starten, genügt der folgende Befehl.
1 |
docker run --name "myjenkins" -p 8080:8080 -d jenkins |
Die folgenden Schritte werden dabei implizit ausgeführt:
- Download der aktuellsten Version des Images “jenkins” aus dem Dockerhub Repository.
- Erzeugen eines Containers mit dem Namen “myjenkins”, bei dem der Port 8080 durchgereicht wird.
- Start des Containers als Hintergrund Prozess
Der Dienst ist anschließend unter http://<IP der Cloudinstanz>:8080
verfügbar und das Team kann loslegen.
Auf diesem Weg lassen sich nun alle weiteren benötigten Dienste starten. Das folgende Diagramm zeigt den generellen Aufbau der Umgebung.

Service-Orchestrierung
Für unser Beispiel benötigen wir jedoch mehrere Container, die, wie das folgende Diagramm zeigt, miteinander verbunden sind.

Um nun nicht alle hierfür notwendigen Dockerbefehle wie im ersten Beispiel über einzelne Kommandos ausführen zu müssen, greifen wir auf das Orchestrierungswerkzeuges docker-compose zurück.
Dafür definieren wir die Container unserer Umgebung in einer einfachen Textdatei im YAML-Format wie folgt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
sonardb: #(1) image: orchardup/postgresql #(2) environment: #(3) - POSTGRESQL_USER=sonar - POSTGRESQL_PASS=geheim - POSTGRESQL_DB=sonar ports: #(4) - "5432:5432" sonarqube: image: harbur/sonarqube links: - sonardb:db #(5) environment: - DB_USER=sonar - DB_PASS=geheim - DB_NAME=sonar ports: - "9000:9000" jenkins: image: jenkins ports: - 8080:8080 links: - sonarqube - sonardb |
1 | Name des Dienstes der die Grundlage des generierten Containernamens bildet |
2 | Docker Image |
3 | Umgebungsvariablen, die im Container gesetzt werden sollen. In diesem Fall werden sie zur Deklaration des Datenbankaccounts genutzt. |
4 | Mapping des im Image definierten auf den extern erreichbaren Port des Containers |
5 | Container-Link: Der so verknüpfte Container (sonardb) steht innerhalb des Containers (sonarqube) unter dem angegebenen Hostname (db) zur Verfügung |
Um auf der Instanz die so definierten Dienste in der richtigen Reihenfolge mit dieser Konfiguration zu starten, genügt der folgende Befehl:
1 |
$ docker-compose up -d |
Auch hier werden die gleichen Schritte wie zuvor bei dem docker run
-Befehl durchlaufen.
Ein Blick mit docker ps
zeigt uns, dass alle Container wie gewünscht bereit stehen.
1 2 3 4 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1b80e6ceba00 jenkins:latest "/usr/local/bin/jenk About a minute ago Up About a minute 50000/tcp, 0.0.0.0:8080->8080/tcp tmp_jenkins_1 ce067316ccd8 harbur/sonarqube:latest "/app/init app:start About a minute ago Up About a minute 0.0.0.0:9000->9000/tcp tmp_sonarqube_1 4f8bafe7b2dd orchardup/postgresql:latest "/usr/local/bin/run" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp tmp_sonardb_1 |
Fazit
Wie gezeigt kann durch die Kombination von Clouddiensten und der Virtualisierungsplattform Docker in kürzester Zeit eine neue Systemumgebung erstellt werden. Die folgenden Vorteile ergeben sich bei diesem Verfahren:
- Reproduzierbarkeit
Gemäß dem Motto “Infrastructure as Code and Infrastucture is Code” wird die gesamte Umgebung durch eine ausführbare Datei beschrieben. Damit ist es möglich, dieselbe Umgebung jederzeit erneut aufzusetzen. Zusätzlich kann die Konfigurationsdatei zur Dokumentation des Systems verwendet werden. - Unabhängigkeit
Das Entwicklerteam kann weitestgehend unabhängig von organisatorischen Rahmenbedingungen wie System- und Prozessvorgaben die benötigte Umgebung erzeugen. Mühselige Systemanpassungen aufgrund restriktiver Softwarerichtlinien entfallen und langwierige Abstimmungen mit anderen Organisationseinheiten werden reduziert. - Versionierung
In der Softwareentwicklung schon lange selbstverständlich stellt die Versionierung der Infrastruktur eher die Ausnahme dar. Dank der dateibasierten Konfiguration können definierte Entwicklungsstände der Systemkonfiguration gemeinsam mit dem Quellcode verwaltet werden. Dadurch erhält man die Möglichkeit, neue Infrastrukturen zeitgleich mit dem Code zu entwickeln und zu testen, nötigenfalls Rollbacks durchzuführen und die Entwicklung vollständig und transparent zu dokumentieren.
Für alle, die jetzt Appetit bekommen haben, Docker auszuprobieren, findet sich im Anhang ein kurzer Überblick zu Docker und den wichtigsten Befehlen.
Anhang
Docker funktioniert vereinfacht ausgedrückt wie eine Micro Virtual Machine, in der nur ein einziger Prozess läuft. Die Container benötigen dafür kein eigenes OS sondern nutzen einen von anderen Prozessen isolierten Zuganz zum Kernel. Hierzu erweitert Docker die LXC-Funktionalität des Linux-Kernels; nähere Informationen dazu finden sich in dem entsprechenden Abschnitt der Docker FAQ.
Die wichtigsten Komponenten dieser Plattform sind:
- Images
Beinhalten alle Dateien und Verzeichnisse, die später im Container zur Verfügung stehen sollen und geben vor, welches Programm ausgeführt werden soll. Darüber hinaus legen sie die Schnittstellen mit der Umwelt fest (Netzwerk, Dateisystem und Umgebungsvariablen). - Container
Sind die Laufzeitumgebungen und basieren jeweils auf einem Image. Sie definieren, wie die Imageschnittstellen mit der Umgebung verbunden werden sollen. - Docker Engine
Kontrolliert und steuert die Container und kann durch den Client mit den docker- und docker-compose-Befehlen gesteuert werden. - Host
Ist die Umgebung, in der die Docker Engine und ihre Container laufen.
Befehl | Bedeutung |
---|---|
|
Listet laufende und beendete Container der Docker Engine auf |
|
sucht nach Images im Dockerhub Repository |
|
Erzeugt und startet einen Container mit Portmapping als Hintergrundprozess |
|
Stoppt einen Container |
|
Startet einen Container |
|
Löscht einen Container |
|
Listet die in der Docker Engine bereits geladenen Images |
|
Löscht ein Image |
|
Zeigt die Konfiguration eines Containers |
|
erzeugt eine interaktive Bash in einem laufenden Container |
|
gibt fortlaufend das Log des Containers aus |