Das Image das wir im vorherigen Blog-Post gebaut haben war sehr einfach. In dieser Lektion bauen wir ein etwas komplexeres Dockerfile das uns ein komplexeres Image erzeugt. Dieses Dockerfile kann man in der folgenden Abbildung sehen.
Bevor wir mit dieser Datei arbeiten schauen wir uns einmal an, was genau in der Datei steht und was die einzelnen Zeilen bedeuten. Die oberste Zeile ist eine Kommentarzeile. Wie in jeder anderen Script-Sprache oder Script-Datei kann man natürlich auch in einem Dockerfile Kommentare unterbringen. Und wie in jeder anderen Programmiersprache ist es auch ratsam diese Kommentare ausgiebig zu nutzen damit man selbst nach einiger Zeit (oder möglicherweise auch andere Personen) anhand der Kommentare erkennen können was die Aufgabe des Dockerfile ist bzw. was die einzelnen Zeilen bewirken. Zeilenkommentare werden durch das Hash-Zeichen (#) eingeleitet.
In der Zeile FROM wird angegeben auf Basis welchen anderen Images das aktuelle Image erzeugt wird. Docker Images bauen aufeinander auf und so kann man ein bereits vorhandenes Docker Image verwenden um darauf aufbauend ein neues Docker Image zu erzeugen. In unserem Fall basiert das neue Image auf dem Alpine Image. Nach dem Doppelpunkt können wir noch eine Versionsnummer des Images definieren – wir nehmen hier die neuste Version die wir bekommen können, indem wir latest spezifizieren. Grundsätzlich muss man das nicht, da Docker davon ausgeht wenn wir nichts anderes angeben, dass wir immer die neuste Version des Images haben möchten. Optional können auch noch ein Tag oder der Digest des Images auf dem wir unser neues Image aufbauen wollen angegeben werden. Es wird sehr davon abgeraten in einem Dockerfile mehr als eine FROM Klausel zu verwenden, da es zu Konflikten kommen kann, wenn man ein Image auf mehr als einer Quelle aufbaut. Beim Suchen des Images wird zunächst auf dem Docker Host nach dem Image gesucht. Konnte es dort nicht gefunden werden wird das Image automatisch vom Docker Hub heruntergeladen.
In der Zeile MAINTAINER wird angegeben wer für das neue Image zuständig ist. Wo genau man diese Information im dockerfile unterbringt ist nicht festgelegt, es ist aber Konvention diese Zeile direkt auf die Zeile FROM folgen zu lassen. Mehrere Befehle werden mit && getrennt.
Mit Hilfe des Befehls RUN kann man jede beliebige Anweisung im Docker Container ausführen lassen. Möchte man mehr als eine Anweisung ausführen, so ist es Konvention, dass man nur einmal den Befehl RUN im Dockerfile anführt und alle Anweisungen hintereinanderschreibt. Es gibt zwei verschiedene Syntax-Arten für den RUN Befehl. Die erste ist
RUN <command>
Der Befehl so wird immer über /bin/sh -c ausgeführt.
Die zweite Syntax-Art ist diese
RUN ["exec", "<arg1>", … , "<argn>"]
Hier wird nicht /bin/sh -c ausgeführt, d.h. es gibt keine Ersetzung der Variablen (z.B. $USER).
In unserem Beispiel sieht der RUN Befehl wie folgt aus
RUN apk add --update supervisor nginx && rm -rf /var/cache/apk/*
An den && können wir direkt erkennen, dass es sich um zwei Befehle handelt. Der erste Befehl vor dem && installiert supervisor und nginx. Der zweite Befehl löscht den apk-Cache indem alle Dateien die unterhalb von /var/cache/apk/ liegen entfernt werden. -rf löscht die Dateien rekursiv und erzwingt das Löschen der Dateien.
Als nächstes kommen ein paar Zeilen mit COPY Anweisungen. COPY ermöglicht es Dateien aus dem Dateisystem des Docker Hosts in das Dateisystem der Images zu kopieren das man über das Dockerfile definiert hat. Man sollte hier immer mit absoluten Pfaden arbeiten. Tut man das nicht geht der COPY Befehl davon aus, dass man relativ zum root-Pfad (/) arbeitet.
In unserem Beispiel gibt es im Verzeichnis in dem das dockerfile liegt die Datei start.sh und ein Verzeichnis files.
Im Verzeichnis files wiederum gibt es drei Dateien
All diese Dateien sind Konfigurationsdateien für die verschiedenen Programme und werden durch die COPY Befehle an die entsprechenden Stellen im Dateisystem des Images kopiert.
Nach den Zeilen mit den COPY Befehlen kommt eine Zeile mit einem ADD Befehl. Im Prinzip funktioniert ADD wie COPY mit dem Unterschied, dass ADD zusätzlich mit TAR Archiven und URLs umgehen kann. Die Zeile
ADD webroot.tar /
Kopiert das TAR Archiv webroot.tar in das root Verzeichnis des Images und entpackt die im Archiv enthaltenen Dateien dort. Im Archiv befinden sich die Inhalte für unsere Website.
Nachdem wir die Dateien ins Image kopiert haben nutzen wir wieder der RUN Befehl um über chown Berechtigungen auf das neu erstellte Verzeichnis so zu ändern, dass der nginx Server zugreifen kann.
In der nächsten Zeile verwenden wir den Befehl EXPOSE. Mit Hilfe von EXPOSE können wir im Image Ports in die Außenwelt freischalten. In unserem Beispiel ist das der Port 80, da wir ja einen Webserver erzeugen wollen. Neben dem Port kann man auch noch das Protkoll für den Port angeben, in unserem Fall tcp.
Der Befehl ENTRYPOINT gibt die Haupt-Applikation im Container an, d.h. wenn diese Applikation im Container beendet wird, wird auch der gesamte Container beendet. Im Prinzip sorgt ENTRYPOINT dafür, dass sich der Container wie eine Applikation verhält. Die Applikation die über den Befehl ENTRYPOINT angegeben wird kann nicht über die Argumente eines docker run Befehl überschrieben werden. Wie beim RUN Befehl gibt es auch für ENTRYPOINT zwei Syntax-Arten die den beiden Syntax-Arten des RUN Befehls entsprechen. Grundsätzlich kann man mehr als eine ENTRYPOINT Zeile in einem dockerfile haben, Docker ignoriert aber alle ENTRYPOINT Zeilen bis auf die letzte. Über die Zeile
ENTRYPOINT ["supervisord"]
Geben wir an, dass der Dienst supervisord (das ist ein Prozess-Controll System http://supervisord.org/) als Entrypoint für das Image verwendet wird.
In der letzten Zeile des dockerfile nutzen wir den Befehl CMD. Über CMD kann man, genau wie über RUN jeden beliebigen Befehl ausführen lassen. Der einzige Unterschied zwischen beiden ist wann der Befehl ausgeführt wird. Befehle die über RUN gestartet werden, werden während der Erstellung des Images ausgeführt. Befehle die über CMD gestartet werden, werden ausgeführt wenn vom Image aus ein Container erzeugt wird.
Wir werden nun aus diesem Dockerfile ein neues Image, aus dem wir dann im nächsten Schritt einen Docker Container instanziieren.
1.) Um das Image aus dem dockerfile zu erzeugen müssen wir den Befehl
docker image build C:\temp\complexdocker -t webserver
eingeben. Dabei ist C:\temp\complexdocker das Verzeichnis in dem das dockerfile und die benötigten Dateien liegen (ggf. müsst ihr das auf Euren Pfad anpassen). Über -t webserver geben wir dem neuen Image ein Tag mit dem wir leicht damit arbeiten können.
2.) Nachdem der Befehl erfolgreich durchgelaufen ist wurde das neue Docker Image erstellt. Über den Befehl
docker container run -d --name ws -p 8080:80 webserver
erstellen wir einen neuen Dockercontainer. Das ist die inzwischen bekannte Syntax. Zunächst wird über -d angegeben dass der Dockerprozess vom PowerShell Fenster abgekoppelt werden soll, so dass wir weiter mit unserer Konsole arbeiten können. –name ws gibt unserem neu erstellten Docker Container den Namen ws und über -p 8080:80 mappen wir den externen Port 8080 auf den internen Port 80. webserver schließlich gibt das Docker Image an von dem wir einen Container instanziieren wollen. Wurde der Docker Container erfolgreich erstellt, so wird der Digest des Containers zurückgegeben.
3.) Da unser Docker Container nun läuft können wir jetzt auch auf den Webserver zugreifen der im Container ausgeführt wird. Hierzu öffnen wir einfach einen Browser und geben die URL http://65.52.229.47:8080 Hat alles funktioniert wird die Webseite wie im Screenshot angezeigt.
4.) Auch nach dieser Übung räumen wir wieder auf. Zunächst müssen wir den Docker Container mit
docker container stop ws
beenden. Wurde der Befehl fehlerfrei ausgeführt, so wird er mit dem Namen des Containers bestätigt.
5.) Nachdem der Container gestoppt ist können wir ihn nun auch entfernen. Hierzu nutzen wir den Befehl
docker container rm ws
Auch dieser Befehl wird mit dem Namen des Containers bestätigt.
6.) Nachdem wir den Container entfernt haben können wir nun zu guter Letzt noch das Image entfernen. Hierzu geben wir den Befehl
docker image rm webserver
ein.