link

Logo for Markus Rennings

Docker

Heute haben wir mit Docker angefangen. Natürlich zunächst einmal mit den Grundlagen. Als erstes stellt sich da natürlich die Frage:

#Was ist Docker?

Docker ist eine (die?) Open-Source Container-Engine. Geschrieben in Go. Docker nutzt einige Features des Linux-Kernels, wie z. B. Namespaces und Control Groups. Daher setzt es Linux voraus, aber es ist auch unter MacOS und Windows lauffähig – dann wird der Linux-Kernel z. B. über WSL (Windows Subsystem für Linux) zur Verfügung gestellt.

Docker ist keine virtuelle Maschine, da es den Kernel des unterliegenden Betriebssystems nutzt, um z. B. Hardware anzusprechen, oder auch für den Netzwerk-Stack. Docker stellt aber für die einzelnen Container einen eigenen abgeschirmten Bereich zur Verfügung, quasi ein Sandbox.

#Es kommt auf die Größe an

Man braucht zwar die Systemebene nicht mit in den Container packen, aber das Userland, also die Abhängigkeiten, die die containerisierte App benötigt. Dazu kann man von Docker Hub bereits fertige Images einbinden. Es gibt für quasi jede Distribution Images, oder auch z. B. für Node-Anwendungen. Wenn man allerdings eine vollwertige Distro einbindet, können die erzeugte Images sehr schnell sehr groß werden. Eine Alternative bietet z. B. Alpine Linux. Eine weitere Alternative, wenn es möglich ist, können statisch gelinkte Binaries sein, die keine Abhängigkeiten haben. Dann braucht man auch kein Base-Image. Hier bietet sich beispielsweise Go als Programmiersprache an. Für C könnte evtl. Fefes dietlibc helfen, die Binaries entsprechend abzuspecken.

#Dockerfile

Das Dockerfile ist der Bauplan für das Image, von dem danach der/die Container gestartet werden können. Dies ist eine einfache Textdatei, mit einer Anweisung pro (logischer) Zeile. Klingt zunächst einfach, kann aber auch recht komplex werden. Ein Dockerfile kann im einfachen Fall z. B. so aussehen:

FROM node:18
ENV TZ="Europe/Berlin"
COPY server.js /src/
EXPOSE 5678
USER node
CMD ["node", "/src/server.js"]

Mit FROM wird das Image bestimmt, auf dem man den eigenen Container aufbaut, hier das node-Image in Version 18. Dieses Image bringt alles mit, was man zur Ausführung von Node-Applikationen braucht, dafür ist das image dann aber auch über 1 GB schwer.

ENV erstellt Umgebungsvariablen, die im Build-Prozess und auch später im Container zur Verfügung stehen. Worauf man allerdings achten sollte, ist, dass diese Variablen nicht gelöscht werden. Sie stehen ab der Deklaration in jeder Schicht zur Verfügung, können notfalls also auch zu Seiteneffekten führen, vor allem, wenn man aus diesem Image wieder weitere ableitet. Braucht man die Variable nur für den Build-Prozess ist ARG wohl die bessere Alternative.

COPY macht genau das, es kopiert die angegebene Datei (Wildcards erlaubt) aus dem »Live-Dateisystem« in das Dateisystem des Images, in dem angegebenen Pfad. Falls vorher ein WORKDIR festgelegt wurde, kann der Pfad im Image auch relativ zum WORKDIR angegeben werden. Mit EXPOSE gibt man einen Port im späteren Container frei, der dann entsprechend angesprochen werden kann. USER gibt den Username an, der ab dem Zeitpunkt – also auch später im Container – genutzt werden soll. Und schließlich wird noch mit CMD angegeben, welcher Befehlt bei Start des Containers ausgeführt werden soll. Dies waren nicht alle möglichen Befehle, die im Dockerfile genutzt werden können. Für eine komplette Übersicht empfehle ich die Dokumentation.

#Image erstellen

Nun haben wir den Bauplan fertig und können das Image erstellen. Die ist recht einfach und geschieht mit dem Befehl

docker build -t name .

Natürlich vorausgesetzt, man befindet sich im Verzeichnis mit dem Dockerfile und den benötigten Dateien. -t gibt einen Namen für das Image an und kann bei Bedarf auch um einen tag (mit Doppelpunkt vom Namen getrennt) erweitert werden. Nun sollte man das image mit

docker images

gelistet bekommen.

#Vom Image zum Container

Nun kann man das Image starten, wozu man den Befehl

docker run <imagename>

nutzt. In meinem Beispiel hier also docker run name. Wenn es ein Serverdienst im Container ist – also wahrscheinlich in den meisten Fällen – sollte man ncoh den Parameter -d (daemonize) mit angeben, damit sich der Container in den Hintergrund legt und man die Shell weiternutzen kann.

Mit docker ps kann man sich alle laufenden Container anzeigen lassen, mittels docker stop <id> einen Container stoppen.