Bei Docker handelt es sich um eine Tech­no­lo­gie für die con­tai­ner­ba­sier­te Vir­tua­li­sie­rung von Software-An­wen­dun­gen. Der von Docker im Main­stream ver­an­ker­te con­tai­ner­ba­sier­te Ansatz hat in den letzten Jahren die An­wen­dungs­ent­wick­lung um­ge­krem­pelt. Dabei wurden alle Bereiche der Ent­wick­lung berührt: Wie man An­wen­dun­gen und Kom­po­nen­ten baut, Software-Dienste verteilt und von der Ent­wick­lung in die Pro­duk­ti­on geht. All diese Prozesse laufen mit Docker anders ab, als es vormals üblich war.

Aber nicht nur die Ent­wick­lungs­pro­zes­se haben sich verändert, sondern auch die Software-Ar­chi­tek­tur: weg von mo­no­li­thi­schen Ge­samt­lö­sun­gen und hin zu Verbünden leicht­ge­wich­ti­ger „Mi­cro­ser­vices“. Damit ein­her­ge­hend erhöhte sich auch die Kom­ple­xi­tät der re­sul­tie­ren­den Ge­samt­sys­te­me. In den letzten Jahren hat sich u. a. die Software Ku­ber­netes etabliert, um Muli-Container-An­wen­dun­gen zu managen.

Die Ent­wick­lung der con­tai­ner­ba­sier­ten Vir­tua­li­sie­rung ist bei weitem nicht ab­ge­schlos­sen; es bleibt also spannend. In diesem Artikel erklären wir, wie Docker als grund­le­gen­de Tech­no­lo­gie funk­tio­niert. Ferner schauen wir uns an, welche Be­weg­grün­de zur Ent­wick­lung von Docker bei­getra­gen haben.

Hinweis

Beachten Sie: Der Name „Docker“ hat mehrere Be­deu­tun­gen. Er wird synonym verwendet für die Software an sich, für das ihr zu­grun­de­lie­gen­de Open-Source-Projekt sowie für eine US-ame­ri­ka­ni­sche Firma, die ver­schie­de­ne Produkte und Dienst­leis­tun­gen kom­mer­zi­ell betreibt.

Neben dem Docker Team sind führende Tech­no­lo­gie­un­ter­neh­men wie Cisco, Google, Huawei, IBM, Microsoft und Red Hat an der Ent­wick­lung von Docker und damit ein­her­ge­hen­der Tech­no­lo­gien beteiligt. Als neuere Ent­wick­lung kommt neben dem Linux-Kernel mitt­ler­wei­le auch Windows als native Umgebung für Docker-Container zum Einsatz. Hier einige der wich­tigs­ten Mei­len­stei­ne in der Docker-Ent­ste­hungs­ge­schich­te:

Jahr Mei­len­stei­ne der Docker-Ent­wick­lung
2007 cgroups-Tech­no­lo­gie im Linux-Kernel in­te­griert
2008 LXC ver­öf­fent­licht; baut wie später Docker auf cgroups und Linux-Name­spaces auf
2013 Docker als Open Source ver­öf­fent­licht
2014 Docker bei Amazon EC2 verfügbar
2015 Ku­ber­netes ver­öf­fent­licht
2016 Docker auf Windows 10 Pro via Hyper-V verfügbar
2019 Docker auf Windows Home via WSL2 verfügbar

Was ist Docker?

Die Kern­funk­tio­na­li­tät von Docker besteht in der Container-Vir­tua­li­sie­rung von An­wen­dun­gen. Diese steht im Gegensatz zur Vir­tua­li­sie­rung mit vir­tu­el­len Maschinen (VM). Mit Docker wird der An­wen­dungs-Code inklusive aller Ab­hän­gig­kei­ten in ein so­ge­nann­tes Image gepackt. Die Docker-Software führt die so verpackte Anwendung in einem Docker-Container aus. Images lassen sich zwischen Systemen bewegen und auf jedem System ausführen, auf dem Docker läuft.

Zitat

„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 […]“ – Aussage der Docker-Ent­wick­ler, Quelle: https://www.docker.com/why-docker

„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)

Wie beim Einsatz einer vir­tu­el­len Maschine (VM) liegt ein Haupt­au­gen­merk bei Docker-Con­tai­nern auf der Iso­lie­rung der laufenden Anwendung. Anders als bei VMs wird jedoch kein kom­plet­tes Be­triebs­sys­tem vir­tua­li­siert. Statt­des­sen weist Docker jedem Container gewisse Be­triebs­sys­tem- und Hardware-Res­sour­cen zu. Aus einem Docker-Image lassen sich beliebig viele Container erzeugen und parallel betreiben. So werden ska­lier­ba­re Cloud-Dienste rea­li­siert.

Auch wenn wir von Docker als einer Software sprechen, handelt es sich tat­säch­lich um mehrere Software-Kom­po­nen­ten, die über die Docker-Engine-API kom­mu­ni­zie­ren. Ferner kommt eine Handvoll spe­zi­el­ler Docker-Objekte zum Einsatz; z. B. gehören dazu die bereits erwähnten Images und Container. Die Docker-spe­zi­fi­schen Workflows setzten sich aus den Software-Kom­po­nen­ten und Docker-Objekten zusammen. Schauen wir uns dieses Zu­sam­men­spiel im Detail an.

Docker-Software

Grundlage für die Docker-Software bildet die so­ge­nann­te Docker-Engine. Diese dient haupt­säch­lich zur Ver­wal­tung und Steuerung der Container und der ihnen zu­grun­de­lie­gen­den Images. Für darüber hin­aus­ge­hen­de Funk­tio­na­li­tä­ten kommen spezielle Docker-Tools zum Einsatz. Diese werden vor allem für die Ver­wal­tung von An­wen­dun­gen benötigt, die aus Gruppen von Con­tai­nern bestehen.

Docker-Engine

Die Docker-Engine läuft auf einem lokalen System oder einem Server und besteht aus zwei Kom­po­nen­ten:

  1. Dem Docker-Daemon dockerd: Dieser läuft dauerhaft im Hin­ter­grund und lauscht auf Zugriffe über die Docker-Engine-API. Auf ent­spre­chen­de Befehle hin verwaltet dockerd Docker-Container und weitere Docker-Objekte.
  2. Dem Docker-Client docker: Dabei handelt es sich um ein Kom­man­do­zei­len­pro­gramm. Der Docker-Client dient zur Steuerung der Docker-Engine und stellt Befehle für Er­stel­lung und Aufbau von Docker-Con­tai­nern sowie Er­stel­lung, Bezug und Ver­sio­nie­rung von Docker-Images bereit.

Docker-Engine-API

Bei der Docker-Engine-API handelt es sich um eine REST-API. Sie bildet die Schnitt­stel­le zum Docker-Daemon. Zur In­te­gra­ti­on der Docker-Engine-API in Software-Projekte stehen of­fi­zi­el­le „Software-De­ve­lo­p­ment-Kits“ (SKDs) für die Pro­gram­mier­spra­chen Go und Python bereit. Daneben exis­tie­ren ver­gleich­ba­re Bi­blio­the­ken für mehr als ein Dutzend weiterer Pro­gram­mier­spra­chen. Der Zugriff auf die API erfolgt auf der Kom­man­do­zei­le mit dem docker-Befehl. Ferner lässt sich die API direkt mittels cURL oder ver­gleich­ba­ren Tools an­spre­chen.

Docker-Tools

Beim Einsatz vir­tu­el­ler Maschinen kommen oft aus mehreren Software-Kom­po­nen­ten be­stehen­de Systeme zum Einsatz. Im Gegensatz dazu be­güns­tigt die Container-Vir­tua­li­sie­rung mit Docker Verbünde lose ge­kop­pel­ter Mi­cro­ser­vices. Diese eignen sich für verteilte Cloud-Lösungen, die ein hohes Maß an Mo­du­la­ri­tät und Hoch­ver­füg­bar­keit bieten. Jedoch wächst die Kom­ple­xi­tät der­ar­ti­ger Systeme schnell an. Um diese effizient zu verwalten, werden spezielle, als „Or­chestra­tor“ bekannte Software-Tools ein­ge­setzt.

Mit Docker Swarm und Docker Compose stehen zwei of­fi­zi­el­le Docker-Tools zur Or­ches­trie­rung von Container-Verbünden bereit. Mit dem 'docker swarm'-Kommando lassen sich mehrere Docker-Engines zu einer vir­tu­el­len Engine zu­sam­men­fas­sen. Dabei können die einzelnen Engines über mehrere Systeme und In­fra­struk­tu­ren verteilt betrieben werden. Der 'docker compose'-Befehl dient zum Erzeugen von als „Stack“ be­zeich­ne­ten Multi-Container-An­wen­dun­gen.

Kom­for­ta­bler im Einsatz als Swarm und Compose ist der ur­sprüng­lich von Google ent­wi­ckel­te Or­chestra­tor Ku­ber­netes. Dieser hat sich als Standard etabliert und wird von der Industrie breit­flä­chig ein­ge­setzt. Hosting-Firmen und weitere Anbieter von „Software as a Service“- und „Platform as a Service“-Lösungen setzen verstärkt auf Ku­ber­netes als grund­le­gen­de In­fra­struk­tur.

Docker-Objekte

Die Ar­beits­ab­läu­fe im Docker-Ökosystem ergeben sich aus dem Zu­sam­men­spiel der Docker-Objekte. Verwaltet werden diese durch Kom­mu­ni­ka­ti­on mit der Docker-Engine-API. Schauen wir uns die einzelnen Arten von Objekten im Detail an.

Docker-Image

Bei einem Docker-Image handelt es sich um eine schreib­ge­schütz­te Vorlage zum Erzeugen eines oder mehrerer iden­ti­scher Container. Docker-Images sind quasi die Samen des Systems; sie werden ein­ge­setzt, um An­wen­dun­gen zu bündeln und aus­zu­lie­fern.

Für den Bezug von Docker-Images kommen ver­schie­de­ne Re­po­si­to­ries zum Einsatz. Es gibt sowohl öf­fent­li­che als auch nicht­öf­fent­li­che Re­po­si­to­ries. Auf dem beliebten „Docker Hub“ stehen zum Zeitpunkt der Ar­ti­kel­er­stel­lung mehr als fünf Millionen ver­schie­de­ne Images zu Download bereit. Über die Docker-Kommandos 'docker pull' und 'docker push' wird ein Image von einem Re­po­si­to­ry bezogen bzw. dort abgelegt.

Docker-Images sind in Schichten – so­ge­nann­ten Layers – aufgebaut. Jeder Layer re­prä­sen­tiert eine spe­zi­fi­sche Änderung am Image. Damit ergibt sich eine durch­ge­hen­de Ver­sio­nie­rung der Images, was ein Rollback zu einem früheren Zustand er­mög­licht. Zur Erzeugung eines neuen Images kann ein exis­tie­ren­des Image als Grundlage genutzt werden.

Docker-File

Beim Docker-File handelt es sich um eine Textdatei, die den Aufbau eines Docker-Images be­schreibt. Ein Docker-File ist ver­gleich­bar mit einem Sta­pel­ver­ar­bei­tungs-Skript; die Datei enthält Befehle, die ein Image be­schrei­ben. Bei der Aus­füh­rung des Docker-Files werden die Befehle nach­ein­an­der ab­ge­ar­bei­tet. Für jeden Befehl wird ein neuer Layer auf dem Docker-Image angelegt. Sie können sich ein Docker-File also auch als eine Art Rezept vor­stel­len, auf dessen Basis ein Image kreiert wird.

Docker-Container

Kommen wir nun zum zentralen Konzept im Docker-Universum: dem Docker-Container. Während ein Docker-Image eine inerte Vorlage ist, handelt es sich beim Docker-Container um eine aktive, laufende Instanz eines Images. Ein Docker-Image liegt lokal in einer einzigen Kopie vor und belegt lediglich etwas Spei­cher­platz. Dem­ge­gen­über lassen sich aus demselben Image mehrere Docker-Container erzeugen und parallel betreiben.

Jeder Docker-Container enthält für die Aus­füh­rung eine gewisse Menge an Sys­tem­res­sour­cen wie CPU-Nutzung, Ar­beits­spei­cher, Netzwerk-Schnitt­stel­len etc. Ein Docker-Container lässt sich erzeugen, starten, beenden und zerstören. Ferner kann man den Zustand eines laufenden Con­tai­ners als neues Image speichern.

Docker-Volume

Wie wir gesehen haben, wird ein laufender Docker-Container aus einem nicht ver­än­der­ba­ren Image erzeugt. Doch wie verhält es sich mit Daten, die innerhalb des Con­tai­ners zum Einsatz kommen und über dessen Le­bens­dau­er hinaus erhalten bleiben sollen? Für diesen An­wen­dungs­fall kommen Docker-Volumen zum Einsatz. Ein Docker-Volumen existiert außerhalb eines spe­zi­fi­schen Con­tai­ners. So können sich mehrere Container ein Volumen teilen. Die im Volume ent­hal­te­nen Daten werden auf dem Da­tei­sys­tem des Hosts ge­spei­chert. Damit ist ein Docker-Volume ver­gleich­bar mit dem Shared Folder einer vir­tu­el­len Maschine.

Wie funk­tio­niert Docker?

Das grund­le­gen­de Wirk­prin­zip von Docker ist in der Funk­ti­ons­wei­se ähnlich wie bei der vorher ent­wi­ckel­ten Vir­tua­li­sie­rungs-Tech­no­lo­gie LXC: Beide bauen auf dem Linux-Kernel auf und rea­li­sie­ren die con­tai­ner­ba­sier­te Vir­tua­li­sie­rung. Sowohl Docker als auch LXC vereinen zwei für sich genommen ge­gen­läu­fi­ge Ziele:

  1. Laufende Container teilen sich denselben Linux-Kernel und sind damit im Vergleich zu vir­tu­el­len Maschinen leicht­ge­wich­tig.
  2. Laufende Container sind von­ein­an­der isoliert und haben nur Zugriff auf ein li­mi­tier­tes Maß an Sys­tem­res­sour­cen.

Um diese Ziele zu erreichen, setzen sowohl Docker auch als LXC auf die Tech­no­lo­gien „Kernel-Name­spaces“ und „Control Groups“. Schauen wir uns an, wie dies im Detail funk­tio­niert.

Linux-Kernel

Der Linux-Kernel ist die Kern­kom­po­nen­te des Open-Source-Be­triebs­sys­tems GNU/Linux. Der Kernel verwaltet die Hardware und steuert Prozesse. Beim Betrieb von Docker außerhalb von Linux wird ein Hy­per­vi­sor oder eine virtuelle Maschine benötigt, um die Funk­tio­na­li­tät des Linux-Kernels be­reit­zu­stel­len. Unter macOS kommt der vom BSD-Hy­per­vi­sor bhyve ab­ge­lei­te­te xhyve zum Einsatz. Unter Windows 10 setzt Docker auf dem Hyper-V-Hy­per­vi­sor auf.

Kernel-Name­spaces

Name­spaces sind ein Feature des Linux-Kernels. Sie par­ti­tio­nie­ren Kernel-Res­sour­cen und ge­währ­leis­ten damit die Ab­gren­zung von Prozessen un­ter­ein­an­der. Ein Prozess eines Namespace kann nur Kernel-Res­sour­cen desselben Namespace sehen. Hier eine Übersicht der in Docker zum Einsatz kommenden Name­spaces:

Namespace Be­schrei­bung Erklärung
UTS Sys­tem­iden­ti­fi­ka­ti­on Con­tai­nern eigene Host- und Domain-Namen zuweisen
PID Prozess-IDs Jeder Container verwendet einen eigenen Na­mens­raum für Prozess-IDs; PIDs aus anderen Con­tai­nern sind nicht sichtbar; so können zwei Prozesse in ver­schie­de­nen Con­tai­nern dieselbe PID benutzen, ohne dass es zu einem Konflikt kommt.
IPC In­ter­pro­zess-Kom­mu­ni­ka­ti­on IPC-Name­spaces isolieren Prozesse in einem Container, sodass diese nicht mit Prozessen in anderen Con­tai­nern kom­mu­ni­zie­ren können.
NET Netz­werk­res­sour­cen Einem Container separate Netz­werk­res­sour­cen wie IP-Adressen oder Routing-Tabellen zuweisen
MNT Mount­points des Da­tei­sys­tems Be­schränkt das Da­tei­sys­tem des Hosts aus Sicht des Con­tai­ners auf einen eng de­fi­nier­ten Aus­schnitt

Control Groups

Control Groups, meist als cgroups abgekürzt, dienen zur hier­ar­chi­schen Or­ga­ni­sa­ti­on von Linux-Prozessen. Einem Prozess (oder einer Gruppe von Prozessen) wird ein be­grenz­tes Maß an Sys­tem­res­sour­cen zu­ge­wie­sen. Dazu zählen Ar­beits­spei­cher, CPU-Kerne, Mas­sen­spei­cher und (virtuelle) Netzwerk-Geräte. Während Name­spaces Prozesse von­ein­an­der isolieren, li­mi­tie­ren Control Groups den Zugriff auf Sys­tem­res­sour­cen. So wird beim Betrieb mehrerer Container die Funk­ti­ons­fä­hig­keit des Ge­samt­sys­tems si­cher­ge­stellt.

Welche Vorteile hat Docker?

Schauen wir uns die Ge­schich­te der Software-Ent­wick­lung an, um die Vorteile von Docker nach­zu­voll­zie­hen. Wie wird und wurde Software gebaut, aus­ge­lie­fert und aus­ge­führt? Was hat sich dabei grund­le­gend verändert? Software bildet das Ge­gen­stück zur Hardware, dem phy­si­schen Computer. Ohne Software ist der Computer nur ein Klumpen Materie. Während die Hardware fest und un­ver­än­der­bar ist, lässt sich Software neu er­schaf­fen und anpassen. Durch das Zu­sam­men­spiel der beiden Ebenen ergibt sich die digitale Wun­der­welt.

Software auf der phy­si­schen Maschine

Tra­di­tio­nell wurde eine Software mit dem Ziel er­schaf­fen, auf einer phy­si­schen Maschine aus­ge­führt zu werden. Dabei stoßen wir schnell an Grenzen. Eine Software läuft ggf. nur auf einer be­stimm­ten Hardware, benötigt z. B. einen be­stimm­ten Prozessor.

Ferner läuft kom­ple­xe­re Software meist nicht komplett autark, sondern in ein Software-Ökosystem ein­ge­bun­den: Dazu gehören Be­triebs­sys­tem, Bi­blio­the­ken und Ab­hän­gig­kei­ten. Damit das Zu­sam­men­spiel funk­tio­niert, müssen alle Kom­po­nen­ten in den richtigen Versionen vorliegen. Hinzu kommt die Kon­fi­gu­ra­ti­on, die be­schreibt, wie die einzelnen Kom­po­nen­ten un­ter­ein­an­der verknüpft sind.

Möchte man mehrere An­wen­dun­gen auf einer Maschine parallel betreiben, ergeben sich schnell Ver­si­ons­kon­flik­te. Ggf. benötigt eine Anwendung eine Version einer Kom­po­nen­te, die mit einer anderen Anwendung nicht kom­pa­ti­bel ist. Im schlimms­ten Fall müsste jede Anwendung auf einer eigenen phy­si­schen Maschine betrieben werden. Dabei gilt: Physische Maschinen sind teuer und lassen sich nicht einfach skalieren. Wächst also der Res­sour­cen-Bedarf einer Anwendung, muss diese unter Umständen auf eine neue physische Maschine migriert werden.

Ein weiteres Problem ergibt sich aus dem Umstand, dass Software in der Ent­wick­lung in ver­schie­de­nen Um­ge­bun­gen ein­ge­setzt wird. Ein Ent­wick­ler schreibt Code auf dem lokalen System und führt diesen dort zum Testen aus. Die Anwendung durch­läuft vor dem Pro­duk­tiv­ein­satz mehrere Stufen. Dazu gehören z. B. eine Test-Umgebung zur Qua­li­täts­si­che­rung oder eine Staging-Umgebung für Tests durch das Produkt-Team.

Die ver­schie­de­nen Um­ge­bun­gen exis­tie­ren oft auf ver­schie­de­nen phy­si­schen Maschinen. Fast immer gibt es Un­ter­schie­de in den Versionen von Be­triebs­sys­tem, Bi­blio­the­ken und Kon­fi­gu­ra­ti­on. Wie diese alle in Einklang bringen? Denn un­ter­schei­den sich die Um­ge­bun­gen von­ein­an­der, sind Tests wenig aus­sa­ge­kräf­tig. Ferner muss bei Ausfall eines Systems selbiges ersetzt werden – wie dabei die Kon­sis­tenz si­cher­stel­len? Mit phy­si­schen Maschinen ist diesen Problemen nur schwer Herr zu werden.

Virtuelle Maschinen als Schritt in die richtige Richtung

Aus der be­schrie­be­nen Pro­ble­ma­tik beim Einsatz phy­si­scher Maschinen entsprang die Be­liebt­heit vir­tu­el­ler Maschinen (VMs). Die Grundidee besteht darin, eine Schicht zwischen Hardware und Be­triebs­sys­tem bzw. Host-Be­triebs­sys­tem und Gast-Be­triebs­sys­te­men ein­zu­bin­den. Eine VM ent­kop­pelt die An­wen­dungs­um­ge­bung von der dar­un­ter­lie­gen­den Hardware. Die spe­zi­fi­sche Kom­bi­na­ti­on von Be­triebs­sys­tem, Anwendung, Bi­blio­the­ken und Kon­fi­gu­ra­ti­on ist aus einem Image re­pro­du­zier­bar. Neben der kom­plet­ten Isolation einer Anwendung erlaubt dies das Bündeln mehrerer An­wen­dun­gen in einer so­ge­nann­ten Appliance.

VM-Images lassen sich zwischen phy­si­schen Maschinen bewegen, zudem können mehrere vir­tua­li­sier­te Be­triebs­sys­te­me parallel betrieben werden. Damit ist die Ska­lier­bar­keit der Anwendung si­cher­ge­stellt. Jedoch ist die Be­triebs­sys­tem-Vir­tua­li­sie­rung res­sour­cen­in­ten­siv und bedeutet für simple An­wen­dungs­fäl­le einen Overkill.

Die Vorteile der Container-Vir­tua­li­sie­rung mit Docker

Die bei der Container-Vir­tua­li­sie­rung genutzten Images kommen ohne Be­triebs­sys­tem aus. Die Container-Vir­tua­li­sie­rung ist leicht­ge­wich­ti­ger und bietet annähernd denselben Grad an Iso­lie­rung wie VMs. Ein Container-Image vereint An­wen­dungs­code mit allen be­nö­tig­ten Ab­hän­gig­kei­ten und der Kon­fi­gu­ra­ti­on. Images sind zwischen Systemen portabel, die darauf auf­bau­en­den Container re­pro­du­zier­bar. Container lassen sich in ver­schie­de­nen Um­ge­bun­gen wie Ent­wick­lung, Pro­duk­ti­on, Test und Staging einsetzen. Mit der Layer- und Image-Ver­si­ons­kon­trol­le ist zudem ein hoher Grad an Mo­du­la­ri­tät gegeben.

Fassen wir die wich­tigs­ten Vorteile der Docker-basierten Vir­tua­li­sie­rung von An­wen­dun­gen im Gegensatz zum Einsatz einer VM zusammen. Ein Docker-Container:

  • enthält kein eigenes Be­triebs­sys­tem und keine si­mu­lier­te Hardware
  • teilt sich einen Be­triebs­sys­tem-Kernel mit anderen, auf demselben System ge­hos­te­ten Con­tai­nern
  • ist im Vergleich zu einer VM-basierten Anwendung in Bezug auf Res­sour­cen-Nutzung leicht­ge­wich­tig und kompakt
  • startet schneller als eine virtuelle Maschine
  • kann in mehreren Instanzen desselben Images parallel betrieben werden
  • lässt sich über Or­ches­trie­rung im Verbund mit weiteren con­tai­ner­ba­sier­ten Services nutzen
  • eignet sich optimal für die lokale Ent­wick­lung
Zum Hauptmenü