Die Vir­tua­li­sie­rungs-Lösung Docker hat im Verlauf des letzten Jahr­zehnts grund­le­gend verändert, wie Software gebaut, verteilt und betrieben wird. Anders als bei den zuvor eta­blier­ten vir­tu­el­len Maschinen (VM) werden mit Docker einzelne An­wen­dun­gen vir­tua­li­siert. Bei einem Docker-Container handelt es sich also um einen An­wen­dungs- oder Software-Container.

Der Begriff des Software-Con­tai­ners ist angelehnt an physische Container, wie sie auf Schiffen ein­ge­setzt werden. In der Logistik haben Container als stan­dar­di­sier­te Einheit die modernen Han­dels­ket­ten erst er­mög­licht. So lässt sich ein Container auf jedem dafür aus­ge­leg­ten Schiff, Lkw oder Zug befördern. Dies funk­tio­niert weit­ge­hend un­ab­hän­gig vom Inhalt des Con­tai­ners. Nach außen hin ist der Container mit stan­dar­di­sier­ten Schnitt­stel­len versehen. Ganz ähnlich ist dies auch bei Docker-Con­tai­nern der Fall.

KI-Assistent kostenlos – Ihr smarter All­tags­hel­fer
  • DSGVO-konform & sicher gehostet in Deutsch­land
  • Pro­duk­ti­vi­tät steigern – weniger Aufwand, mehr Output
  • Direkt im Browser starten – ohne In­stal­la­ti­on

Was ist ein Docker-Container?

Worum genau handelt es sich nun bei einem Docker-Container? Lassen wir dafür die Docker-Ent­wick­ler zu Wort kommen:

Zitat

„Container sind eine stan­dar­di­sier­te Einheit von Software, die es Ent­wick­lern erlaubt, ihre App von ihrer Umgebung zu isolieren.“ (Über­set­zung: IONOS)

Ori­gi­nal­zi­tat: „Con­tai­ners are a stan­dar­di­zed unit of software that allows de­ve­lo­pers to isolate their app from its en­vi­ron­ment.“ – Quelle: https://www.docker.com/why-docker

Anders als ein phy­si­scher Container existiert ein Docker-Container im vir­tu­el­len Umfeld. Ein phy­si­scher Container wird einer stan­dar­di­sier­ten Spe­zi­fi­ka­ti­on folgend zu­sam­men­ge­baut. Bei vir­tu­el­len Con­tai­nern finden wir ein ähnliches Muster vor: ein Docker-Container wird aus einer un­ver­än­der­li­chen Vorlage, dem so­ge­nann­ten Image, erzeugt. Ein Docker-Image enthält die zur Erzeugung eines Con­tai­ners not­wen­di­gen Ab­hän­gig­kei­ten und Kon­fi­gu­ra­ti­ons­ein­stel­lun­gen.

So wie viele physische Container auf eine einzige Spe­zi­fi­ka­ti­on zu­rück­zu­füh­ren sind, lassen sich beliebig viele Docker-Container aus einem Image erzeugen. Damit bilden Docker-Container die Basis für ska­lier­ba­re Dienste und re­pro­du­zier­ba­re An­wen­dungs­um­ge­bun­gen. Wir können einen Container aus einem Image erzeugen sowie einen exis­tie­ren­den Container in einem neuen Image speichern. Innerhalb des Con­tai­ners lassen sich Prozesse ausführen, pausieren und beenden.

Anders als bei der Vir­tua­li­sie­rung mit vir­tu­el­len Maschinen (VM) enthält ein Docker-Container kein eigenes Be­triebs­sys­tem („Operating System“, OS). Statt­des­sen greifen alle auf einem Docker-Host laufenden Container auf denselben OS-Kernel zu. Beim Einsatz von Docker auf einem Linux-Host wird der vor­han­de­ne Linux-Kernel genutzt. Läuft die Docker-Software auf einem Nicht-Linux-System, kommt ein minimales Linux-Sys­tem­ab­bild via Hy­per­vi­sor oder vir­tu­el­ler Maschine zum Einsatz.

Jedem Container wird bei der Aus­füh­rung eine gewisse Menge an Sys­tem­res­sour­cen zu­ge­wie­sen. Dazu gehören Ar­beits­spei­cher, CPU-Kerne, Mas­sen­spei­cher und (virtuelle) Netz­werk­ge­rä­te. Technisch wird der Zugriff eines Docker-Container auf Sys­tem­res­sour­cen über die so­ge­nann­ten cgroups („Control Groups“) limitiert. Zum Par­ti­tio­nie­ren der Kernel-Res­sour­cen und zur Ab­gren­zung der Prozesse un­ter­ein­an­der kommen so­ge­nann­te Kernel-Name­spaces zum Einsatz.

Nach außen hin kom­mu­ni­zie­ren Docker-Container über das Netzwerk. Dafür werden Ports frei­ge­schal­tet, auf denen spe­zi­fi­sche Dienste lauschen. Oft handelt es sich dabei um Web- oder Da­ten­bank­ser­ver. Die Container selbst werden auf dem je­wei­li­gen Docker-Host über die Docker-API gesteuert. Dabei lassen sich Container u. a. starten, stoppen und entfernen. Der Docker-Client stellt ein Kom­man­do­zei­len-Interface („Command Line Interface“, CLI) mit den ent­spre­chen­den Befehlen bereit.

Wie un­ter­schei­den sich Docker-Container und Docker-Image?

Die Dualität der Begriffe Docker-Container und Docker-Image sorgt oft für Ver­wir­rung. Dies ist wenig über­ra­schend, handelt es sich doch um ein Henne-Ei-Problem: Ein Container wird aus einem Image erzeugt; jedoch lässt sich ein Container auch als neues Image speichern. Schauen wir uns die Un­ter­schie­de zwischen den beiden Konzepten im Detail an.

Ein Docker-Image ist eine inerte Vorlage. Das Image belegt lediglich etwas Platz auf der Fest­plat­te und macht ansonsten nichts. Hingegen handelt es sich beim Docker-Container um eine „lebendige“ Instanz. Ein laufender Docker-Container hat ein Verhalten – der Container in­ter­agiert mit der Umgebung. Ferner hat ein Container einen Zustand, der sich über die Zeit ändert und dabei eine variable Menge an Ar­beits­spei­cher belegt.

Viel­leicht sind Ihnen die Konzepte „Klasse“ und „Objekt“ aus der ob­jekt­ori­en­tier­ten Pro­gram­mie­rung (OOP) bekannt. Die Beziehung zwischen Docker-Container und Docker-Image ist in etwa ver­gleich­bar mit der Beziehung zwischen Objekt und da­zu­ge­hö­ri­ger Klasse: Eine Klasse liegt nur einmal vor; daraus lassen sich mehrere gleich­ar­ti­ge Objekte erzeugen. Die Klasse selbst wird aus einer Quelltext-Datei geladen. Im Docker-Universum findet sich ein ähnliches Muster: Aus einer Quelltext-Einheit, dem „Do­cker­file“, wird eine Vorlage erzeugt, aus der wiederum viele Instanzen entstehen:

  Quelltext Vorlage Instanz
Docker-Konzept Do­cker­file Docker-Image Docker-Container
Pro­gram­mier-Analogie Klassen-Quelltext geladene Klasse in­stan­zi­ier­tes Objekt
Tipp

Wir be­zeich­nen den Docker-Container als „laufende Instanz“ des da­zu­ge­hö­ri­gen Images. Dabei sind die Begriffe „Instanz“ und „In­stan­zi­ie­ren“ abstrakt. Falls Sie damit nicht viel anfangen können, nutzen Sie eine Esels­brü­cke. Ersetzen Sie mental „in­stan­zi­ie­ren“ durch „stanzen“. Auch wenn keine Ver­wandt­schaft zwischen den Worten besteht, gibt es auf die In­for­ma­tik bezogen eine hohe Über­ein­stim­mung der Be­deu­tun­gen. Stellen Sie sich das Prinzip so vor: Wie wir mit einer Keksform viele gleich­ar­ti­ge Kekse aus einer Lage Teig stanzen, in­stan­zi­ie­ren wir aus einer Vorlage viele gleich­ar­ti­ge Objekte. In­stan­zi­ie­ren ist also der Zeitpunkt, an dem ein Objekt durch eine Vorlage erzeugt wird.

Wie ist ein Docker-Container aufgebaut?

Um zu verstehen, wie ein Docker-Container aufgebaut ist, hilft ein Blick auf die „Zwölf-Faktor-App“-Me­tho­do­lo­gie. Dabei handelt es sich um eine Sammlung von zwölf grund­le­gen­den Prin­zi­pi­en für Aufbau und Betrieb ser­vice­ori­en­tier­ter Software. Sowohl Docker als auch die Zwölf-Faktor-App stammen aus dem Jahr 2011. Die Zwölf-Faktoren-App un­ter­stützt Ent­wick­ler dabei, Software-as-a-Service-Apps nach be­stimm­ten Standards zu gestalten. Dazu zählen u. a.:

  • De­kla­ra­ti­ve Formate nutzen, die die Kon­fi­gu­ra­ti­on au­to­ma­ti­sie­ren und neu be­tei­lig­ten Ent­wick­lern Zeit und Kosten ersparen
  • Zu­grun­de­lie­gen­des Be­triebs­sys­tem beachten und maximale Por­tier­bar­keit zwischen Aus­füh­rungs­ebe­nen ge­währ­leis­ten
  • De­ploy­ment auf Cloud-Platt­for­men be­vor­zu­gen (Server und Ser­ver­ad­mi­nis­tra­ti­on vermeiden)
  • Ein­heit­li­che Ent­wick­lung und Pro­duk­ti­on, um agiles Con­ti­nuous De­ploy­ment zu er­mög­li­chen
  • Ska­lier­bar­keit, ohne dass Tooling, Ar­chi­tek­tur oder Ent­wick­lungs­ver­fah­ren geändert werden müssen

Der Aufbau eines Docker-Con­tai­ners ori­en­tiert sich an diesen Grund­sät­zen. Ein Docker-Container umfasst die folgenden Kom­po­nen­ten, die wir uns nach­fol­gend im Detail anschauen:

  1. Container-Be­triebs­sys­tem und Union-Da­tei­sys­tem
  2. Software-Kom­po­nen­ten und -Kon­fi­gu­ra­ti­on
  3. Um­ge­bungs­va­ria­blen und Laufzeit-Kon­fi­gu­ra­ti­on
  4. Ports und Volumen
  5. Prozesse und Logs

Container-Be­triebs­sys­tem und Union-Da­tei­sys­tem

Im Un­ter­schied zu den vir­tu­el­len Maschinen enthält ein Docker-Container kein eigenes Be­triebs­sys­tem. Statt­des­sen greifen alle auf einem Docker-Host laufenden Container auf einen geteilten Linux-Kernel zu. Im Container ist lediglich eine minimale Aus­füh­rungs­schicht enthalten. Dazu gehören für ge­wöhn­lich eine Im­ple­men­tie­rung der C-Standard-Bi­blio­thek sowie eine Linux-Shell zum Ausführen von Prozessen. Hier eine Übersicht der Kom­po­nen­ten des of­fi­zi­el­len „Alpine-Linux“-Image:

Linux Kernel C-Standard-Bi­blio­thek Unix-Kommandos
vom Host musl libc BusyBox

Ein Docker-Image besteht aus einem Stapel schreib­ge­schütz­ter Da­tei­sys­tem-Schichten, zu Englisch „Layers“. Ein Layer be­schreibt die Än­de­run­gen am Da­tei­sys­tem zum darunter be­find­li­chen Layer. Über ein spe­zi­el­les Union-Da­tei­sys­tem wie overlay2 werden die Layers über­la­gert und zu einer kon­sis­ten­ten Ober­flä­che vereint. Beim Erzeugen eines Docker-Con­tai­ners aus einem Image wird den schreib­ge­schütz­ten Layers ein weiterer, be­schreib­ba­rer Layer hin­zu­ge­fügt. Alle Än­de­run­gen am Da­tei­sys­tem werden per „Copy-on-Write“-Verfahren in den be­schreib­ba­ren Layer auf­ge­nom­men.

Software-Kom­po­nen­ten und -Kon­fi­gu­ra­ti­on

Aufbauend auf dem minimalen Container-Be­triebs­sys­tem werden in einem Docker-Container weitere Software-Kom­po­nen­ten in­stal­liert. Für ge­wöhn­lich folgen dann weitere Ein­rich­tungs- und Kon­fi­gu­ra­ti­ons­schrit­te. Zur In­stal­la­ti­on kommen die ge­bräuch­li­chen Wege zum Einsatz:

  • per System-Pa­ket­ma­na­ger wie apt, apk, yum, brew etc.
  • per Pro­gram­mier­spra­chen-Pa­ket­ma­na­ger wie pip, npm, composer, gem, cargo etc.
  • via Kom­pi­la­ti­on im Container mit make, mvn etc.

Hier einige Beispiele für häufig in Docker-Con­tai­nern ein­ge­setz­te Software-Kom­po­nen­ten:

Ein­satz­ge­biet Software-Kom­po­nen­ten
Pro­gram­mier­spra­chen PHP, Python, Ruby, Java, Ja­va­Script
Ent­wick­lungs-Tools node / npm, React, Laravel
Datenbank-Systeme MySQL, Postgres, MongoDB, Redis
Webserver Apache, nginx, lighttpd
Caches und Proxies Varnish, Squid
Content-Ma­nage­ment-Systeme WordPress, Magento, Ruby on Rails

Um­ge­bungs­va­ria­blen und Laufzeit-Kon­fi­gu­ra­ti­on

Der Zwölf-Faktoren-App-Me­tho­do­lo­gie folgend wird die Kon­fi­gu­ra­ti­on eines Docker-Con­tai­ners in Um­ge­bungs­va­ria­blen, so­ge­nann­ten Env-Vars („En­vi­ron­ment Variables“), ge­spei­chert. Dabei verstehen wir unter Kon­fi­gu­ra­ti­on alle Werte, die sich zwischen den ver­schie­de­nen Um­ge­bun­gen, etwa Ent­wick­lungs- vs. Pro­duk­ti­ons­sys­tem, ändern. Oft gehören dazu Hostnamen und Datenbank-Cre­den­ti­als.

Die Werte der Um­ge­bungs­va­ria­blen be­ein­flus­sen das Verhalten des Con­tai­ners. Um Um­ge­bungs­va­ria­blen innerhalb eines Con­tai­ners verfügbar zu machen, kommen zwei primäre Wege zum Einsatz:

  1. De­fi­ni­ti­on in Do­cker­file

Im Do­cker­file wird mit der ENV-Anweisung eine Um­ge­bungs­va­ria­ble de­kla­riert. Dabei lässt sich ein op­tio­na­ler Default-Wert vergeben. Dieser kommt zum Tragen, falls die Um­ge­bungs­va­ria­ble beim Starten des Con­tai­ners leer ist.

  1. Übergeben beim Starten des Con­tai­ners

Um auf eine Um­ge­bungs­va­ria­ble im Container zu­zu­grei­fen, die nicht im Do­cker­file de­kla­riert wurde, übergeben wir die Variable beim Starten des Con­tai­ners. Dies funk­tio­niert für einzelne Variablen per Kom­man­do­zei­len-Parameter. Ferner lässt sich eine so­ge­nann­te Env-Datei übergeben, die mehrere Um­ge­bungs­va­ria­blen samt deren Werten definiert.

Hier das Muster, um eine Um­ge­bungs­va­ria­ble beim Starten des Con­tai­ners zu übergeben:

docker run --env <env-var> <image-id>

Bei vielen Um­ge­bungs­va­ria­blen bietet es sich an, eine Env-Datei zu übergeben:

docker run --env-file /path/to/.env <image-id>
Hinweis

Mit Hilfe des 'docker inspect'-Befehls lassen sich die im Container vor­han­de­nen Um­ge­bungs­va­ria­blen samt deren Werten anzeigen. Daher gilt Vorsicht bei der Nutzung geheimer Daten in Um­ge­bungs­va­ria­blen.

Beim Starten eines Con­tai­ners aus einem Image lassen sich Kon­fi­gu­ra­ti­ons-Parameter übergeben. Dazu gehören u. a. die Menge zu­ge­wie­se­ner Sys­tem­res­sour­cen, die ansonsten un­li­mi­tiert sind. Ferner kommen Start-Parameter zum Einsatz, um Ports und Volumen für den Container zu de­fi­nie­ren – mehr dazu im nächsten Abschnitt. Die Start-Parameter können evtl. im Do­cker­file vor­ein­ge­stell­te Werte über­schrei­ben. Nach­fol­gend einige Beispiele.

Docker-Container beim Start einen CPU-Kern und 10 Megabyte Speicher zuweisen:

docker run --cpus="1" --memory="10m" <image-id>

In Do­cker­file de­fi­nier­te Ports beim Starten des Con­tai­ners frei­schal­ten:

docker run -P <image-id>

TCP-Port 80 des Docker-Hosts auf Port 80 des Docker-Con­tai­ners mappen:

docker run -p 80:80/tcp <image-id>

Ports und Volumen

Ein Docker-Container enthält eine von der Außenwelt isolierte Anwendung. Damit dies nützlich ist, muss die In­ter­ak­ti­on mit der Umgebung möglich sein. Daher gibt es Wege, Daten zwischen Host und Container sowie zwischen mehreren Con­tai­nern aus­zu­tau­schen. Stan­dar­di­sier­te Schnitt­stel­len erlauben dabei den Einsatz eines Con­tai­ners in un­ter­schied­li­chen Um­ge­bun­gen.

Die Kom­mu­ni­ka­ti­on mit im Container laufenden Prozessen von der Au­ßen­sei­te läuft über frei­ge­schal­te­te Netzwerk-Ports. Hierbei kommen die Stan­dard­pro­to­kol­le TCP und UDP zum Einsatz. Stellen wir uns als Beispiel einen Docker-Container vor, der einen Webserver enthält; dieser lauscht auf TCP-Port 8080. Ferner enthält das Do­cker­file des Docker-Images die Zeile 'EXPOSE 8080/tcp'. Wir starten den Container mit 'docker run -P' und greifen unter der Adresse 'http://localhost:8080' auf den Webserver zu.

Ports dienen zur Kom­mu­ni­ka­ti­on mit im Container laufenden Diensten. In vielen Fällen ist es jedoch sinnvoll, eine zwischen dem Container und dem Host-System geteilte Datei zum Da­ten­aus­tausch zu nutzen. Zu diesem Zweck kennt Docker ver­schie­de­ne Typen von Volumen:

  • Benannte Volumen – empfohlen
  • Anonyme Volumen – gehen beim Entfernen des Con­tai­ners verloren
  • Bind Mounts – his­to­risch bedingt und nicht empfohlen; per­for­mant
  • Tmpfs Mounts – liegen im Ar­beits­spei­cher; nur unter Linux

Die Un­ter­schie­de zwischen den Vo­lu­men­ty­pen sind subtil; die Wahl des passenden Typs hängt stark vom je­wei­li­gen Ein­satz­sze­na­rio ab. Eine de­tail­lier­te Be­schrei­bung würde den Rahmen dieses Artikels sprengen.

Prozesse und Logs

Ein Docker-Container kapselt für ge­wöhn­lich eine Anwendung oder einen Dienst. Die innerhalb des Con­tai­ners aus­ge­führ­te Software bildet eine Menge laufender Prozesse. Die Prozesse eines Docker-Con­tai­ners sind isoliert von Prozessen anderer Container oder des Host-Systems. Innerhalb eines Docker-Con­tai­ners lassen sich Prozesse starten, stoppen und auflisten. Die Steuerung erfolgt über die Kom­man­do­zei­le, bzw. über die Docker-API.

Laufende Prozesse geben kon­ti­nu­ier­lich Sta­tus­in­for­ma­tio­nen aus. Der Zwölf-Faktoren-App-Me­tho­do­lo­gie folgend werden zur Ausgabe die Standard-Da­ten­strö­me STDOUT und STDERR verwendet. Die Ausgabe auf diese beiden Da­ten­strö­me lässt sich mit dem 'docker logs'-Kommando auslesen. Al­ter­na­tiv kann ein so­ge­nann­ter Logging-Treiber verwendet werden. Der Standard-Logging-Treiber schreibt Logs im JSON-Format.

Wie und wo werden Docker-Container verwendet?

Docker kommt heut­zu­ta­ge in allen Bereichen des Software-Life­cy­cles zum Einsatz. Dazu gehören Ent­wick­lung, Test und Betrieb. Die auf einem Docker-Host laufenden Container werden über die Docker-API gesteuert. Der Docker-Client nimmt Befehle auf der Kom­man­do­zei­le entgegen; zur Steuerung von Verbünden aus Docker-Con­tai­nern kommen spezielle Or­ches­trie­rungs-Tools zum Einsatz.

Das grund­le­gen­de Muster beim Einsatz von Docker-Con­tai­nern sieht fol­gen­der­ma­ßen aus:

  1. Docker-Host bezieht Docker-Image von Registry.
  2. Docker-Container wird aus Image erzeugt und gestartet.
  3. Im Container ent­hal­te­ne Anwendung läuft, bis der Container gestoppt oder entfernt wird.

Schauen wir uns zwei Beispiele für den Einsatz von Docker-Con­tai­nern an:

Einsatz von Docker-Con­tai­nern in der lokalen Ent­wick­lungs­um­ge­bung

Besonders beliebt ist der Einsatz von Docker-Con­tai­nern in der Software-Ent­wick­lung. Für ge­wöhn­lich wird eine Software von einem Team von Spe­zia­lis­ten ent­wi­ckelt. Dabei kommt eine als Toolchain (zu Deutsch: „Werk­zeug­ket­te“) be­zeich­ne­te Sammlung von Ent­wick­lungs-Tools zum Einsatz. Jedes Tool liegt in einer spe­zi­fi­schen Version vor, und die ganze Kette funk­tio­niert nur dann, wenn die Versionen un­ter­ein­an­der kom­pa­ti­bel sind. Ferner muss die Kon­fi­gu­ra­ti­on der Tools stimmen.

Um si­cher­zu­stel­len, dass die Kon­sis­tenz der Ent­wick­lungs­um­ge­bung gegeben ist, greift man auf Docker zurück. Es wird einmalig ein Docker-Image erstellt, das die gesamte Toolchain korrekt kon­fi­gu­riert enthält. Jeder Ent­wick­ler des Teams zieht sich das Docker-Image auf die lokale Maschine und startet daraus einen Container. Die Ent­wick­lung erfolgt dann innerhalb des Con­tai­ners. Ergibt sich eine Änderung an der Toolchain, wird das Image zentral ak­tua­li­siert.

Einsatz von Docker-Con­tai­nern in or­ches­trier­ten Verbünden

In Da­ten­cen­tern von Hosting-Providern und PaaS-Anbietern („Platform-as-a-Service“) kommen Verbünde von Docker-Con­tai­nern zum Einsatz. Load-Balancer, Webserver, Da­ten­bank­ser­ver etc.: Jeder Dienst läuft in einem eigenen Docker-Container. Dabei kann ein einzelner Container nur eine gewisse Last stemmen. Docker-Tools über­wa­chen die Container sowie deren Aus­las­tung und Zustand. Bei stei­gen­der Last startet der Or­chestra­tor zu­sätz­li­che Container. Dieser Ansatz erlaubt die schnelle Ska­lie­rung von Diensten als Reaktion auf sich ändernde Be­din­gun­gen.

Vorteile und Nachteile der Docker-Container-Vir­tua­li­sie­rung

Die Vorteile der Vir­tua­li­sie­rung mit Docker sind ins­be­son­de­re mit Hinsicht auf den Einsatz vir­tu­el­ler Maschinen (VM) zu be­trach­ten. Im Vergleich zu VMs sind Docker-Container deutlich leicht­ge­wich­ti­ger. Sie lassen sich schneller starten und ver­brau­chen weniger Res­sour­cen. Auch die den Docker-Con­tai­nern zu­grun­de­lie­gen­den Images sind um Grö­ßen­ord­nun­gen kleiner: Während VM-Images für ge­wöhn­lich hunderte MB bis einige GB groß sind, beginnen Docker-Images bereits bei wenigen MB.

Jedoch hat die Container-Vir­tua­li­sie­rung mit Docker auch einige Nachteile. Da ein Container kein eigenes Be­triebs­sys­tem enthält, ist die Iso­lie­rung der darin laufenden Prozesse nicht ganz perfekt. Beim Einsatz großer Mengen von Con­tai­nern ergibt sich ein hoher Grad an Kom­ple­xi­tät. Ferner handelt es sich bei Docker um ein ge­wach­se­nes System. Mitt­ler­wei­le macht die Docker-Plattform zu viel. Daher werden verstärkt An­stren­gun­gen un­ter­nom­men, die einzelnen Kom­po­nen­ten auf­zu­spal­ten.

Zum Hauptmenü