Docker-Workshop (1): Installation, erste Schritte

Docker-Workshop (1): Installation, erste Schritte

Dies ist der erste Teil eines mehrteiligen Docker-Workshops. Wir erklären, was Docker ist, wofür es sich sinnvoll einsetzen lässt und wie Sie es verwenden. Außerdem erfahren Sie, was sich in der neuen Docker-Version 1.13 geändert hat.

Geht es um Docker, ist immer die Rede von Containern. Doch das ist in etwa so ausagekräftig, wie beim Programmieren von Objekten zu sprechen. "Containers don’t contain" liest man des öfteren im Internet, vor allem wenn die Rede auf die mangelnde Sicherheit von Containern kommt.

Im Gegensatz zu älteren Containerlösungen wie Virtuozzo und OpenVZ ist die Sicherheit eines Docker-Containers nicht das primärze Ziel. Jedenfalls bisher, denn mit stärkerer Verbreitung (vor allem im "Enterprise") steigt auch das Sicherheitsbewusstsein und die Forderung nach mehr Sicherheit wird lauter. Bisher gilt die Empfehlung, Docker-Container mit AppArmor oder SELinux einzuschränken.

Der Erfolg von Docker-Containern beruht auf einem anderen Aspekt: Docker hat eine Infrastruktur für Container-Images entwickelt, die es ermöglicht, Images von anderen Anwendern aus einem Repository zu laden und sie für eigene Zwecke anzupassen. Von Vorteil ist dabei der schichtweise Aufbau der Images, der mit Hilfe von Overlay-Dateisystemen einen schreibbaren Layer auf die bereits vorhandenen Schichten legt und somit Funktionalität hinzufügt. Dies spart potenziell Platz bei der Übertragung übers Netz sowie beim Speicher im lokalen Respository und beschleunigt die Entwicklung eigener Images. Docker gilt deshalb weniger als Virtualisierungslösung gehandelt, denn als neuer Weg, einfach und unkompliziert Anwendungen samt ihren Abhängigkeiten auszuliefern.

Vor kurzem ist Docker 1.13 erschienen und hat einiges über den Haufen geworfen, zum Beispiel das Commandline-Interface, das nun besser strukturiert ist. So folgt nun nach dem Docker-Befehl zuerst die Domäne, auf die sich der danach folgende Befehl bezieh. Um installierte Images aufzulisten, verwenden Sie also:

docker image ls

Analog funktioniert das mit Containern:

docker container ls

Die alten Subbefehle list funktionieren auch noch, werden aber vielleicht irgendwann abgeschafft. Auch docker ps zeigt noch die laufenden Container an - dieser Befehl war bisher Standard und ist noch in vielen Tutorials und bestimmt auch in der Docker-Dokumentation zu finden. Die folgende Tabelle stellt die wichtigsten der alten und neuen Aufrufe gegenüber:

Table 1. Neue Befehlssyntax in Docker 1.13

früher

Docker 1.13

docker attach

docker container attach

docker commit

docker container commit

docker exec

docker container exec

docker logs

docker container logs

docker inspect

docker {container,image} inspect

docker ps

docker container ls

docker rm

docker container rm

docker run

docker container run

docker start

docker container start

docker stop

docker container stop

docker top

docker container top

docker build

docker image build

docker history

docker image history

docker images

docker image ls

docker import

docker image import

docker pull

docker image pull

docker rmi

docker image rm

docker tag

docker image tag

docker deploy

docker stack deploy

docker events

docker system events

Um Docker zu installieren, greifen Sie am besten auf die Pakete der hinter der freien Software stehenden Firma zurück. Zwar liefern auch die Linux-Distributoren in ihren Repositories Docker aus, aber meist nur alte Versionen.

Laden Sie also etwa unter CentOS von der Docker-Downloadseite die Repository-Infos herunter und speichern sie unter /etc/yum.repos.d/docker.repo. Analog funktioniert das mit Fedora, Debian (7.7, 8.0, Testing) und Ubuntu (14.04, 16.04, 16.10). Auch für Windows und macOS gibt es Docker-Pakete, die zusätzlich zur Container-Umgebung noch VMs verwenden, in denen ein Container-Linux läuft. Die Windows-Version setzt Hyper-V voraus (also 64 Bit Win 10 Pro etc.), die macOS-Version bringt eine angepasste Variante des XHyve-Hypervisors mit.

Ist Docker installiert, läuft (auf Linux) im Hintergrund ein Daemon, der Befehle vom Commandline-Tool "docker" entgegennimmt. Der Docker-Daemon wird über das Init-System der Distribution gestartet, auf CentOS 7 und Ubuntu 16.10 also per Systemd:

# systemctl start docker
# ps auxw | grep docker
root     13356  4.0  0.1 572328 37132 ?        Ssl  16:40   1:20 /usr/bin/dockerd
root     13366  0.0  0.0 571056  8728 ?        Ssl  16:40   0:01 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc

Wie Sie sehen, läuft nicht nur der Docker-Daemon, sondern noch ein zweiter Daemon, der mit dem Docker-Daemon kommuniziert. Der Grund dafür ist, dass Docker immer stärker modularisiert wird. So griff Docker am Anfang auf LXC zurück, um damit über Cgroups und Namespaces Container zu erzeugen. In einer späteren Version wurde diese Funktion in der Libcontainer selbst implementiert. Als sich dann bei der Linux Foundation Arbeitsgruppen zur Standardisierung von Containern gründete, lagerte Docker sein Container-Runtime-Interface "runc" aus und spendierte es der Open Container Initiative.

Docker lässt sich ohne Root-Rechte als normaler User verwenden, wenn er der Gruppe "docker" angehört. Der Befehl "docker info" zeigt die Basisinformation der Docker-Installation an, die etwa den Storage Driver und das Runtime-Plugin (eben runc):

$ docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 1
Server Version: 1.13.0
Storage Driver: overlay
 Backing Filesystem: xfs
 Supports d_type: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
...

Wie erwähnt ist ein Grund für den Erfolg von Docker das gut gefüllte Repository, in dem sich Images beinahe jeder freien Software finden - die mehr oder weniger gut gepflegt werden. Um die Funktion von Docker zu demonstrieren, soll nur ein einfaches Image installiert werden, das den Umgang mit Images und Containern demonstriert, die minimalistische Linux-Distribution Alpine. Das Image laden Sie aus dem Repository mit dem folgenden Befehl herunter:

docker image pull alpine

(Es funktioniert auch noch der alte Aufruf docker pull …, aber in der neuen Version ist klarer, worum es geht.) Da das Image nur ein paar MByte groß ist, ist der Download schnell erledigt. Ein Aufruf von "docker image ls" zeigt, dass das Image nun im lokalen Repository vorhanden ist:

$ docker image ls
REPOSITORY     TAG         IMAGE ID        CREATED             SIZE
alpine         latest      88e169ea8f46    6 weeks ago         3.98 MB

Nun starten Sie von diesem Image einen Container:

docker container run -it alpine /bin/ash

Wenn Sie den Run-Befehl ausführen, ohne vorher das passende Image heruntergeladen zu haben, erledigt Docker das übrigens automatisch. Haben Sie den obigen Aufruf eingegeben, finden Sie sich in einer Root-Shell von Alpine Linux wieder.

Der obige Befehl ist nicht unbedingt die typische Art, einen Container starten, denn mit den Optionen -i und -t wird ein Pseudoterminal angelegt und die Standardeingabe geöffnet, also der Container in einem interaktiven Modus gestartet. Typischerweise werden Container, die mit Anwendungen fertig konfektioniert sind, stattdessen mit -d im Hintergrund gestartet. Außerdem steht in unserem Beispiel an letzter Stelle des Docker-Aufrufs noch der Befehl, der nach dem Start des Containers ausgeführt wird; das ist hier die Almquist Shell /bin/ash, die Standard-Shell von Alpine Linux. Anwendungscontainer haben normalerweise das auszuführende Kommando fest konfiguriert. Die obigen Aufrufoptionen werden allerdings typischerweise verwendet, um einen schon laufenden Container zu "betreten". Vorausgesetzt, es handelt sich um eine mehr oder weniger vollständige Linux-Distribution, dient dazu der folgenden Befehl:

docker container exec -it Containername/Hash /bin/bash

Doch zurück zu unserem Alpine-Beispiel: Führen Sie nun in einem anderen Terminal docker container ls aus, sehen Sie den Alpine-Container:

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES
ac882c4e7173        alpine              "/bin/ash"          6 minutes ago       Up 6  minutes                            sleepy_sinoussi

An erster Stelle steht ein (abgekürzt wiedergegebener) Hash, der den Container eindeutig identifiziert. An letzter Stelle ist der besser lesbare Name zu sehen, den Docker automatisch vergibt (hier "sleepy_sinoussi"). Stattdessen können Sie beim Start eines Container einen eigenen Namen vergeben:

docker container run --name alpine1 -it alpine /bin/ash

Wenn Sie in der Alpine-Shell mit ps die Prozesse anzeigen, sehen Sie neben dem ps-Befehl nur die Shell. Die Prozesse des Host-Systems sind dank der Namespaces verborgen, auch wenn Container und Host den gleichen laufenden Kernel verwenden.

Über den Namen oder den Hash, die Sie mit "docker container ls" erfahren, stoppen Sie den Container wieder:

docker container stop alpine1

Alternativ würde in unserem Beispiel auch das Verlassen der Shell dazu führen, dass der Container beendet wird. Wenn Sie nun den Container mit dem Namen noch einmal starten wollen, bekommen Sie eine Fehlermeldung präsentiert:

docker: Error response from daemon: Conflict. The container name "/alpine1" is already in use by container 1c08e339d0742b463b02b9ddcf0c776f7badb0e7e44036151db8a9fa31dd4a7d. You have to remove (or rename) that container to be able to reuse that name..

Der Grund dafür ist, dass Docker beendete Container aufbewahrt, sodass Sie sie später wieder starten können. Der Aufruf docker container ls -a zeigt alle – auch nicht mehr laufenden – Container an. Sie löschen einen Container mit docker container rm …, entweder gefolgt vom Namen oder dem Hash. Jetzt dürfen Sie einen neuen Container mit dem alten Namen starten. Sie können auch beim Start festlegen, dass Docker den Container nach dem Beenden löscht, indem Sie ihn mit der Option --rm starten:

docker container run --name alpine1 -it --rm alpine /bin/ash

Analog zum Löschen eines Containers löschen Sie ein Image aus dem Repository mit docker image rm …, gefolgt vom Hash oder von dem "Repository:Tag" (siehe oben die Ausgabe von docker image ls), mit dem das Image versehen ist. Im Beispiel von Alpine Linux sieht das so aus:

docker image rm alpine:latest

Besonders praktisch sind auch die neuen Prune-Kommandos, die beim Aufräumen helfen. So löscht docker container prune alle nicht mehr laufenden Container, während docker image prune -a alle Images löscht, von denen es keine Container mehr gibt.

Zum Abschluss noch eine kleines Bad-Practice-Beispiel, das die Fähigkeiten von Docker demonstriert. Haben Sie einen Alpine-Container gestartet und sind in der Root-Shell gelandet, aktualisieren Sie zunächst mit apk update die Paketquellen. Mit apk info zeigen Sie die installierten Pakete an. Installieren Sie nun mit apk add nginx den Nginx-Webserver, wechseln in ein anderes Terminal auf dem Docker-Host und geben den folgenden Befehl ein:

docker container commit alpine1 alpine-nginx:0.1

Wenn Sie jetzt mit docker image ls einen Blick in das lokale Repository werfen, finden Sie dort ihr erstes eigenes Image, das auf dem Alpine-Image basiert, aber zusätzlich den Nginx-Webserver enthält. Wie angedeutet, gilt dieser Weg nicht als Best Practice, da das interaktive Hinzufügen eines Pakets nur ein Ad-hoc-Verfahren ist, das nicht reproduzierbar ist und den Weg zum Image nicht dokumentiert. Um Docker-Images strukturiert zu erzeugen, hält Docker mit den sogenannten Dockerfiles einen besseren Weg bereit, mit denen sich dieses Workshop noch ausführlich beschäftigen wird. Allerdings demonstriert das Beispiel bereits, wie sich Docker-Image schichtweise zusammensetzen.

Mehr Informationen über die verfügbaren Befehle gibt das Docker-Kommando selbst mit docker help; docker Befehl --help verrät mehr zu einem einzelnen Befehl. Wer die Bash-Shell verwendet, kann sich die Arbeit mit einem von Docker geschrieben Autocompletion-File vereinfachen.

Im Docker-Repository sind die meisten anderen Linux-Distributionen in unterschiedlichen Versionen vorhanden, die mit dem Docker-Befehl herunterladen und damit experimentieren können. Wie gesagt ist das nicht der typische Einsatzzweck für Container, aber auf der anderen Seite sind Container auf diese Weise eingesetzt auch eine gute und schnelle Alternative zu den typischen Wegwerf-VMs, um eben mal etwas auszuprobieren.

In der zweiten Folge unseres Docker-Tutorials geht es darum, wie man mit Volumes flüchtige Container mit dauerhaften Nutzerdaten verwendet.

Ähnliche Artikel

comments powered by Disqus
Mehr zum Thema

Docker-Workshop (2): Volumes

Die letzte Folge unseres Docker-Workshops hat beschrieben, wie Sie Docker instalieren und Container starten. Um größeren Nutzen aus Docker zu ziehen, fehlen noch zwei Dinge: eine Netzwerkverbindung und eine Möglichkeit, Nutzdaten dauerhaft zu speichern.

Artikel der Woche

Container-Anwendungen isolieren mit Aporeto Trireme

Beim Umstieg auf Container-basierte Anwendungen sind viele Klippen zu umschiffen, dies gilt insbesondere für das Thema Security. So lassen sich Anwendungen nur schwer voneinander kontrollierbar isolieren. Hier setzt Aporeto mit Trireme an. Die Software sorgt dank einer attributbasierten Zugriffskontrolle für mehr Sicherheit. Wir stellen das Konzept anhand eines Beispiels vor. (mehr)
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Linux-Backup

Welche Backup-Lösung setzen Sie für Linux im professionellen Umfeld ein?

  • keine
  • eigene Scripts
  • rsnapshot
  • rdiff-backup
  • Bacula
  • Bareos
  • Borg
  • Duplicity
  • Amanda
  • Burp
  • Clonezilla
  • Acronis
  • Arkeia
  • SEP sesam
  • Veeam
  • Redo Backup
  • Relax-and-Recover
  • andere kommerzielle Software
  • andere Open-Source-Software

Google+

Ausgabe /2017

Microsite