Mit Docker automatisiert Anwendungscontainer erstellen

Docker-Container von Hand zu konfigurieren ist zwar praktisch, birgt allerdings auch Nachteile. Deshalb kann es sinnvoll sein, das Verpackungstool automatisiert zu verwenden.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 18 Min.
Von
  • Golo Roden
Inhaltsverzeichnis

Docker-Container von Hand zu konfigurieren ist zwar praktisch, birgt allerdings auch Nachteile. Deshalb kann es sinnvoll sein, das Verpackungstool automatisiert zu verwenden.

Nachdem die Grundlagen von Docker bereits im ersten Teil des Artikels zur Sprache kamen und der Schwerpunkt auf der manuellen Konfiguration der Container lag, soll es nun um das automatisierte, reproduzierbare Erzeugen von Containern und deren Zusammenspiel gehen. Container von Hand einzurichten und das Ergebnis anschließend als Image zu speichern, weist in der Praxis einige Nachteile auf: Zum einen ist der Vorgang nicht wiederholbar, zum anderen ist er unter Umständen sehr mühsam. Abhilfe schafft eine Datei namens Dockerfile, die die Bauanleitung für ein Image enthält.

Es handelt sich dabei um eine einfache Textdatei, die Anweisungen für Docker enthält. Das Ziel eines Dockerfiles entspricht dem von Vagrantfiles: Statt Images beziehungsweise virtuelle Maschinen dauerhaft speichern zu müssen, genügt es, die weitaus kleinere Anleitung zu hinterlegen. Im Falle eines Falles lässt sich ein Image beziehungsweise eine virtuelle Maschine anhand dieser erneut auf die gleiche Art erstellen.

Die Kommandos innerhalb eines Dockerfiles sind stets einzeilig und beginnen mit einem Schlüsselwort. Zwar ist die Groß-/Kleinschreibung für Docker unerheblich, allerdings hat sich eine konsequente Großschreibung für Schlüsselwörter eingebürgert. Dockerfiles dürfen zudem Kommentare enthalten, die das #-Zeichen einleitet.

Ein minimales Dockerfile enthält mindestens die Angabe des FROM-Schlüsselworts, das das gewünschte Basis-Image angibt. Bei Bedarf lässt sich zudem ein Tag eintragen, und auch das Verzeichnen der Autoren samt Kontaktdaten empfiehlt sich:

# goloroden/nodejs
# VERSION 0.0.1

FROM ubuntu
MAINTAINER Golo Roden <webmaster@goloroden.de>

Um ein Image auf Basis dieses Dockerfiles zu erzeugen, dient der build-Befehl von Docker. Er erwartet die Angabe des Verzeichnisses, das das Dockerfile enthält und im weiteren Verlauf als Build-Kontext bezeichnet wird:

$ docker build .

Nun führt das Programm die im Dockerfile beschriebenen Schritte nacheinander aus und erzeugt für jeden Zwischenschritt zunächst einen temporären Container, den es anschließend in ein temporäres Image umwandelt. Zuletzt gibt Docker die ID des erzeugten finalen Images aus:

$ docker build .
Step 0 : FROM ubuntu
---> 9cd978db300e
Step 1 : MAINTAINER Golo Roden <webmaster@goloroden.de>
---> Running in 968111fc369c
---> a5ed8e1b2435
Successfully built a5ed8e1b2435

Ein Blick auf die Liste der im System hinterlegten Images bestätigt, dass das neue Image erfolgreich erzeugt und hinzugefügt wurde:

$ docker images
REPOSITORY TAG IMAGE_ID CREATED VIRTUAL SIZE
<none> <none> a5ed8e1b2435 2 minutes ago 204.4 MB
...

Führt man den build-Befehl erneut aus, fällt die deutlich höhere Verarbeitungsgeschwindigkeit auf: Docker greift nun auf die zuvor erzeugten temporären Images zurück und führt nur noch jene Schritte tatsächlich aus, die ein anderes Ergebnis nach sich ziehen würden. Das Programm betreibt also nur den minimal notwendigen Aufwand zum Erzeugen eines Images.

In der Regel soll ein fertiges Image einen Namen erhalten, um es komfortabel verwenden zu können. Hierzu dient das tag-Kommando:

$ docker tag a5ed8e1b2435 goloroden/nodejs

Gibt man außer dem Namen ein Tag (beispielsweise eine Versionsnummer) an, ist es ratsam, das Kommando zusätzlich mit der Angabe ":latest" auszuführen. Das Vorgehen stellt sicher, dass das Image auch ohne die explizite Angabe eines Tags ansprechbar ist:

$ docker tag a5ed8e1b2435 goloroden/nodejs:0.10.26
$ docker tag a5ed8e1b2435 goloroden/nodejs:latest

Optional lassen sich ein Name und ein Tag bereits mit dem build-Kommando angeben, indem man den Parameter -t übergibt:

$ docker build -t goloroden/nodejs .

Hat man ein Basis-Image erzeugt, kann man es anschließend mit Leben füllen. Dazu dient das Schlüsselwort RUN, das beliebige Kommandos für die Konsole entgegennimmt:

[...]
MAINTAINER Golo Roden <webmaster@goloroden.de>

RUN apt-get update
RUN apt-get install -y wget git
RUN wget http://nodejs.org/dist/v0.10.26/node-v0.10.26-linux-x64.tar.gz
RUN tar xvfz node-v0.10.26-linux-x64.tar.gz
RUN rm node-v0.10.26-linux-x64.tar.gz
RUN mv ./node-v0.10.26-linux-x64 /opt/node

Es gilt, darauf zu achten, dass die Kommandos ohne Interaktion ausführbar sind. Daher muss beispielsweise der Aufruf von "apt-get install" mit dem Parameter -y erfolgen, der sämtliche Nachfragen während der Paketinstallation automatisch beantwortet.

Wie bereits angesprochen, erzeugt Docker für jeden einzelnen Schritt einen neuen temporären Container und ein entsprechendes Image: Daher erhöht sich mit der Anzahl der verwendeten RUN-Schlüsselwörter auch die Anzahl der Layer, auf denen das endgültige Image aufbaut. Docker weist allerdings eine Obergrenze von maximal 127 Layern auf, vor Version 0.7.2 lag sie bei lediglich 42. Es schadet deshalb nicht, mehrere Kommandos in einem Aufruf zusammenzufassen.

Vorsicht ist übrigens geboten, wenn Kommandos nicht global zur Verfügung stehen oder Pfade nicht absolut angegeben werden sollen: Ein Verzeichniswechsel durch den cd-Befehl wirkt sich nämlich nur für den zugehörigen RUN-Schritt aus – alle weiteren starten erneut im Wurzelverzeichnis. Abhilfe schafft das Schlüsselwort WORKDIR, dem sich ein Verzeichnispfad übergeben lässt. Alle nachfolgenden RUN-Schritte führt Docker im Anschluss im angegebenen Verzeichnis aus. Dementsprechend darf WORKDIR innerhalb eines Dockerfiles mehrfach vorkommen.

Zusätzlich ist auch die Angabe des Schlüsselworts USER möglich, dem man entweder einen Benutzernamen oder eine Benutzer-ID übergeben kann. Alle Schritte innerhalb des Dockerfiles werden daraufhin mit den Rechten des angegebenen Benutzers ausgeführt.

Obwohl Node.js nun im erzeugten Image installiert ist, lässt sich noch nicht darauf zugreifen. Die Ursache hierfür besteht in der fehlenden Ergänzung der PATH-Variablen, sodass das System nicht weiß, in welches Verzeichnis Node.js installiert wurde. Zur Ergänzung von Umgebungsvariablen dient in einem Dockerfile das Schlüsselwort ENV:

[...]
RUN mv ./node-v0.10.26-linux-x64 /opt/node

ENV PATH $PATH:/opt/node/bin

Zu guter Letzt fehlt noch die Möglichkeit, einem Image gezielt Verzeichnisse und Dateien aus dem Wirtssystem hinzuzufügen. Das ist beispielsweise dann erforderlich, wenn Quellcode vom Wirt in das Image übertragen werden soll. Diese Integration übernimmt das ADD-Schlüsselwort, das zwei Parameter erwartet:

  • Zum einen erfordert ADD die Angabe der zu kopierenden Quelle. Dabei kann es sich um ein Verzeichnis oder eine Datei handeln. Allerdings unterstützt Docker dies nur für Verzeichnisse und Dateien, die sich innerhalb des Build-Kontextes befinden. Alternativ lässt sich eine URL angeben, sodass man beispielsweise auch Dateien direkt aus GitHub einbinden kann.
  • Zum anderen erfordert ADD die Angabe des Ziels, wohin kopiert werden soll. Dabei handelt es sich um ein Verzeichnis innerhalb des Images, dessen Pfad stets absolut anzugeben ist.