Deployments leichtgemacht

Docker und Dokku: das PaaS-Traumpaar?
Kommentare

Das Deployment komplexer Webapplikationen ist eine Wissenschaft für sich. „Platform as a service“ (PaaS) wie Heroku oder Cloudcontrol haben den Markt revolutioniert, indem sie auch DevOps-unerfahrenen Entwicklern die Möglichkeit geben, ihre Anwendung mittels eines einfachen „git push“-Befehls zu deployen. Dokku ist eine Sammlung von Shell-Skripten, die es erlaubt, sich sein eigenes PaaS zu implementieren. Das Projekt ist nicht sehr komplex und darüber hinaus durch eine simple Plug-in-Architektur erweiterbar. In diesem Artikel wird demonstriert, wie man Dokku auf einem Server installiert und exemplarisch eine Anwendung dort deployt.

Dokku ist die kleinste auf Docker basierende PaaS-Lösung, die es gibt. Dokku ist Open-Source-Software und wurde von Jeff Lindsay entwickelt. Es besteht aus ungefähr 100 Zeilen Bashscript und nutzt die von Heroku Open Source zur Verfügung gestellten Build Packs, um definierte Umgebungen wie z. B. Rails- oder Node.js-Apps isoliert in Docker-Containern laufen zu lassen. Bei 9elements sind Docker und Dokku deshalb sehr interessante Technologien, weil sie den Deployment-Prozess von Applikationen signifikant vereinfachen. Es muss kein langwieriges Provisioning von Serverressourcen via Chef, Ansible oder Babushka stattfinden – ein einfaches git push genügt, um eine Applikation zu deployen. Jeder Entwickler, der Git bedienen kann, ist nun in der Lage, seine Arbeitsergebnisse zu deployen. Das ist insbesondere bei Feature Branches interessant, wo z. B. kurz ein Schulterblick seitens des Kunden benötigt wird.

Die Komponenten von Dokku

Dokku setzt voraus, dass man seine Applikation mit der Softwareversionskontrolle Git verwaltet. Ohne Git kann man Dokku leider nicht verwenden, da der Einstiegspunkt in das Deployment mittels eines Gitreceive-Hooks auf Serverseite stattfindet. Ähnlich wie bei Heroku richtet man bei Git einen weiteren Remote Server ein. Dokku lauscht nun auf diesem remote, und wenn per SSH ein Update gepusht wird, dann startet die eigentliche Mechanik von Dokku über einen Gitreceive-Hook (Abb. 1).

Im ersten Schritt startet Docker das Image einer Standarddistribution in einem Container. Der nächste Schritt wird „Buildstep“ genannt. Im ersten Schritt des Buildsteps wird die Applikation in den Container mittels tar übertragen. Buildstep analysiert danach das Git-Repository, indem es heuristisch testet, ob z. B. ein Gemfile für eine Ruby-Applikation vorhanden ist, oder ob es eine package.json-Datei, z. B. für eine Node.js-Applikation, gibt. Für die meisten Programmiersprachen sind diese so genannten Buildpacks verfügbar. Manche werden offiziell von Heroku bereitgestellt, andere wiederum werden von der Open-Source-Community gepflegt.

Im ersten Schritt startet Docker das Image einer Standarddistribution in einem Container. Der nächste Schritt wird „Buildstep“ genannt. Im ersten Schritt des Buildsteps wird die Applikation in den Container mittels tar übertragen. Buildstep analysiert danach das Git-Repository, indem es heuristisch testet, ob z. B. ein Gemfile für eine Ruby-Applikation vorhanden ist, oder ob es eine package.json-Datei, z. B. für eine Node.js-Applikation, gibt. Für die meisten Programmiersprachen sind diese so genannten Buildpacks verfügbar. Manche werden offiziell von Heroku bereitgestellt, andere wiederum werden von der Open-Source-Community gepflegt.

Nach dem Gitreceive können Plug-ins mittels des Plug-ins-Hooks ausgeführt werden. Die Applikation selbst wird über einen definierten Port verfügbar gemacht, der dann über einen NGINX-Proxy per HTTP zur Verfügung gestellt wird. SSH Command ist ein so genannter „convenient-wrapper“, um Shell-Befehle via SSH auszuführen.

Abb. 1: Die Komponenten von Dokku

Abb. 1: Die Komponenten von Dokku

Lebenszyklus einer Dokku-Applikation

Die Grafik in Abbildung 2 veranschaulicht noch einmal im Detail den Lebenszyklus einer Dokku-Applikation, inklusive der oben erwähnten Schritte.

Abb. 2: Lebenszyklus einer Docker-Applikation (Autor: Peter Roßbach)

Abb. 2: Lebenszyklus einer Docker-Applikation (Autor: Peter Roßbach)

Installation

Die Installation ist sehr einfach gehalten und versteckt sich hinter einem Shell-Skript, das per wget heruntergeladen wird: $ wget -qO- https://raw.github.com/progrium/dokku/v0.2.3/bootstrap.sh | sudo DOKKU_TAG=v0.2.3 bash

Alle notwendigen Pakete werden dann automatisiert heruntergeladen und installiert. Die gesamte Installation dauert ungefähr fünf Minuten. Als Basissystem haben wir ein Ubuntu 14.04 verwendet, doch auch auf anderen Systemen kann Dokku installiert werden. Eventuell muss man ein paar Anpassungen manuell vornehmen. Der Server kann wahlweise „echtes Metall“ sein oder ein virtualisierter Server, z. B. bei Digital Ocean. Man sollte allerdings beachten, dass der Server über genügend Arbeitsspeicher verfügt (> 512 MB), da sonst eine einwandfreie Installation nicht gewährleistet werden kann. Weiterhin benötigen wir eine Domain, bei der wir Zugriff per DNS haben, denn Dokku benötigt zwei A-Record-Einträge, davon einer als Wildcard.

Bei 9elements verwalten wir unsere Domains mit dem kostenpflichtigen Service DNSimple. Abbildung 3 zeigt einen Screenshot mit den konfigurierten Einträgen.

Wer eine genaue Anleitung mit mehr Hintergrundinformationen benötigt, der sei auf die README des offiziellen GitHub-Repositories von Dokku verwiesen.

Abb. 3: A-Record Einträge: Der Server selbst und ein Wildcard für die Dokku-Apps

Abb. 3: A-Record Einträge: Der Server selbst und ein Wildcard für die Dokku-Apps

Achtung: Dokku lässt sich in der Regel auch mit weniger Arbeitsspeicher installieren, allerdings benötigen manche Buildpacks (z. B. für Ruby on Rails) viel RAM. Weiterhin stehen die Fehlermeldungen nicht unbedingt in Relation zu einem Speicherüberlauf. Sollte z. B. beim Kompilieren einer Bibliothek mit gcc ein Fehler auftreten – beispielsweise das Problem, dass eine Linker-Datei nicht gefunden werden kann – so kann das mitunter an einem zu geringen Speicher liegen. Leider sagt einem der Linker-Prozess nicht, dass er die Datei deswegen nicht erstellen konnte. Der Prozess beendet ohne eine Fehlermeldung, und die Datei ist schlichtweg nicht vorhanden. Erst der nächste Kompiliervorgang, der diese Datei erwartet, beschwert sich dann über ihr Fehlen.

Konfiguration

Zunächst muss die Domain konfiguriert werden. In unserem Fall haben wir bei unserem DNS zwei A-Record-Einträge vorgenommen:

dokku.9elements.com
*.dokku.9elements.com

Bei dem zweiten Record handelt es sich um einen Wildcard-Eintrag. Dieser wird von Dokku dazu benutzt, Domains verfügbar zu machen. Damit Dokku weiß, unter welcher Domain Apps zur Verfügung gestellt werden, muss man in der Datei /home/dokku/VHOST die Domain eintragen. Weiterhin sollte man seinen SSH Key-hinterlegen:

$ cat ~/.ssh/id_rsa.pub | ssh root@dokku.9elements.com "sshcommand acl-add dokku sebastiandeutsch"

Achtung: Vergisst man diese Schritte, werden die Applikationen auf zufälligen Ports zur Verfügung gestellt.

Konfiguration

Als erstes Beispiel (Listing 1) deployen wir eine Heroku-Beispiel-App, die man unter folgendem URL finden kann: https://github.com/heroku/node-js-sample.

Listing 1
$ cd node-js-sample
$ git remote add dokku dokku@dokku.9elements.com:node-js-app
$ git push dokku master
Counting objects: 296, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (254/254), done.
Writing objects: 100% (296/296), 193.59 KiB, done.
Total 296 (delta 25), reused 276 (delta 13)
-----> Building node-js-app ...
       Node.js app detected
-----> Resolving engine versions

... Viel Output von Node.js und NPM ...

-----> Application deployed:
       http://node-js-app.dokku.9elements.com

Unter dem in Listing 1 genannten URL http://node-js-app.dokku.9elements.com läuft nun unsere Node.js-Applikation. Um die Funktionsweise von Dokku besser zu verstehen, muss man die Philosophie hinter Heroku kennen. Diese wird im nächsten Abschnitt genauer erklärt.

12 Factor

Die Zwölf-Faktoren-Methode ist eine Herangehensweise, mit der man Webapplikationen oder „Software-as-a-Service-Applikationen“ so bauen kann, dass das Deployment folgenden Bedürfnissen genügt:

  • Das Setup wird automatisiert, indem Setup-Schritte deklarativ beschrieben werden können.
  • Es wird angestrebt, die Applikation auf einer möglichst breiten Masse an Betriebssystemen laufen zu lassen. Allerdings darf man sich mit den Buildpacks nicht in falscher Sicherheit wiegen. Man definiert Abhängigkeiten zwar deklarativ, allerdings werden die Versionen der installierten Komponenten nicht überwacht. In der Regel installieren Buildpacks immer die neuste Version der Distribution des Betriebssystems. Was heute funktioniert, muss nicht zwangsläufig in der Zukunft auch funktionieren.
  • Die App sollte sich auf modernen Cloud-Plattformen deployen lassen.
  • Die Skalierung der Performance sollte ohne größere Änderungen an der Toolchain durchgeführt werden können.

Damit eine Applikation nach dieser Philosophie implementiert wird, muss sie zwölf Faktoren genügen:

  1. Die Codebase sollte via Versionskontrolle getrackt werden. Sie kann jederzeit auch mehrfach auf verschiedene Systeme deployt werden.
  2. Abhängigkeiten sollen explizit deklariert und isoliert werden. Bei einer Ruby-on-Rails-Anwendung geschieht das z. B. durch das Gemfile-Manifest. Dort werden alle benötigten Bibliotheken nebst Versionsnummern deklarativ festgehalten. Bei einer Node.js-Applikation wird dies z. B. über die package.json-Datei deklariert.
  3. Umgebungsvariablen steuern die Konfiguration. Ziel ist es, eine strikte Trennung zwischen Code und Konfiguration herzustellen.
  4. Weitere Services wie z. B. eine MySQL-Datenbank oder ein SMTP-Dienst (wie Postmark) sind isoliert zu betrachten.
  5. Build, Release, Run – jede Applikation besteht aus diesen voneinander abgegrenzten Phasen.
  6. Die App sollte als Share-Nothing-Prozess laufen.
  7. Die App sollte „self-contained“ laufen. Das heißt, es wird nicht noch während der Ausführung ein Webserver „injectet“. Die App sollte einen HTTP-Service auf einem bestimmten Port bereitstellen. Load Balancing oder Verschlüsselung per HTTPS passieren in einem nachgelagerten Schritt über einen Reverse Proxy z. B. via NGINX.
  8. Die Skalierung der Applikation basiert darauf, dass man mehrere Prozesse der Applikation startet.
  9. Die Startup-Zeit sollte minimiert werden. Weiterhin sollte sie sich sauber beenden lassen.
  10. Development-, Staging- und Production-System sollten so gleich wie möglich sein.
  11. Logs sollten wie Event Streams behandelt werden. Metriken sollten am besten über einen externen Dienst gesammelt werden.
  12. Administrative Aufgaben sollten als einzelne Prozesse gestartet werden können.

Nutzt man eines der bekannteren Frameworks, wie z. B. Ruby on Rails oder Express.js, dann sind viele dieser zwölf Faktoren bereits erfüllt. Damit bei Ruby on Rails alle Faktoren erfüllt sind, muss man das „factor12“-Gem in das Gemfile für das Production Environment einfügen. Generell gilt: Was sich auf Heroku deployen lässt, kann auch via Dokku deployt werden. Wenn die Applikation ein komplexeres Setup benötigt, kann man sich eigene Buildpacks schreiben. Die Erstellung eines eigenen Buildpacks wird in einem späteren Abschnitt erklärt.

Das Dokku-Ökosystem

In unserem Testbeispiel haben wir eine sehr einfache Node.js-Applikation, ohne größere Abhängigkeiten wie z. B. eine Datenbank, auf Dokku deployt. Eine „waschechte“ Webanwendung ist allerdings um einige Komponenten komplexer. In der Regel gibt es nämlich eine Datenbank (MySQL, Postgres), die sich um die Persistenz von Daten kümmert. Spielt Performanz eine Rolle, kann es sein, dass ein In-Memory-Cache (Memcache, Redis) benötigt wird.

Möchte man auch diese Services mit Dokku betreiben, dann sollte man einen Blick auf die Plug-ins werfen. Doch bevor ich gängige Plug-ins beschreibe, möchte ich die Plug-in-Architektur von Dokku vorstellen.

Die Plug-in-Architektur von Dokku

Plug-ins werden bei Dokku in dem Verzeichnis /var/lib/dokku/plugins abgelegt. Die einfachste Variante ist das Klonen via Git direkt in das Verzeichnis (Abb. 4). Wie auch Dokku sind Plug-ins nichts anderes als Shell-Skripte.

Ein Plug-in besteht dann z. B. aus einem commands-Skript, das neue Kommandos für Dokku bereitstellt, und einem install-Skript, das durch den Befehl dokku plugins-install aufgerufen wird. Dokku-Plug-ins sind Fluch und Segen zugleich. Segen, weil es durch die Dokku-Community sehr einfach wird, bestimmte Services wie z. B. Datenbanken einzusetzen. Fluch, weil diese Plug-ins auch weiterentwickelt werden müssen. Es kann z. B. sein, dass ein bestimmtes Plug-in nur mit einer bestimmten Version von Dokku zusammenarbeitet, und dass zwei verschiedene Plug-ins auf diese Weise einen Abhängigkeitskonflikt verursachen. Da es für Dokku keine explizite Verwaltung von Abhängigkeiten gibt, kann es mitunter eine ziemlich komplizierte Angelegenheit sein, beide Plug-ins zum Laufen zu bekommen. Weiterhin ist für die Funktionalität oder Stabilität eines Plug-ins der Maintainer selbst verantwortlich. Manchmal sind diese Plug-ins aber verwaist, und es kann Tage oder Wochen dauern, bis sich jemand eines etwaigen Problems annimmt. Da die Buildpacks Open Source sind, kann man natürlich ein Plug-in forken und sein gutes altes Bash-Know-how anwenden, um das Problem selbst zu lösen. Wer sein Wissen auffrischen möchte, dem sei dieses Online-Tutorial ans Herz gelegt: http://bin-bash.de/

Tipp: Bei 9elements haben wir sowohl Dokku als auch die eingesetzten Plug-ins via Git geforkt, sodass wir jederzeit in der Lage sind, eine bestimmte Dokku-Installation exakt wieder herzustellen. Updates der Infrastruktur werden dann via Git getaggt, sodass man Änderungen am System exakt nachvollziehen kann. Weiterhin kann man in den Projekten in einer Datei vermerken, welche Git-Tags von Dokku und Plug-ins dann letztendlich zum Einsatz kommen.

Abb. 4: Verzeichnisstruktur des Postgres Plugins

Abb. 4: Verzeichnisstruktur des Postgres Plugins

Sinnvolle Plug-ins

Im nächsten Abschnitt möchte ich einige Plug-ins vorstellen, die für den aktiven Betrieb einer Webanwendung unentbehrlich sind:

Custom Domains: Das auf Github erhältliche Plug-in ist eine Möglichkeit, eine Dokku-Applikation mit echten Domains zu betreiben. Die Syntax des Plug-ins lehnt stark an Heroku an.

Persistent Storage: Startet man einen Container neu, sind alle Änderungen im Dateisystem verloren. Es kann jedoch sein, dass man z. B. Dateien wie Uploads oder Logs nicht verlieren möchte. Mit dem auf Github erhältlichen Plug-in ist es möglich, bestimmte Ordner des Dateisystems via Docker Volumes in das Dateisystem des Hosts zu mounten. Auf diese Art und Weise sind die Dateien auch nach dem Neustart eines Containers noch verfügbar.

Achtung: Die Nutzung dieses Plug-ins verstößt in gewisser Hinsicht gegen die Zwölf-Faktoren-Philosophie. Eine strikte Befolgung würde Uploads auf einen anderen Service (z. B. Amazon S3) verlagern, damit man die Applikation auch sauber skalieren kann. Zum Testen einer App oder zum Betrieb einer Staging-Umgebung kann dieses Plug-in allerdings sehr nützlich sein.

PostgreSQL-Plugin for Dokku: Hierbei handelt es sich um ein Plug-in, mit dem man einen PostgreSQL-Container laufen lassen kann. Es kann auf Github heruntergeladen werden. Erstellt man eine Datenbank, zu der es bereits eine gleichnamige App gibt, linkt Dokku die beiden via Umgebungsvariablen zusammen. Die Daten des PostgreSQL-Containers werden via Docker Volumes in das Dateisystem des Hosts gemountet. So sind auch nach einem Neustart des Containers noch alle Dateien vorhanden.

Eine komplette Übersicht aller Plug-ins gibt es auf den Plug-in-Seiten von Dokku.

Troubleshooting

Gerade beim ersten Betrieb von Dokku (inklusive Plug-ins und Buildpacks) kann es sein, dass Dinge nicht so laufen, wie man sich das vorstellt. Dann wünscht man sich die Möglichkeit, bestimmte Skripte zu debuggen. Dazu hat sich der Entwickler einen guten Mechanismus einfallen lassen: Man kann in /home/dokku/ eine Datei dokkurc anlegen und in ihr Folgendes vermerken: export DOKKU_TRACE=1.

Danach loggt Dokku jeden Befehl, der ausgeführt wird. Das ist für die Plug-in-Entwicklung oder das Debugging sehr interessant. Einen weiteren Test, den ich sehr empfehlen kann, ist es, eine lauffähige Dokku-Installation einmal zu rebooten, um zu sehen, ob alle Container und Prozesse auch in der richtigen Reihenfolge wieder gestartet werden. An dieser Stelle hatten wir z. B. Probleme mit dem MongoDB-Plug-in, denn der Container hat einen Reboot schlichtweg nicht überlebt, und die Datenbank ließ sich nur mit großem manuellem Aufwand wieder zum Laufen bringen.

Custom Buildpacks

Stößt man beim Deployment an die Grenzen der Buildpacks, die Open Source von Heroku zur Verfügung gestellt werden, dann besteht die Möglichkeit, ein eigenes Buildpack zu entwickeln. Ein Buildpack ist ebenfalls nichts anderes als eine Sammlung von Bash-Skripten. So gibt es z. B. ein detect-Script, das testet, ob eine Applikation mit Node.js entwickelt wurde. Im Folgenden testet das Skript das Vorhandensein einer package.json-Datei:

#!/usr/bin/env bash
# bin/detect 

if [ -f $1/package.json ]; then
 echo "Node.js" && exit 0
else
 echo "no" && exit 1
fi

Die eigentlich Magie passiert im compile-Skript, das deklarativ alle Abhängigkeiten installiert, ggf. die Applikation analysiert und vor dem Start wiederum weitere Präprozessoren startet. Unser Entwicklungsstack bei 9elements ähnelt stark dem Web Starter Kit von Google und umfasst z. B. die Tools Gulp.js, haml (als Präprozessor für HTML), SASS (als Präprozessor für CSS) und Compass (als Framework für SASS). Das Standard-Buildpack für Node.js von Heroku stellt diese Tools allerdings nicht zur Verfügung. Eine Liste der verfügbaren Buildpacks findet sich auf  Herouku.com.

Um alle diese Tools zu installieren und unsere Websites sauber laufen zu lassen, haben wir uns ein eigenes Buildpack entwickelt und bei Github online gestellt. Um das Buildpack mit Dokku nutzen zu können, müssen Sie in Ihrem Git-Repository eine Datei mit dem Namen .env erstellen und dort eine Umgebungsvariable BUILDPACK_URL exportieren:

export BUILDPACK_URL=https://github.com/9elements/heroku-buildpack-nodejs-gulp-haml-sass-compass.git

Wenn man lediglich mehrere Buildpacks gleichzeitig verwenden will (manche Rails-Apps haben Node.js als Abhängigkeit), muss man sich aber nicht zwangsläufig ein eigenes Buildpack schreiben. Man kann mit einem Trick auch mehrere Buildpacks gleichzeitig benutzen. Dazu benutzt man das Multi Buildpack und kann dann in der Datei .buildpacks angeben, welche Buildpacks benutzt werden sollen:

$ cat .buildpacks
https://github.com/heroku/heroku-buildpack-nodejs.git#0198c71daa8
https://github.com/heroku/heroku-buildpack-ruby.git#v86

Fazit

Dokku hat unseren Deployment-Prozess für Test und Staging-Systeme deutlich vereinfacht. Auch das Hosting einfacher Apps in virtualisierten Umgebungen ist damit ohne größere Probleme möglich. Allerdings würden wir mit Dokku keine Mission-Critical-Applikationen deployen. Das hat mehrere Gründe: Der Hauptgrund ist, dass die Technologie zu jung und noch zu sehr in Bewegung ist. Gerade an der Stelle, wo man eine Dokku-Installation upgraden möchte, knirscht und hakt es mitunter. Man muss dann sehr genau untersuchen, woher die Fehler kommen. Ein weiterer Grund ist die Fragilität der Plug-ins. So ist es bereits vorgekommen, dass nach einem Servercrash ein Plug-in einfach nicht mehr laufen wollte. Es hat dann recht lange gedauert, den Fehler zu identifizieren und zu beheben. Auch ist die Community bereits weitergezogen. Projekte wie Dies oder Flynn haben mehr Dynamik und sind auch leistungsfähiger. Wer also mal schnell ein kleines Nebenprojekt bei Digital Ocean online stellen will und keine Lust hat, sich eine Umgebung mit Chef zu bauen, der ist mit Dokku gut bedient. Man merkt jedoch schnell, dass man an seine Grenzen stößt und sich ggf. mehr Flexibilität wünscht.

Was wird Dokku nie können

Dokku wird laut GitHub-Readme nie Multi-Host-fähig werden. Wer also eine Applikation deployen möchte, die so groß ist, dass sie verteilt auf mehreren Server laufen muss, der sollte sich nach einer anderen Lösung wie z. B. Flynn umschauen.

Weiterhin wird es keine Mandantenfähigkeit und keine Clientapplikation gehen. Wer Dokku bedienen will, der muss SSH-Zugriff auf den Server haben. Es sind keine weiteren Rechteeinschränkungen vorgesehen.

 

Aufmacherbild: Wedding couple Foto via Shutterstock / Urheberrecht: Mr. Exen

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -