Docker
Einleitung
Docker ist eine ab 2013 von Docker inc. in der Programmiersprache Go entwickelte Software mit dem Ziel der Virtualisierung von Betriebsystemdiensten, damit Applikationen rechner- und betriebssystemunabhängig zur Verfügung gestellt werden können (Platform-as-a-Service, PaaS). Ausserdem ist Docker hervorragend für verteilte Systeme geeignet und stellt dazu robuste Werkzeuge zum Load Balancing (Lastverteilung) bereit.
Docker organisiert Anwendungen in Form von Containern. Ein Container ist eine aktiv laufende Instanz eines Images, welcher automatisch terminiert, wenn seine Aufgabe erfüllt ist. Ein Image kann als Rezept zum Start eines Containers verstanden werden: es enthält alle von der Anwendung benötigten Dienste, Dateien und Befehle in der Form von schreibgeschützten Layern (Schichten). Ein verteiltes System liegt dann vor, wenn mehrere Container des selben Images parallel ausgeführt werden.
Images, zum Beispiel ein Image zum betriebssystemunabhängigen Ausführen von Python-Programmen in der letzten Version, können beliebig von Entwicklern geteilt werden. Dabei werden Images in privaten oder allgemein zugänglichen Registries (Registrierungen) veröffentlicht. Die größte, öffentliche Registry ist Docker-Hub.
Docker ist mit über 100.000 Projekten, die auf Docker basieren, die aktuell populärste Virtualisierungssoftware. Größter Konkurrent von Docker ist rkt, welches im Gegensatz zu Docker versucht, eine universelle Sprache für Container zu verbreiten.
Interoperabilität hat einen hohen Wert in der Entwicklung von Software. Potentielle Nachteile von Docker sind jedoch Sicherheitsprobleme (Images von öffentlichen Registries können Schadcode beinhalten) sowie der hohe Speicherbedarf von Dockerapplikationen, da nicht einfach nur der Anwendungscode, sondern auch die virtualisierten Systemfunktionen in Images enthalten ist.
Die grundlegenden Funktionalitäten sind dabei kostenfrei in der Docker Consumer Edition enthalten, es gibt aber auch die leistungsfähigere und kostenpflichtige Docker Enterprise Edition.
Installation
Windows
Ein Installer für Windows wird auf der offiziellen Webseite von Docker zur Verfügung gestellt.
Mac-OSX
Die Installation auf Mac/OSX erfolgt via .dmg-installer.
Ubuntu Linux
Auf Ubuntu Linux wird Docker mit folgenden Eingaben installiert:
sudo apt-get remove docker docker-engine docker.io containerd runc -y sudo apt-get update sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io -y
Hallo Welt
Erzeugt der Terminalbefehl
docker run hello-world
eine Erfolgsmeldung, so ist Docker korrekt installiert (Auszug):
Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly.
Das Docker-Demoimage hello-world
wurde erst lokal gesucht und dann, da nicht vorhanden, von Docker-Hub heruntergeladen und als Container erfolgreich ausgeführt.
Achtung: Falls bei einem der Befehle unter Linux unzureichende Zugriffsrechte (Got permission denied...
) gemeldet werden, so muss der Befehl einfach mit einem vorangestellten sudo
ausgeführt werden!
Folgende Befehle laden BusyBox von Docker-Hub herunter und führen es aus:
docker pull busybox docker run busybox echo ”Hallo Welt!”
BusyBox ist eine reduzierte Linux-Kommandozeile; erfolgt als Ausgabe auf den zweiten Befehl Hallo Welt!
, so war die Installation des Images erfolgreich.
Wie in der Einleitung bereits beschrieben terminieren Container, wenn sie ihre Aufgabe erfüllt haben. Mit dem Parameter -it
wird BusyBox im interaktiven Modus gestartet und bleibt so lange aktiv, bis der Benutzer es schliesst:
docker run -it busybox
Dabei gilt, dass jeder Container in einer neu erstellten Umgebung gestartet wird. Wenn beispielweise das bin
-Verzeichnis von BusyBox gelöscht, danach aber der BusyBox-Container neu gestartet wird, so stehen die in bin
enthaltenen Terminalbefehle erneut zur Verfügung.
Container
Container sind laufende Instanzen von Docker-Applikationen und eine Kernkomponente von Docker. Weiterhin werden einige wichtige Befehle zum Management von Containern mit Docker vorgestellt.
Mittels
docker ps -a
werden alle derzeit laufenden Container angezeigt, beispielsweise BusyBox, wenn es im interaktiven Modus (siehe vorheriges Kapitel) gestartet wurde.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b67303440f9f busybox "sh" 21 seconds ago Up 18 seconds nifty_keller
Laufende Container können via ID beendet werden:
docker rm b67303440f9f docker container prune
Letzterer Befehl löscht alle beendeten Container.
Container in Docker bieten ihre Services über (Netzwerk-) Ports an. Die von einem Container mit der ID CONTAINER
verwendeten Ports werden so ausgegeben:
docker port CONTAINER
Damit ein Container Daten persistent speichern kann, muss entweder ein Bind Mount oder ein Volume eingerichtet werden.
docker run –it –name bindmount -v PFAD busybox
Mit obigem Kommando wird ein Bind Mount namens bindmount
angelegt, dass unter PFAD
Daten speichern kann. Volumes haben im Gegensatz zu Bind Mounts den Vorteil der Betriebssystemunabhängigkeit und werden wie folgt eingerichtet:
docker volume create volume1 docker run -it -name volume1 -mount type=volume,source=volume1,target=PFAD busybox
Dabei ist volume1
der beliebig zu wählende Name des Volumes und PFAD
der Speicherort.
Zum Löschen von Volumes dienen diese Befehle:
docker volume rm NAME docker volume prune
Der zweite Befehl löscht alle Volumes, die nicht gerade aktiv von Containern verwendet werden. Verwendet ein Container externe Dateien, so müssen diese in den Container kopiert werden:
docker cp DATEINAME CONTAINERNAME:/DATEINAME
Zum Betrachten der Entwicklungshistorie eines Containers dient der folgende Befehl:
docker diff CONTAINERNAME
Images
Images beinhalten alle Informationen, die nötig sind, um einen Container zu starten. Die einzelnen Befehle und Dateien eines Images sind in Layern organisiert.
Mit list
werden alle verfügbaren Images angezeigt:
docker image list
Ausgabe:
REPOSITORY TAG IMAGE ID CREATED SIZE busybox latest edabd795951a 19 hours ago 1.22MB hello-world latest bf756fb1ae65 8 months ago 13.3kB
Alle Images können so gelöscht werden:
docker rmi $(docker images -q)
Wird ausserdem -force
als Parameter übergeben, werden sogar aktiv in Repositories verwendete Images gelöscht. Images werden aus Docker Files erzeugt. Ein gültiger Docker-File sieht beispielsweise so aus:
FROM python:3-onbuild EXPOSE 5000 CMD [”python3”, ”./app.py”]
Die erste Zeile fügt ein Image für Python3 hinzu, die zweite Zeile markiert den Port 5000 für die Anwendung und die dritte Zeile beinhaltet den eigentlichen Start der Anwendung app.py
mit Python3. Wird diese Datei unter dem Namen DockerFile
im Verzeichnis, in dem auch app.py
liegt, gespeichert, so kann nun ein Image generiert werden:
docker build -t BENUTZERNAME/IMAGENAME .
Dabei ist BENUTZERNAME
der Name eines auf Docker-Hub registrierten Accounts und IMAGENAME
der gewünschte Name des Images. Falls das Image modifiziert wurde via DockerFile, ist es nötig, es neu zu kompilieren:
docker container commit IMAGENAME
Um das Image öffentlich verwendbar zu machen, muss es auf Docker-Hub (oder einer anderen öffentlichen Registry) publiziert werden:
docker push BENUTZER/IMAGENAME
Mit history
wird die Entwicklungshistorie eines Images ausgegeben.
docker history IMAGENAME
Für das früher verwendete Image hello-world
erzeugt das die Ausgabe
IMAGE CREATED CREATED BY SIZE COMMENT bf756fb1ae65 8 months ago /bin/sh -c #(nop) CMD ["/hello"] 0B <missing> 8 months ago /bin/sh -c #(nop) COPY file:7bf12aab75c3867a… 13.3kB
Wenn ein Image auf Docker-Hub aktualisiert wurde, kann die eigene,lokale Version mit pull
auf den neuesten Stand gebracht werden:
docker pull IMAGENAME
Weiterhin kann Docker-Hub nach passenden Images für einen Suchbegriff durchsucht werden:
docker search BEGRIFF
DockerFile
Ein DockerFile ist eine Reihe von Anweisungen, aus denen ein Image erstellt wird. Die wichtigsten dieser Kontrollanweisungen werden im folgenden vorgestellt.
FROM
Mit FROM
wird ein Basisimage, auf dem das generierte Image aufbaut, definiert.
FROM python:3-onbuild
EXPOSE
EXPOSE
definiert den Port, über den die Anwendung kommuniziert.
EXPOSE 5000
CMD
CMD
teilt dem Image mit, welche Befehle beim Containerstart ausgeführt werden sollen.
CMD [”python3”, ”./app.py”]
RUN
Durch RUN
können Befehle auf das Dateisystem des Images ausgeführt werden, wie z.B.
RUN apt-get update
ENV
Mittels ENV
werden Systemvariablen gesetzt:
ENV name=Brian
WORKDIR
Das Arbeitsverzeichnis des Images wird mit WORKDIR
definiert:
ENV name=Brian
USER
Der Linux-Benutzer des Images wird mit USER
bestimmt:
USER John
VOLUME
VOLUME
erzeugt ein Volume für das Image:
VOLUME /var/www/html/app
LABEL
LABEL
erlaubt es, dem aktuellen Stand des Images beliebige Metadaten anzuhängen:
LABEL version=”1.0”
COPY
Weiterhin werden mit COPY
Dateien in das Image kopiert.
COPY test.txt /Verzeichnis/
Compose
Docker Compose dient dazu, verschiedene Container zu einer gemeinsamen Anwendung zu vereinigen, beispielsweise eine Flask-Webanwendung in einem Container mit einer Redis-Datenbank in einem zweiten. So können komplexe Anwendungen modular aufgebaut werden. Um dies zu erreichen wird eine Konfigurationsdatei im YAML-Format, die sogenannte Compose File, benötigt. Eine solche kann zum Beispiel folgendermaßen aussehen:
version: ’3’ services: app: build: . image: takacsmark/flask-redis:1.0 environment: - FLASK_ENV=development ports: - 5000:5000 redis: image: redis:4.0.11-alpine
Dieser ComposeFile bestimmt zuerst die zu verwendende Version von Compose - dies muss unbedingt beachtet werden, da es Formatsunterschiede zwischen den verschiedenen Versionen gibt. Anschliessend wird das Buildverzeichnis .
spezifiziert. Zwei Images werden eingebunden: flask-redis:1.0
als Hauptimage und zusätzlich redis:4.0.11-alpine
. Als Port für die Anwendung wird 5000
festgelegt und ausserdem noch eine Umgebungsvariable FLASK_ENV
erzeugt.
Die Anwendung kann jetzt mit compose
gestartet werden (im Verzeichnis des ComposeFile!):
docker-compose up
Nun können beispielsweise via curl
Daten an die Anwendung gesendet und anschliessend wieder abgerufen werden:
curl –header ”Content-Type: application/json”\ –request POST \ –data ’{”name”:”John”}’ \ localhost:5000 curl localhost:5000
Es ist aber auch möglich, auf die Anwendung via Python zuzugreifen:
redis = Redis(host=”redis”, db=0, socket_timeout=5, charset=”utf-8”, decode_responses=True)
Swarm
Mit einem Docker Swarm (Schwarm) werden Container auf verschiedenen Rechnern (Nodes, Knoten) in einem gemeinsamen Cluster gemanagt. Die großen Vorteile eines Swarms sind Robustheit (ausgefallene Nodes werden neu gestartet) und Load Balancing (Lastenausgleich: je nach Bedarf werden Nodes beendet oder gestartet).
Dabei besteht ein Swarm aus Manager- und Workernodes: die Worker führen ausschließlich die eigentlichen Aufgaben aus, die Managernodes kontrollieren zusätzlich, ob die aktiven Container dem Bedarf entsprechen.
Zuallererst muss der Swarm initialisiert werden:
docker swarm init
Jetzt gilt es, Manager und Worker einzurichten. Dies erfolgt mittels Token. Der Befehl und Token zum Hinzufügen als Manager wird so angezeigt:
docker swarm join-token manager
Ausgabe:
To add a manager to this swarm, run the following command: docker swarm join --token SWMTKN-1-0b6wd94ymcgkg9e0n6wfvnqrq4ieaaxd11lbyikd0hsngx6xsj-f4vt04d7zbw5aqwv2d0iq7dbx 192.168.0.104:2377
Mit folgendem Kommando wird statt dessen der Befehl zum hinzufügen eines Workers ausgegeben:
docker swarm join-token worker
Ausgabe:
To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-0b6wd94ymcgkg9e0n6wfvnqrq4ieaaxd11lbyikd0hsngx6xsj-ecf9wql7bx1cd4vdwswdy7iys 192.168.0.104:2377
Beachte: Nur der letzte Teil des Tokens zum erzeugen eines Managers oder Workers ist verschieden, ansonsten sind die Befehle identisch!
Nun können diese Befehle auf beliebigen Rechnern im gleichen Netzwerk ausgeführt werden, um Worker oder Manager zum Swarm hinzuzufügen.
Die aktuelle Struktur des Rechnerclusters wird mit node
ausgegeben:
docker node ls
Ausgabe mit nur einem Managernode:
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION t95lcv3fc4tc9lgsjfpjabyrd * anton-narki Ready Active Leader 19.03.12
Von einem Managernode aus können andere Nodes unter Angabe der ID des Nodes als letztem Parameter gelöscht werden:
docker node rm t95lcv3fc4tc9lgsjfpjabyrd
Auf Nodes werden Dienste in Form von Containern wie folgt gestartet, hier als Beispiel ein Apache-HTTP-Daemon:
docker service create httpd
Zur Anzeige aller laufenden Dienste auf einem Node wird ebenfalls node
verwendet:
docker node ls
Ausgabe:
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS q7p6yiyrdswh musing_snyder.1 httpd:latest anton-narki Running Running 4 minutes ago
Beendet wird ein Dienst auf dem entsprechenden Node mit rm
und dem Namen des Dienstes:
docker service rm httpd
Spannenderweise kann dem Swarm mit einem einfachen Befehl mitgeteilt werden, wie viele Instanzen eines Dienstes parallel im Cluster laufen sollen:
docker service skale httpd=2
Mit einem sogenannten Routing Mash wird ein Dienst auf dem gesamten Cluster unter einem bestimmten Port (hier: 8080) zur Verfügung gestellt:
docker service create –name testhttpd –publish published=8080,target=80 httpd
Run
Das Docker-Programm zum starten von Containern verfügt über vielfältige Konfigurationsoptionen. Im folgenden werden die wichtigsten davon vorgestellt.
Detached
Detached ist ein Dienst, wenn er im Hintergrund läuft. Dies wird mit dem Flag -d
erreicht:
docker run -d image service nginx start
Tag
Mit einem Tag hinter dem Imagenamen wird das Image in der spezifizierten Version ausgeführt, zum Beispiel
docker run ubuntu:14.04
Namespace
Namespace bezeichnet den Wirkbereich der von einem Programm verwendeten Variablen. Mit dem Flag
–ipc=”MODUS”
und entweder none
, private
oder shareable
als Wert für MODUS
wird der Namespace für einen Container festgelegt. Dies ist unter anderem wichtig, wenn Container auf gemeinsame Daten zugreifen sollen.
Netzwerk
Die Netzwerkparameter eines Containers können mit run
bestimmt werden. Gültige Parameter sind
–dns=[] –add-host= –ip= –network=”MODUS”
mit none
, bridge
, container
oder host
als Wert für MODUS
.
Neustartverhalten
Das Neustartverhalten eines Containers kann praktischerweise ebenfalls mit run
festgelegt werden. Folgender Befehl startet den Container redis
jedesmal neu, wenn er beendet wurde oder abgestürzt ist:
docker run –restart=always redis
Glossar
Azure Container Registry
Registrierung von Docker-Images zur Arbeit mit MicroSoft Azure, welche die Verwendung von Azure Active Directory-Gruppen und -Berechtigungen gestattet.
Base-Image (Basis Image)
Grundlegendes, unterstes Image eines Containers. Wird beispielsweise ein Container für eine Python-Applikation entworfen, so stellt das Base Image mindestens den benötigten Python-Interpreter zur Verfügung.
Bind Mount
Ein Bind Mount ist ein einem laufenden Container zugeordneter, persistenter Speicherort im Dateisystem des Computers. Bind Mounts sind Betriebssystemabhängig.
Build (Aufbau)
Mit Build wird ein Containerimage basierend auf der Docker-Datei und zusätzlich benötigter Dateien aufgebaut. Der Befehl hierzu ist docker build
.
Cluster (etwa: Schwarm)
Ein Cluster bezeichnet eine Sammlung von Docker-Hosts, die zusammen einen einzelnen, virtuellen Host darstellen. Dies dient der Skalierung: weitere Instanzen der Anwendungen werden als Hosts dem virtuellen Host hinzugefügt. Dienste hierzu sind unter anderem Kubernetes, Docker Swarm und Azure Service Fabric.
Compose (etwa: komponieren)
Ein Kommandozeilenwerkzeug, mit dem basierend auf Konfigurationsdateien im YAML-Dateiformat und weiteren Metadaten Anwendungen mit mehreren Containern ausgeführt werden können. Dabei wird eine einzelne Anwendungen mit ein oder mehreren YAMLKonfigurationsdateien und allen zugehörigen Images definiert, die dann alle mit dem Befehl docker-compose-up
konvertiert werden.
Container
Container bezeichnet eine Instanz eines Docker-Images. Nach Start eines Containers stellt dieser einen Dienst dem System zur Verfügung. Zum Skalieren dieses Dienstes müssen mehrere Instanzen eines Containers gestartet werden, entweder manuell oder automatisiert mit einem Batchauftrag, der jeder Instanz eigene Startparameter übergibt.
Docker Client
Kommandozeilenwerkzeug, durch das der Benutzer mit dem Docker Daemon interagieren kann.
Docker Community Edition (CE)
Softwarepacket zum Erzeugen, Ausführen und Testen von Containern für Windows und macOS. Linux-Container können mit Docker CE
mit der Hyper-V-VM (Virtuelle Maschine) entwickelt werden. Die macOS-Variante basiert auf dem Hypervisor-Framework und dem Xhyve-Hypervisor. Docker CE ist der Nachfolger der Docker Toolbox.
Docker Daemon
Programm im Hintergrund, welches das bauen, starten und verteilen von Docker-Containern kontrolliert.
Docker-Datei (Docker-File)
Die Anweisungen zum Erstellen eines Docker-Images werden in einer Docker-Datei zusammengefasst. Die erste Zeile bezeichnet das Basisimage, die folgenden Zeilen beinhalten Anweisungen zur Installation der benötigten Programme und Dateien, bis alle erforderlichen Funktionalitäten vorhanden sind.
Docker Enterprise Edition (EE)
Auf Unternehmen ausgerichtete Version von Docker für die Linux- und Windows-Entwicklung.
Docker-Hub
Docker-Hub ist die größte öffentliche Registrierung von Images und im Besitz des Unternehmens Docker. Dabei bietet Docker-Hub unter anderem Docker-Image-Hosting, öffentliche oder private Registrierungen, Buildtrigger, Web-Hooks und Integration mit GitHub.
Docker Trusted Registry (DTR)
Eine im Produkt Docker Datacenter enthaltene Registrierung zur Verwaltung privater Images im lokalen Netzwerk.
Host (etwa: Wirt)
In Bezug auf Docker ist ein Host ein Computer, der Docker-Container ausführt. Docker unterstützt die Betriebssysteme Windows, macOS und Linux.
Image (etwa: Abbild)
Ein Image enthält alle erforderlichen Abhängigkeiten und Informationen zum Erstellen eines Containers. Ebenfalls beinhaltet ist die von einer Containerruntime verwendete Konfiguration. Üblicherweise besteht ein Image aus mehreren, in Schichten organisierten Sub-Images, die das Dateisystem des Containers darstellen. Images können multiple Architekturen unterstützen,
indem sie bei ihrer Erzeugung Sub-Images für die Rechnerarchitektur benötigte Versionen von Sub-Images anfordert. Nach dem Erstellen kann ein Image nicht mehr geändert werden.
Layer (Ebene)
Ein Layer bezeichnet über ein Image gelegte Modifikationen. Wird ein Image erneut aufgebaut, werden nur geänderte Layer aktualisiert.
Mehrstufige Builds
Seit Docker 17.05 kann mit mehrstufigen Builds die Größe des finalen Images verringert werden. Beispielsweise kann so ein großes Basisimage mit komplettem SDK (software development kit) verwendet werden, um die Anwendung zu kompilieren. Im finalen Image wird dann statt dessen jedoch ein kleines Basisimage, das lediglich die Laufzeitumgebung (Runtime Environment)
enthält.
Node (Knoten)
Eine Instanz eines Docker-Swarm, die einen Container ausführt.
Orchestrator
Ein Orchestrator ist ein Werkzeug zur Vereinfachung der Verwaltung von Clustern und Docker-Hosts. Images, Container und Hosts werden über die Kommandozeile oder eine graphische Schnittstelle verwaltet. Dies erlaubt die Kontrolle von Containernetzwerken, des Gerätestatus, des Lastenausgleichs sowie der Container- und Hostconfiguration. Orchestratoren werden beispielsweise von Kubernetes und Azure Service Fabric angeboten.
Registrierung
Eine Sammlung von Images. Der Docker-Hub ist die größte Sammlung öffentlich verfügbarer Images. Viele Unternehmen, die mit Docker arbeiten, haben private verfügbare Registrierungen für selbst entwickelte Images.
Repositoryname (Repo)
Der Repositoryname ist Auflistung der verwendeten Docker-Images mit einem Tag, das die Version des Images angibt. Dabei kann ein Repository mehrere Varianten eines Images enthalten, wie zum beispiel SDK- und Runtime-Varianten oder Images für Linux und Windows.
Service (Dienst)
Ein Service bezeichnet den Umfang eines Docker-Swarm bezüglich eines einzelnen Images.
Stack
Stack bezeichnet die Menge der von einem Schwarm bereitgestellten Services.
Tag (Bezeichner)
Vom Nutzer gewählter Bezeichner eines Images, die verwendet wird, um Images oder Image-Versionen unabhängig von Versionsnummer oder Zielumgebung identifizieren zu können.
Virtuelle Maschine (Virtual machine)
Eine virtuelle Maschine simuliert eine Laufzeitumgebung in einem nicht-nativen System. Container sind eine leichtgewichtige Form virtueller Machinen, die nur die absolut nötigsten Betriebssystemsoperationen bereitstellen.
Volume
Ein Volume ist ein persistenter Datenspeicher für die von Containern gespeicherte Daten. Sie können von verschiedenen Containern parallel genutzt werden und sind Betriebssystemunabhängig.