Vom Dockerfile zum Docker-Image

Docker Basics: Einführung in die System-Level-Virtualisierung
Kommentare

Möchte man sich tiefer in eine neue Technologie einarbeiten, ist fundiertes Grundlagenwissen unabdingbar. Deshalb möchten wir Sie in die Basics von Docker einführen. Dabei wird unter anderem demonstriert, wie man hundert virtuelle Maschinen auf einem eigenen Rechner starten kann. Alle Beispiele können von GitHub heruntergeladen und ausprobiert werden.

Der Einsatz von virtuellen Maschinen hat viele Vorteile. Sie lasten Hardware besser aus, man kann leicht Backups machen und einzelne Dienste auf einer Hardware voneinander isolieren. Virtuelle Maschinen haben aber auch Nachteile. Ihre Images sind meist mehrere Gigabytes groß und sperrig. Wesentlicher ist jedoch, dass virtuelle Maschinen viele Ressourcen verbrauchen, da stets ein gesamter Rechner emuliert und ein Betriebssystemkernel gestartet werden muss. Docker ist eine leichtgewichtige Alternative, die System-Level-Virtualisierung unter Linux nutzt und so die Vorteile von virtuellen Maschinen ohne die genannten Nachteile erreicht. Dieser Einführungsartikel bezieht sich auf die Docker-Version 1.2.0. Alle Beispiele sind vollständig auf GitHub verfügbar.

Was ist Docker?

Docker hat in diesem Jahr sehr viel Traktion entwickelt und wird in aktuellen Distributionen von Red Hat und Ubuntu ausgeliefert. Firmen wie Spotify, Google, BBC, eBay, CenterDevice und seit Kurzem auch Zalando (Interview auf Seite 94) setzen Docker bereits produktiv ein. Die geschickte Kombination aus Laufzeitumgebung und Image Repository erleichtert den Einsatz von virtuellen Maschinen enorm. Zur Unterscheidung von Hypervisor-basierten virtuellen Maschinen wird von System-Level-basierten virtuellen Maschinen oder einfach Containern gesprochen. Damit ist Docker ein hervorragendes Werkzeug, um die tägliche Arbeit von Entwicklern und Administratoren zu vereinfachen.

Docker nutzt spezielle Kernelfunktionalitäten, um einzelne Prozesse in Containern voneinander zu isolieren. Für Prozesse, die mit Containern gestartet werden, scheint es so, als würden sie auf einem eigenen System laufen. Bis Version 0.8.1 setzte Docker für die Prozessisolation das Linux-Containers-Projekt (LXC) ein. Seit Version 0.9 bringt Docker eine eigene Container-Bibliothek namens libcontainer mit. LXC wird jedoch weiterhin unterstützt.

Die Basis von Linux-Containern sind Kernel-Namespaces, cgroups und Container-Templates. Wie Java Packages ermöglichen Kernel-Namespaces eigene Namensräume für Prozesse zu erzeugen. Prozess-IDs, Mount Points, Netzwerkgeräte und mehr können so pro Prozess wiederverwendet werden. Nähere Details lesen Sie im Artikel „Docker Network Magic“ auf Seite 28. Zum Beispiel können zwei Prozesse auf die Netzwerkschnittstelle eth0 zugreifen, die jedoch tatsächlich vom Kernel auf die zwei physikalischen Schnittstellen eth1 und eth2 gemappt werden. Die Prozesse bekommen davon nichts mit. cgroups ist eine Kernelerweiterung, die ursprünglich von Google entwickelt worden ist und es erlaubt, Ressourcenbeschränkungen auf Prozessebene zu definieren. Damit lässt sich zum Beispiel festlegen, wie viel Speicher ein Prozess nutzen darf. Container-Templates sind eine Konvention einer Verzeichnisstruktur, die das Root-Dateisystem eines Linux-Container-Prozesses darstellt. Nähere Details bietet der Artikel „Docker: die Linux-Basics unter der Container-Haube“.

Solche Linux-Container werden zum Beispiel von Heroku eingesetzt, um Prozesse einzelner Kunden voneinander zu trennen. Docker ist eine offene Implementierung dieses Konzepts, das darüber hinaus noch viele weitere spannende Funktionen mitbringt. Ferner bietet es Werkzeuge zum Erstellen, Austauschen und Ausführen von Linux-Containern, sodass diese einfach genutzt werden können. Es wird als Open Source federführend von der Firma Docker, Inc. entwickelt. Lesen Sie dazu auch das Interview mit Docker, Inc. auf Seite 40. Ein typisches „Hello World“ mit Docker zeigt das Listing 1. Was hier genau passiert, wird im Folgenden genau erklärt.

Listing 1

> docker run -t ubuntu:14.04 /bin/echo "Hello World"
Unable to find image 'ubuntu:14.04' locally
Pulling repository ubuntu
826544226fdc: Download complete
511136ea3c5a: Download complete
b3553b91f79f: Download complete
ca63a3899a99: Download complete
ff01d67c9471: Download complete
7428bd008763: Download complete
c7c7108e0ad8: Download complete
Hello World

Docker-Bestandteile

Docker besteht grundsätzlich aus drei Teilen: Docker Daemon, Docker Images und Docker Repository (Abb. 1). Der Docker Daemon verwaltet die Laufzeitumgebung von Docker-Containern. Genauso wie virtuelle Maschinen auf Images basieren, basieren Docker-Container auf Docker Images. Diese unterscheiden sich jedoch in einem wichtigen Punkt von Images in virtuellen Maschinen: Sie sind stapelbar. Docker Repositories sind eine Art GitHub für Images. Images werden in Repositories wie in einem Source Code Repository versioniert abgelegt. Es gibt ein öffentliches, von Docker, Inc. betriebenes Repository, das Docker Hub genannt wird. Dort findet man für sehr viele Dienste, wie zum Beispiel MongoDB, RabbitMQ, Redis, CouchDB, Elasticsearch, Postgres, MySQL, Apache Tomcat etc. fertige Images. Es werden auch komplette Stacks für Programmiersprachen wie Go, Java, Ruby etc. angeboten. Alle Images aus diesem Artikel findet man ebenfalls hier. Man kann aber auch private, nur für einen geschlossenen Personenkreis zugängliche Repositories betreiben. Im Artikel „Docker-Tools und -Helferlein“ wird beschrieben, wie man eine eigene Docker Registry betreiben kann.

Abb. 1: Bestandteile von Docker – siehe auch [6]

Abb. 1: Bestandteile von Docker – siehe auch hier

Abbildung 1 veranschaulicht die Bestandteile von Docker und zeigt, wie diese miteinander verbunden sind. Zusätzlich wird boot2docker dargestellt, das Docker auf Windows und OS X mithilfe von VirtualBox zugänglich macht. Weitere Details dazu lesen Sie weiter unten im Abschnitt Installation.

Stapelbare Dateisysteme

Bei stapelbaren Dateisystemen werden einzelne Schichten aufeinander gelegt. Jede dieser Schichten wird dabei getrennt erstellt. Bekannt ist dieses Verfahren von so genannten Live-CDs, bei denen ein Betriebssystem von einer CD gestartet wird. Dabei ist der Inhalt der CD unveränderbar. Um dennoch Dateien anlegen und löschen zu können, wird zunächst der Inhalt der CD als nur-lesbar gemountet und anschließend eine zweite, im Hauptspeicher gehaltene, schreibbare Schicht über die erste gelegt. So entsteht für den Benutzer die Illusion eines schreibbaren Dateisystems mit allen Dateien der CD. Das gleiche Prinzip verwendet Docker für Images. Im Gegensatz zu Images von virtuellen Maschinen werden Änderungen an Docker Images Schicht für Schicht gespeichert. Auf diese Weise genügt es, nur diese Änderungen zu speichern, sodass Docker Images kleiner ausfallen und wiederverwendbar sind.

Abbildung 2 zeigt ein Beispiel dazu. Ausgehend von einem Debian-Basis-Image werden in zwei Schichten zunächst Emacs und Apache hinzugefügt. Dieses Dreischichten-Image kann anschließend als Container gestartet werden. Dazu wird auf die vorherigen Schichten eine weitere, schreibbare Schicht während der Laufzeit hinzugefügt. Möchte man nun lieber nginx anstatt Apache als Webserver benutzen, so kann man vom Image Emacs ausgehend, nginx an Stelle von Apache hinzufügen. Zusätzlich gespeichert wird in diesem Fall ausschließlich die neue Schicht nginx. Auf diese Weise kann man Images von anderen ableiten, ohne dass Daten repliziert werden. Dies ermöglicht flexible und platzsparende Ketten von Images, die leicht in Repositories gespeichert und mit anderen getauscht werden können. Wie Docker Images konkret erstellt werden, wird im nächsten Abschnitt beschrieben. Zurzeit verwendet Docker standardmäßig AUFS für Images. Jedoch kann es leicht auf BTRFS umgestellt werden, das effizienter und schneller arbeitet, jedoch noch nicht von allen Distributionen von Haus aus unterstützt wird.

Abb. 2: UnionFS für Docker Images (© Docker, Inc.)

Abb. 2: UnionFS für Docker Images (© Docker, Inc.)

Hundert virtuelle Maschinen

Das Docker Motto lautet „Build, Ship and Run Any App, Anywhere“. Dieses Motto soll uns auch als Leitsatz für einen tieferen Einstieg in Docker dienen. Als Beispielprojekt nehmen wir einen Python-Webserver, der in hundert System-Level-virtuellen Maschinen skaliert werden soll – auf nur einem System. Die Container-Technologie macht es möglich. Der Grund: Es müssen eben nicht hundert Kernel in separaten Speicherbereichen gestartet werden, sondern der Kernel des Hostbetriebssystems wird wiederverwendet. Das vollständige Beispiel liegt auf GitHub mit Installationsanleitung bereit.

Installation

Grundsätzlich läuft Docker nur unter Linux und kann auf den meisten aktuellen Distributionen installiert werden, bei Fragen zu konkreten Installationsschritten hilft https://docs.docker.com/installation/ weiter. Für OS X und Windows-Benutzer ist den Beispielen auf GitHub eine Datei für Vagrant beigefügt, die vollautomatisch eine virtuelle Maschine provisioniert, um Docker auch unter Windows und OS X ausprobieren zu können. Das Projekt boot2docker für OS X und Windows nutzt einen ähnlichen, generelleren Ansatz und ermöglicht so, dass beliebige Docker-Container plattformunabhängig verwaltet und genutzt werden können. Es wird im Detail im Artikel „Docker-Tools und Helferlein“ vorgestellt. Die beschriebenen Beispiele funktionieren natürlich auch mit boot2docker.

Vom Dockerfile zum Docker-Image

Ein Dockerfile ist eine Art Bauanleitung für ein Docker-Image. Es enthält die einzelnen Schritte, die notwendig sind, um aus einem beliebigen Basis-Image ein neues Docker Image zu erstellen. In unserem Beispiel nutzen wir zwei Dockerfiles. Das erste baut auf einem Ubuntu 14.04 auf und fügt die Python Runtime hinzu. Das zweite Dockerfile installiert das Python-Webserverskript. Es entstehen also zwei Images. Diese Trennung ist sinnvoll, da Änderungen am Python-Skript lediglich das Erstellen des zweiten Images zur Folge haben, ohne dass erneut die Python Runtime installiert werden müsste. Listing 2 zeigt das erste Dockerfile.

Listing 2

FROM ubuntu:14.04
MAINTAINER Lukas Pustina <lukas.pustina@codecentric.de>
RUN apt-get install -y python

Das FROM-Kommando gibt das Basis-Image an. Es muss stets das erste Kommando in einem Dockerfile sein. Das zweite Kommando gibt den MAINTAINER an. Das dritte Kommando RUN ist ein Shell-Befehl, der auszuführen ist, während das Image gebaut wird. Hier wird über apt-get zunächst der Paketcache aktualisiert und anschließend Python installiert. Listing 3 ist etwas komplizierter und beschreibt, wie das zweite Image erstellt werden soll. Zunächst ist zu sehen, dass dieses Mal von einem anderen Basis-Image abgeleitet wird. Es handelt sich hierbei um unser erstes Image. Wie sich die Namen zusammensetzen, wird weiter unten erläutert. Zunächst werden zwei Dateien aus dem lokalen Verzeichnis ins Image mit dem Kommando ADD hinzugefügt. Dies ist zum einen der in Python geschriebene Webserver und zum anderen ein kurzes Shell-Skript, das den Webserver startet.

Listing 3

FROM lukaspustina/docker_demo_python
MAINTAINER Lukas Pustina <lukas.pustina@codecentric.de>
ADD webserver.py /opt/webserver/webserver.py
ADD run.sh /opt/webserver/run.sh
EXPOSE 8080
VOLUME ["/logs"]

Docker-Container dürfen grundsätzlich nur Netzwerkanfragen vom selben Host beantworten. Dies ist natürlich bei einem Webserver langweilig. Aus diesem Grund wird mithilfe des Kommandos EXPOSE der Port 8080 für externe Anfragen freigegeben. Nur zu freigegebenen Ports sind externe Verbindungen erlaubt. Da ein Docker-Container wie eine virtuelle Maschine ein eigenständiges System ist, kann man zur Laufzeit zunächst nicht ohne Weiteres in das System hineinschauen. Das Kommando VOLUME erzeugt innerhalb des Containers eine Art Mount Point, in den von außen, also vom Hostbetriebssystem, ein lokales Verzeichnis oder lokaler Mount Point eingebettet werden kann. In diesem konkreten Fall wird das Verzeichnis /logs des Containers als ein solches Volume gekennzeichnet. Beim Starten des Containers wird dann angegeben, welches lokale Verzeichnis in den Container eingeblendet werden soll. Hierbei kann ein lokales Verzeichnis auch an mehrere Container weitergeleitet werden.

Docker Images bauen: docker build

Aus diesen Dockerfiles werden nun auf der Kommandozeile die beiden Docker Images erzeugt:

docker build --force-rm -t lukaspustina/docker_demo_python python
> docker build --force-rm -t lukaspustina/docker_demo_webserver webserver

Dieses Kommando baut das erste Docker Image mithilfe des Dockerfiles im Verzeichnis python. Hierbei erzeugt jedes Kommando im Dockerfile ein neues Docker Image mit einer eigenen, Git-ähnlichen ID. Jedes dieser Images wird in das lokale Repository committet und kann wiederverwendet werden. Dies kann natürlich bei vielen Dockerfiles und vielen Builds zu einer großen Menge von Docker Images führen, die man normalerweise nicht benötigt. Der Parameter –rm führt deswegen nach erfolgreichem Erstellen des finalen Images dazu, dass alle Zwischenimages (Intermediate Images) gelöscht werden. Mithilfe des Parameters –force-rm wird auch bei Fehlern anschließend bereinigt. Wie auch bei Git, kann man einem Commit bzw. einem Image einen Tag hinzufügen, über den es leichter als über die ID ansprechbar ist. Dies wird mit dem Parameter -t erreicht. Hierbei ist der Begriff Tag doppelt besetzt. Der Aufbau des Tagnamens folgt hier der GitHub-Konvention <User>/<Repository>:<Tag>. <Tag> wird üblicherweise für explizite Versionen wie 0.0.1 oder latest verwendet.

Mit den Befehlen docker images bzw. docker images –tree kann man sich die aktuellen Images im lokalen Repository als Liste oder als Abhängigkeitsbaum anzeigen lassen (Listing 4). Obwohl –tree ein sehr hilfreicher Parameter ist, wurde er von Docker, Inc. abgekündigt und wird in kommenden Versionen vermutlich verschwinden.

Listing 4

> docker images
lukaspustina/docker-demo_python     latest   ef489f0186e1  Less than a second ago   210.7 MB
lukaspustina/docker-demo_webserver  latest   b898a85622e4  Less than a second ago   210.7 MB
ubuntu                 quantal  b750fe79269d  9 months ago             175.3 MB

> docker images --tree
└─27cf78414709 Size: 175.3 MB (virtual 175.3 MB)
  └─b750fe79269d Size: 77 B (virtual 175.3 MB) Tags: ubuntu:quantal
    └─7c1926658b21 Size: 0 B (virtual 175.3 MB)
      └─ef489f0186e1 Size: 35.43 MB (virtual 210.7 MB) Tags: lukaspustina/docker_demo_python:latest
        └─90807ce05b64 Size: 0 B (virtual 210.7 MB)
          └─d3822b811f4c Size: 0 B (virtual 210.7 MB)
            └─380871b22bde Size: 1.454 kB (virtual 210.7 MB)
              └─8f5a9cde5ae2 Size: 161 B (virtual 210.7 MB)
                └─b898a85622e4 Size: 0 B (virtual 210.7 MB) Tags: lukaspustina/docker_demo_webserver:latest

In der Baumansicht kann man deutlich sehen, dass die tatsächliche Image-Größe nur um Änderungen wächst. Zum Bespiel wachsen die Images 380871b22bde und 8f5a9cde5ae2 um genau die Dateigrößen des in Python geschriebenen Webservers und des kleinen Startskripts – zum Vergleich lohnt ein Blick auf die ADD-Kommandos aus Listing 3. Der Kommandozeilen Parameter -f / –filter kann eingesetzt werden, um die Ausgabe von docker images zu filtern und so einzuschränken.

Änderungen

Ein großer Vorteil der stapelbaren Images ist es, dass sich Änderungen nur auf einzelne Images und deren Kind-Container auswirken. Ändert man zum Beispiel etwas am Quelltext des Python-Webservers und erstellt die beiden Images neu, so zeigt die Baumansicht nur darauf aufbauende Änderungen. Die ursprünglichen Images bleiben natürlich im lokalen Repository erhalten, verlieren jedoch ihr Tag, das zu den neuen Images überführt wird (Listing 5).

Listing 5

> docker images --tree 
└─27cf78414709 Size: 175.3 MB (virtual 175.3 MB)
  └─b750fe79269d Size: 77 B (virtual 175.3 MB) Tags: ubuntu:quantal
    └─7c1926658b21 Size: 0 B (virtual 175.3 MB)
      └─ef489f0186e1 Size: 35.43 MB (virtual 210.7 MB) Tags: lukaspustina/docker_demo_python:latest
        └─90807ce05b64 Size: 0 B (virtual 210.7 MB)
          |─0e153dd3c547 Size: 0 B (virtual 210.7 MB)
          | └─922d6e34d2d5 Size: 1.454 kB (virtual 210.7 MB)
          |   └─4bfc3d0fb5b2 Size: 161 B (virtual 210.7 MB)
          |     └─abac38e20f27 Size: 0 B (virtual 210.7 MB) Tags: lukaspustina/docker_demo_webserver:latest
          └─d3822b811f4c Size: 0 B (virtual 210.7 MB)
            └─380871b22bde Size: 1.454 kB (virtual 210.7 MB)
              └─8f5a9cde5ae2 Size: 161 B (virtual 210.7 MB)
                └─b898a85622e4 Size: 0 B (virtual 210.7 MB)

Container starten: docker run

Das Starten eines Images instanziiert einen neuen Container und wird über das Kommando docker run ausgeführt:

> docker run -d --cidfile=webserver.cid --name=webserver -v 'pwd'/logs:/logs 
lukaspustina/docker_demo_webserver:latest /opt/webserver/run.sh

Hier wird das letzte Image mit dem Tag lukaspustina/docker_demo_webserver gestartet. Also anstatt Image abac38e20f27 wird das aus dem zweiten Build erzeugte Image b898a85622e4 instanziiert. Die Container-ID ist eine Art PID für die Instanz und wird über den Parameter –cidfile in eine Datei geschrieben. Wie Images werden auch Container über IDs angesprochen. Um dies zu vereinfachen, kann man einem Container auch einen eindeutigen Namen über den Parameter –name geben. Dieser darf, solange der Container existiert, nicht wieder verwendet werden. Die letzten beiden Argumente geben an, welches Image genutzt und welches Shell-Kommando innerhalb des Containers gestartet werden soll. Hierbei ist wichtig zu beachten, dass Docker auf Prozessisolation basiert. Es wird also genau ein einziger Prozess gestartet.

Volumes

Im Dockerfile für den Webserver wird das Kommando VOLUME benutzt, um einen Mount Point für lokale Verzeichnisse zu erstellen, auf die der Container zugreifen können soll. Dieser Mount Point wird beim Starten des Containers mit dem Parameter -v, –volume belegt. Hier im Beispiel wird das lokale Unterverzeichnis logs an den Mount Point /logs im Container gebunden.

Mehr Container

Mit einer einfachen for-Schleife kann man auch mehrere Container starten:

> for i in 'seq 1 100'; do
docker run -d --cidfile=webserver-$i.cid --name=webserver-$i --volume 'pwd'/logs:/logs lukaspustina/docker_demo_webserver:latest /opt/webserver/run.sh 
/logs;
done

Devices

docker run ermöglicht es mithilfe des Parameters –device, Geräte vom Wirtssystem direkt in den Docker-Container zugreifbar zu machen. So kann zum Beispiel eine Festplatte anstatt eines Volumes direkt in den Container eingeblendet werden:

> docker run -d --cidfile=webserver-$i.cid --name=webserver-$i --
device=/dev/sdc:/dev/xvdc /opt/webserver/run.sh /logs

Damit dieses Mapping funktioniert, muss der Prozess im Container natürlich die Festplatte entsprechend nach /logs mounten. Im Beispiel müsste man das Skript run.sh dafür anpassen.

Umgebungsvariablen

Ein weiterer, sehr hilfreicher Kommandozeilenparameter ist -e / –env, der es ermöglicht, Umgebungsvariablen im Container zu definieren, die der Prozess im Container auswerten kann. So können zum Beispiel Konfigurationsparameter zur Laufzeit übergeben werden. Docker nutzt einen ähnlichen Mechanismus, um die Kommunikation zwischen Docker-Containern zu erleichtern. Dieser Container-Link genannte Mechanismus wird im Artikel „Docker Network Magic“ auf Seite 28 vorgestellt.

Container anzeigen: docker ps

Mit dem Kommando docker ps kann man sich die laufenden Container anzeigen lassen (Listing 6). Hier sieht man die ersten zehn Container mit den Namen webserver-* und ihre freigegebenen Ports 8080.

Listing 6

CONTAINER ID  IMAGE                          COMMAND                PORTS     NAMES
63120c069662  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-10
5af293d56a0b  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-9
a4a44f159658  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-8
40b35ff10aa2  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-7
6e9127f8a113  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-6
4623905191d2  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-5
e57def12fe25  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-4
c352eccb21de  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-3
f65e43115864  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-2
111c936c2763  lukaspustina/docker_demo_webserver:latest   /opt/webserver/run.sh   8080/tcp  webserver-1

Der Kommandozeilenparameter -f / –filter ermöglicht es, die Ausgabe von der docker ps -a nach Exit-Codes zu filtern; zum Beispiel docker ps -a -f exited=-1.

Container Details: docker inspect

Um einen der laufenden Webserver anzusprechen, ist neben dem Port auch die IP-Adresse des Containers notwendig. Diese kann man mithilfe des Kommandos docker inspect abfragen. Das Ergebnis dieses Kommandos ist ein JSON-Dokument mit allen Informationen über den Container. Mithilfe des Parameters -f kann man die Ausgabe auf einen Teil des Dokuments beschränken. Folgender Aufruf zeigt die IP-Adressen aller laufenden Container an:

&gt docker ps -q | xargs docker inspect -f '{{ .NetworkSettings.IPAddress }}'
172.17.0.12
172.17.0.11
...

Wie man sieht, bekommt jeder Container eine eigene IP-Adresse zugewiesen. Über die IP-Adressen und den Port 8080 kann man nun die Webserver abfragen.

Ressourcenverbrauch

Ein großer Vorteil von Docker bzw. der System-Level-Virtualisierung ist der im Vergleich zur Hypervisor-basierten Virtualisierung signifikant geringere Ressourcenverbrauch. Dies kann man sich konkret ansehen. Führt man das Kommando ps aux | grep ‚webserver‘ aus, werden die verbrauchten Ressourcen für die gestarteten Container ausgegeben (Listing 7).

Listing 7

PID %CPU %MEM   RSS COMMAND
2091  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2137  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py
2149  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2195  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py
2208  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2256  0.0  0.7  7596 /usr/bin/python /opt/webserver/webserver.py
2269  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2316  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py
2329  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2377  0.0  0.7  7588 /usr/bin/python /opt/webserver/webserver.py
2390  0.0  0.1  1476 /bin/bash /opt/webserver/run.sh /logs
2436  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py
2449  0.0  0.1  1476 /bin/bash /opt/webserver/run.sh /logs
2498  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py
2511  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2559  0.0  0.7  7588 /usr/bin/python /opt/webserver/webserver.py
2572  0.0  0.1  1472 /bin/bash /opt/webserver/run.sh /logs
2617  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py
2630  0.0  0.1  1468 /bin/bash /opt/webserver/run.sh /logs
2677  0.0  0.7  7592 /usr/bin/python /opt/webserver/webserver.py

Es zeigt sich, dass allein die in Container gesperrten Prozesse Ressourcen verbrauchen. Für jeden Webserver sieht man einmal das Shell-Skript (/bin/bash /opt/webserver/run.sh /logs) und den darin gestarteten Python-Webserver (/usr/bin/python /opt/webserver/webserver.py). Es entfällt der übliche Overhead für Betriebssystem und Systemprozesse bei der Nutzung von Hypervisor-basierten virtuellen Maschinen.

Container anhalten und wieder starten: docker pause, unpause

Docker-Container können ähnlich wie virtuelle Maschinen mit den Kommandos docker pause pausiert und docker unpause wieder gestartet werden. Dies ist insbesondere sinnvoll, wenn man den Zustand eines Containers mithilfe von docker commit sichern möchte. Auf diese Weise wird vermieden, dass während der Sicherung der Prozess im Container Änderungen vornimmt und die Sicherung somit inkonsistent wird. docker build nutzt diesen Mechanismus intern.

Container stoppen: docker stop, kill, commit, remove

Die Kommandos docker stop und docker kill beenden einen Container mit den Signalen SIGTERM bzw. SIGKILL. Das Beenden eines Containers führt lediglich zum Stoppen der Ausführung. So wie eine gestoppte virtuelle Maschine existiert der Container danach weiter, jedoch wird er nicht mehr ausgeführt. Man kann sich nun entscheiden, ob etwaige Änderungen am Image gespeichert oder der Container tatsächlich gelöscht werden soll. docker commit speichert die Änderungen am Image als weitere Schicht und docker rm löscht einen Container.

Ausblick

Docker bietet noch viel mehr Funktionen zum Erstellen und Pflegen von System-Level-virtuellen Maschinen. Insbesondere die Möglichkeit, Images in einer zentralen Registry intern für Kollegen oder sogar öffentlich über den Docker Hub anzubieten, erlaubt eine einfache und effektive Wiederverwendung von Docker Images.

Docker 1.3
Die nächste Version 1.3 von Docker, die nach Redaktionsschluss veröffentlicht wurde, enthält drei erwähnenswerte neue Funktionen. docker exec erlaubt es, einen weiteren Prozess in einem laufenden Container zu erzeugen. So kann man zum Beispiel eine Shell starten und Einstellungen sowie Logfiles prüfen. Mit docker create / start kann docker run in zwei separate Schritte aufgeteilt werden, um den Lebenszyklus von Containern flexibel zu beeinflussen. Schließlich ermöglicht der neue Parameter docker run –security-opts SELinux- und AppArmor-Profile für Container zu setzen. Mehr Details finden sich in den Release Notes von Docker 1.3.

 

Bei CenterDevice setzen wir Docker sowohl in der Entwicklung als auch der Produktion ein. Da ein Docker Image unabhängig vom Hostbetriebssystem ist, kann dasselbe Image für die Entwicklung auf dem Entwicklernotebook als auch in der Produktion auf einem Server eingesetzt werden. Docker erleichtert so DevOps, indem Entwickler ein Docker Image bauen, wie sie es benötigen und Administratoren Container als unabhängige Dienste auf den Produktivservern starten können. Im Artikel „Docker – Tools und Helferlein“ werden darüber hinaus hilfreiche Werkzeuge aus dem Docker-Universum vorgestellt – viel Spaß beim Lesen!

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -