Worum gehts?

Konferenzen und E-Books sind voll davon: Container. Wenn über Container in der Software-Entwicklung gesprochen wird, fällt es meist zusammen mit dem Wort Docker. Dieser Blog-Post beschäftigt sich mit Docker und den Containern und soll einen kurzen Überblick über die damit im Zusammenhang stehenden Begriffe geben.

Docker Logo

Was ist Docker?

Fangen wir mit dem Begriff Docker an. Docker ist eine Firma die eine Plattform rund um Container bereitstellt. Außerdem ist diese Firma (damals noch dotCloud) auch der Initiator des gleichnamigen Open-Source-Projektes, welches mittlerweile größtenteils zu Moby wurde. Details bezüglich der Aufteilung des Open-Source-Projektes und Docker als Plattform können hier nachgelesen werden.

Mit Hilfe der Docker-Plattform und dessen Tools lassen sich Images erstellen, verwalten, registrieren und Container bauen und starten. Doch was ist eigentlich ein Image und was ein Container und welche Begriffe sollten einem sonst noch etwas sagen?

  • Dockerfile/Dockerdatei
    In einer Dockerfile werden die Befehle hinterlegt, welche bei der Erstellung eines Images der Reihenfolge nach ausgeführt werden. Diese Befehle beschreiben die spätere Umgebung die der Prozess innerhalb des Containers vorfindet, z.B. Dateien und Umgebungsvariablen oder auch andere Laufzeiten (NodeJS, Pyhton) die innerhalb des Containers benötigt werden.
  • Image
    Ein Image wird durch die Ausführung einer Dockerfile erstellt. Es ist die Summe der einzelnen Layern die durch die Ausführung angelegt werden. Ein Image ist ein ausführbares Paket und wird zur Instanziierung von Containern gebraucht.
  • Layer
    Für jeden Befehl innerhalb einer Dockerfile der ausgeführt wird, wird eine unveränderliche Layer erzeugt. Eine Layer kann dabei als Änderung des darunterliegenden Images verstanden werden. Nach jeder Layer erhält man also wieder ein temporäres Image. Diesem wird wieder eine Layer, also eine Änderung hinzugefügt. Das geschieht solange, bis das Image dem entspricht was man haben möchte, bzw. was in der Dockerfile beschrieben wurde.
  • Container
    Ein Container wird erzeugt, wenn ein Image ausgeführt wird. Jedes Image hat einen Eintrittspunkt definiert, der beim Starten eines Containers ausgeführt wird. Der instantiierte Container bleibt solange am Leben, wie der in ihm ausgeführte Prozess läuft.
  • Container Registry
    In einer Container Registry (z.B. der Docker Hub) können Images gespeichert und verwaltet werden.

Wieso Container?

"It works on my machine." ist eine klassiche Aussage in der Software-Entwicklung. Dieser Satz weißt häufig auf ein Problem hin:
Eine Applikation läuft in unterschiedlichen Umgebungen. Das kann häufig dazu führen, dass Probleme auftreten, die in der anderen Umgebung nicht aufgetreten sind (z.B. das Fehlen eine Laufzeitumgebung wie NodeJS).

Mit Containern kann diesem Phänomen Abhilfe geschaffen werden. Wie oben bereits beschrieben ist die Umgebung innerhalb Containern immer identisch, wenn diesen das gleiche Image zugrunde liegt. Dadurch ist es jedem Entwickler möglich die Software unter den gleichen Bedingungen zu entwickeln, zu starten oder zu testen wie in der späteren Live-Umgebung.

Mit Hilfe dieser Docker-Container wird auch das Bereitstellen der Software innerhalb einer Cloud vereinfacht. Die meisten Cloud-Anbieter (z.B. Amazon oder Microsoft) sind in der Lage Container zu hosten und bieten dafür Produkte an. Da Container ihre eigene interne Laufzeitumgebung definieren, erleichtert das das Hosten innerhalb einer Cloud.

Nicht nur das Bereitstellen an sich wird vereinfach und vereinheitlicht, auch die Bereitstellungszeit wird im Gegensatz zu einer VM deutlich verkürzt. Während Virtualisierung mit Hilfe einer VM ein Host-Betriebssystem, einen Hypervisor und ein vollständiges Gast-Betriebssystem benötigt, nutzen Container den Kernel des Host-Betriebssystems gemeinsam und benötigen kein eigenes vollständiges Gast-Betriebssystem. Dadurch wird die Größe von Containern im Gegensatz zu einer VM reduziert und auch die Bereitstellungszeit deutlich verkürzt.

VMvsContainer

Doch dadruch ergeben sich nicht nur Vorteile:
Beispielsweiße können dadurch Linux-Container nicht direkt auf Windows-Hostsystemen instantiiert werden oder umgekehrt.

Container sind also eine andere, man könnte sagen leichtgewichtigere, Art der Virtualisierung, die keineswegs VMs ersetzt, sondern das Virtualisierungs-Portfolio ergänzt.

Wie baue ich einen Docker-Container?

Um einen Container zu bauen, also ein Image zu instantiieren benötigt man natürlich zuerst einmal ein Image. Wie bereits erklärt wird ein Image mit Hilfe einer Dockerfile beschrieben und kann daraus erstellt werden.
Als Beispiel nehmen wir an, wir benötigen einen Container der als Webserver (nginx) fungiert und uns eine Single Page Application liefert.

Das Dockerfile

Das dazugehörige Dockerfile würde wie folgt aussehen:

FROM ngnix:alpine

COPY ./dist /usr/share/nginx/html

Ja richtig! Diese beiden Zeilen sind alles, was für diesen Anwendungsfall benötigt wird. Doch schauen wir uns die beiden Befehle in der Dockerfile etwas genauer an.


FROM ngnix:alpine

FROM ist der wichtigste Befehl. Dieser gibt an auf welchem Image das neue Image aufbaut. In unserem Fall nutzen wir das ngnix Image welches mit alpine getaggt wurde. Auf dieses Basis-Image gehen wir gleich nochmal genauer ein.


COPY ./dist /usr/share/nginx/html

COPY ist relativ selbsterklärend. Dieser Dockerbefehl kopiert einen Ordner/eine Datei vom Hostsystem auf dem das Image gebaut wird in das Image selber. ./distist hier der relative Pfad zum Ordner in dem die Single Page Application auf dem Hostsystem liegt (index.html, css-Files, js-Files). /user/share/nginx/htmlist der Zielpfad innerhalb des Images. Der Pfad ist in der Readme des nginx-Images im Docker Hub beschrieben.


Das ngnix:alpine Image wird auch wieder durch eine Dockerfile beschrieben. Wenn man sich diese File ansieht, entdeckt man noch weitere Docker-Commands:

FROM alpine:3.7

LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"

ENV NGINX_VERSION 1.15.0

RUN GPG_KEYS=B0F425...

COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.vh.default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

STOPSIGNAL SIGTERM

CMD ["nginx", "-g", "daemon off;"]

LABEL maintainer="NGINX Docker Main...

Der LABEL Ausdruck fügt dem Image Metadaten hinzu. Metadaten sind Key-Value Pairs und können beliebig zu einem Image hinzugefügt werden.


ENV NGINX_VERSION 1.15.0

ENV setzt dabei den Wert einer Umgebungsvariablen innerhalb des Images. In diesem Fall wird die Variable mit dem Key NGINX_VERSION auf den Wert 1.15.0gesetzt.


RUN GPG_KEYS=B0F425...

RUN führt einen Befehl auf der Shell (/bin/sh -c für Linux und cmd /S /Cfür Windows) aus. Das Ergebnis der Ausführung fließt in das Image in Form eines neuen Layers ein.


EXPOSE 80

EXPOSE gibt an, dass der Container zur Laufzeit auf den angegeben TCP Port lauscht. UDP-Ports werden mit der Schreibweise /udp angegeben.


CMD ["nginx", "-g", "daemon off;"]

CMD gibt den standardmäßigen Eintrittspunkt für die Container-Ausführung an. Hier wird beim Starten des Containers der Befehl nginxmit den Parametern -gund deamon off; ausgeführt. Sollten mehrere CMD-Befehle in einer Dockerfile beschrieben sein, so wird immer nur letzterer berücksichtigt.


Diese Ausdrücke sind aber nur einige aller möglichen Befehle die zum Beschreiben eines Images genutzt werden können. Eine Übersicht aller Möglichkeiten kann hier gefunden werden.

Die Docker CLI

Nachdem man nun ein Dockerfile erstellt hat, was das Image für seinen Einsatzzweck beschreibt, wird es an der Zeit dieses auch zu erstellen. Docker bietet hierfür die Command-Line Interfaces. Über die Docker CLI können Images nicht nur gebaut, sondern auch verwaltet werden. Die CLI bietet auch Unterstützung um Container zu instantiieren, zu überwachen und zu stoppen.

Um von einer Dockerfile ein Image zu erzeugen wird der build Befehl benötigt.

docker build -t testapp:1.0.0 ., ausgeführt im Verzeichnis der Dockerfile, wird nach und nach die Commands innerhalb der Dockerfile ausführen und so das Image Layer für Layer erzeugen (. ist der relative Pfad zu dem aktuellen Verzeichnis). Mit der Option -twird das erstellte Image in das Repository testapp gelegt und mit 1.0.0 getaggt.

Mit docker run -p 8080:80 --name firstTestappContainer -d testapp wird nun einen Container aus dem Image testapp gestartet. Die Option -p 8080:80 (oder --publish 8080:80) gibt an, das der container-interne Port 80 (TCP), auf welchen der Webserver innerhalb des Containers lauscht, auf den Port 8080 des Hostsystems gemappt wird. Dadurch können wir den lokal laufenden Container über die URL http://localhost:8080/ erreichen und unsere SPA aufrufen. Mit Hilfe von --name firstTestappContainer geben wird dem Container einen menschen-lesbaren Namen um nicht mit der ID des Containers arbeiten zu müssen. Die Flag -d (oder --detach) gibt an, das der Container im Hintergrund ausgeführt werden soll.

Mit docker ps können wir nun unseren laufenden Container auflisten lassen. Hier sollte nun ein Container mit dem vergebenen Namen gelistet sein.

Doch wie sieht es nun mit Log-Daten aus, die normal auf der Konsole zu lesen wären?

docker attach firstTestappContainer bewirkt, dass der Standard-Eingabe, -Ausgabe und Fehler-Stream an den Container angehängt wird.

Die Docker-CLI kann mit Hilfe von docker push und docker pull Images in eine Registry laden oder sich von einer Registry Images ziehen.

Fazit

Container bieten eine leichtgewichtige Isolierung durch Virtualisierung auf Betriebssystemebene und Docker liefert dafür eine Plattform und die notwendigen Tools.

Durch Container wird sichergestellt, dass Anwendungen innerhalb des Containers stets ihre benötigte Laufzeitumgebung vorfinden und das, bis auf das Betriebssystem, unabhängig vom hostenden System. Das trägt zur Stabilität und vor allem auch zur Protabilität von Applikationen bei.

Auch während der Entwicklung können Container helfen die Entwicklungsumgebungen zu vereinheitlichen, oder mehrere Build- oder Release-Agents einfach zu verwalten. Dadurch das Testumgebungen repetitiv erstellt werden können erleichtert es auch das Testen von Software-Produkten.

Man sollte jedoch nicht versuchen alles zwingend in Container zu verpacken, da, wie bereits erwähnt, sie eine Ergänzung im Virtualisierungs-Portfolio sind, aber kein Lösung für alle Probleme. Je nach Anwendungsfall sollte abgewogen werden, ob ein eigener Server, eine eigene VM oder ein Container den Anforderungen am besten gerecht werden.

Weitere Links

1 Antwort

Trackbacks & Pingbacks

  1. […] development is hard. Sure, there are things that can make your live easier (e.g. Container or ubiquitous language), but sadly there is "No Silver Bullet" as Frederick P. Brooks Jr. […]

Dein Kommentar

An Diskussion beteiligen?
Hinterlasse uns Deinen Kommentar!

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert