Das brandheiße Docker-Projekt fährt gleichzeitig mehrere Linux-Distros auf einem Rechner ohne den Überhang voller Virtualisierung und erlaubt es dem Perlmeister, seine Module plattformübergreifend zu testen.
Virtualisierung war gestern. Statt die gesamte Hardware samt Operationssystem zu abstrahieren, baut das Docker-Projekt auf den in neueren Linux-Kerneln vorhandenen Support für Linux-Container (LXC) auf und isoliert diese auf Prozessebene. Verkaufsargumente sind drastische Einsparungen beim verbrauchten Speicher und Performance-Gewinne um Größenordnungen. Wenn jeder Rechner plötzlich mühelos tausende voneinander abgekapselte Applikationen laufen lassen kann, eröffnen sich ganz neue Möglichkeiten im Rechenzentrum.
Das Docker-Projekt (http://docker.io und [2]) setzt auf den LXC-Features neuerer Linux-Kernels auf und fährt einen Dämon hoch, der alle Docker-Container verwaltet. Er läuft auf dem Host-System, aber gerne auch in einer VM. Wer also noch ein älteres System fährt, das Docker noch nicht im Kernel unterstützt, kann einfach mit Vagrant ([3]) zum Beispiel ein Ubuntu-13-Image erzeugen und dort Docker mit den Anweisungen aus [4] installieren.
Im Perl-Snapshot testen verschiedene Container heute, ob sich der Tarball eines CPAN-Moduls auf verschiedensten Linux-Distos installieren lässt.
Docker-Container bieten viel mehr als LXC allein, angefangen von einem standartisierten Applikationsformat, das es erlaubt, auf einem Linux-System definierte Container auf allen anderen Systemen laufen zu lassen, die ebenfalls Docker unterstützen. Weiter arbeitet Docker mit geschichteten Dateisystemen, sodass eine Applikation auf einem Base-Image aufsetzt, und nur die Differenzen definiert, die Docker auch noch mit Git-ähnlicher Präzision versioniert. Docker definiert auch noch eine Netzwerkschnittstelle zwischen den Containern, so dass die darin laufenden Applikationen über Unix-Ports miteinander kommunizieren können. Und dass User einmal definierte Docker-Container in einem öffentlichen (oder wahlweise auch privaten) Repositories zur Weiterverwendung ablegen können, gibt der Docker-Community eine gute Portion Schwung. Das Projekt ist gut ein Jahr alt und seit Version 0.8 laut Entwicklern produktionsreif.
Der Hauptvorteil isolierter Container ist freilich die Entkoppelung der Komponenten. Nutzen zwei Applikationen zum Beispiel die gleiche Library, brauchen aber unterschiedliche Versionen, ist dies kein Hinderungsgrund, denn jeder Container bringt alles notwendige selbst mit.
Einen neuen Container holt der User zunächst
als Basisversion aus dem öffentlichen Repository und steigt dann hinein,
um ihn entsprechend den Anforderungen einzurichten. So angelt
docker pull ubuntu
ein etwa 200MB großes Ubuntu-Image vom Netz und
# docker run -i -t ubuntu /bin/sh
öffnet dann eine Shell im Container, in der der User mit "apt-get update"
und "apt-get install" nach Herzenslust neue Ubuntu-Pakete installieren kann.
Die Option -i
legt den interaktiven Modus fest, -t
öffnet ein
Pseudo-Terminal zum Bedienen der Shell. Zur maschinellen Erstellung von
Containern hingegen dient eine Datei namens Dockerfile
wie in Listing 1.
1 FROM ubuntu 2 3 RUN apt-get -y update 4 RUN apt-get -y install cpanminus 5 RUN apt-get -y install make 6 RUN apt-get -y install libwww-perl
Sie legt mit der Direktive FROM
fest, von welchem Basis-Image der
Container abzuleiten ist (in diesem Fall ubuntu
). Neben dem Standard-Image
fördert das Kommando docker search ubuntu
noch 683 weitere
Variationen zutage, die ebenfalls zum Download bereitstehen.
Die mit dem Schlüsselwort RUN
eingeleiteten Zeilen im Dockerfile
geben Kommandos ein, mit denen docker
den Container anpassen soll.
Nach einem apt-get update
zum Auffrischen des Paket-Index installieren
die Kommandos im Perltest-Container noch die Pakete libwww-perl
zum
Herunterladen von CPAN-Modulen und die Utility make
zum Bauen
und Testen derselben.
Das Paket cpanminus
bringt die Utility cpanm
mit,
die nicht nur CPAN-Module testen und installieren kann, sondern auch
Distributions-Tarbälle entpackt und deren Unit-Test-Suite startet.
Das ist nicht immer trivial,
denn Perl-Module definieren oft Abhängigkeiten zu anderen
CPAN-Modulen, die erst einmal eingeholt, getestet und installiert werden
müssen bevor die Installation des eigentlichen Moduls beginnen kann.
Alle heute vorgestellten Container installieren diese Utilty entsprechend
den Erfordernissen der dort laufenden Linux-Distribution, damit das
Testskript, das den Tarball in allen konfigurierten Containern testet,
dort jeweils ein einheitliches Kommando aufrufen kann.
1 FROM base/arch 2 3 RUN pacman -S --noconfirm tar make community/cpanminus 4 RUN ln -s /usr/bin/vendor_perl/cpanm /usr/bin/cpanm
Wichtig ist, dass die im Container ausgeführten Kommandos keinerlei
Rücksprache mit dem User halten. Besteht nämlich
keine interaktive Verbindung mit dem Container durch ein Pseudo-Terminal,
führen Rückfragen automatisch zu einem Fehler und zum Abbruch des Kommandos.
So frägt die Utilty apt-get
auf Ubuntu zum Beispiel immer mit
"Do you want to continue [Y/n]?"
nach, bevor sie zur Installation eines
angeforderten Pakets schreitet, und erwartet, dass der User y
eingibt.
Das Dockerfile
in Listing 1
ruft apt-get
deshalb mit der Option -y
auf, das
die Abfrage unterbindet und stattdessen gleich loslegt.
Das Dockerfile
in Listing 2 hingegen definiert den Inhalt des
Perltest-Containers für Arch Linux.
Dessen Docker-Image liegt unter base/arch im öffentlichen Repo.
In der Distro heißt der Paket-Manager
pacman
und er
führt die Installation von Paketen normalerweise nur aus, wenn der
User auf die Frage "Proceed with installation? [Y/n]" mit "y"
antwortet. Mit der Option --noconfirm
geht er stattdessen gleich ans
Werk.
Zwar kommt Arch Linux von Haus aus mit einem Perl-Binary daher, aber es
fehlen die Utilities tar
und make
. Das cpanm
-Skript ist im
Arch-Paket community/cpanminus
enthalten, also zieht das Dockerfile
diese über pacman
beim Einrichten des Containers heran.
Allerdings installiert Arch Linux das Skript cpanm
im Pfad
/usr/bin/vendor_perl
, der sich nicht in der $PATH
-Variablen der
Shell findet. Da das Testskript später
aber cpanm
ohne Pfadangabe aufruft,
erzeugt das zweite RUN
-Kommando mit ln -s
kurzerhand einen
symbolischen Link unter /usr/bin
. Dort sucht die Shell unter
Arch Linux standardmäßig nach Kommandos, und somit ist Plattformparität
sicher gestellt.
Abbildung 1: Jedes Plattform-Verzeichnis enthält ein C |
Die zwei bislang vorgestellten Container-Konfigurationen zum Testen von
Perl-Modulen (und etwaige weitere)
erwartet das Skript conprep
zur Containervorbereitung
in Listing 3 nun unterhalb des
Verzeichnisses containers
jeweils in einem Unterverzeichnis mit dem
Namen der Distro ("arch" bzw. "ubuntu", siehe Abbildung 1). Es
springt nach dem Aufruf ins Verzeichnis mit dem Dockerfile
der Distro und ruft dort das Kommando
# docker build --no-cache .
auf. Der abschließende Punkt steht für das aktuelle
Verzeichnis, in dem sich die festgezimmerte Konfiguration befindet.
Die Option --no-cache
bestimmt, dass dies auch wirklich Schritt für
Schritt passiert und Docker keine Abkürzung über eventuell schon auf
dem Host zwischengespeicherten Images von früheren Installationen nimmt.
Dabei holt Docker jeweils das entsprechende Image aus dem Repo und führt die
angegebenen Kommandos der Reihe nach aus.
Damit aus einem heruntergeladenen
Image ein Container wird, muss docker run
laufen, was implizit mit den
im Dockerfile
aufgelisteten RUN-Direktiven passiert. Falls sich dort
keine befänden, würde der beispielsweise
der Aufruf docker run -i -t ubuntu ls
dafür sorgen,
dass tatsächlich ein Container entsteht und docker
nach der Ausführung
des ls
-Kommandos aus dem Container herausspringt und die Kontrolle zur
Shell des Hosts zurückgibt.
Aber wo findet sich nun der entstandene Container?
Die Ausgabe der Docker-Kommandos
verrät darüber überraschenderweise nichts, dort stehen nur die Image-IDs, nicht
die Container-IDs, die man braucht, um in letztere einzusteigen und dort
Kommandos auszuführen. Zu Hilfe kommt das Kommando docker ps
, das alle
laufenden Container mit ihren IDs anzeigt, und mit der Option -l
gibt es
nur den als letzten erzeugten an (Abbildung 2).
Abbildung 2: Das Kommando C |
Die Hex-Zahl in der ersten Spalte ist die ID des Containers. Diese genügt
aber ebenfalls nicht, um in den Container hinein zu springen. Vielmehr besteht
Docker darauf, dass diese ID nun mit dem Kommando docker commit id name
festgezimmert und einem Namen zugewiesen wird. Erst mit diesem Namen kann
der User dann mit
docker run -i -t name /bin/sh
in den Container einsteigen. Das Skript in Listing 1 vollführt den
vorgeschriebenen Regentanz und weist jedem neuen Container den Namen
distname-perltest
zu, wobei distname
für den Namen der
verwendeten Distro (z.B. "ubuntu") steht.
01 #!/usr/bin/perl -w 02 use strict; 03 use Sysadm::Install qw(:all); 04 use Log::Log4perl qw( :easy ); 05 use File::Basename; 06 use Getopt::Std; 07 08 Log::Log4perl->easy_init($DEBUG); 09 10 for my $container_path ( <containers/*> ) { 11 cd $container_path; 12 my $container = basename $container_path; 13 14 tap { raise_error => 1 }, 15 "docker", "build", "--no-cache", "."; 16 17 my( $stdout, $stderr, $rc ) = 18 tap "docker", "ps", "-l"; 19 20 my @lines = split /\n/, $stdout; 21 22 if( $lines[1] =~ /^(\w+)/ ) { 23 my $container_id = $1; 24 tap { raise_error => 1 }, 25 "docker", "commit", $container_id, 26 "$container-perltest"; 27 } else { 28 die "unexpected format: @lines"; 29 } 30 31 cdback; 32 }
Zeile 20 trennt die Ausgabezeilen
des über tap()
ausgeführten Kommandos docker ps -l
, und Zeile
22 schnappt sich die zweite und letzte Zeile und extrahiert die am
Zeilenanfang stehende Hex-ID. Das Commit-Kommando in Zeile 25 zimmert
die Version unter dem
angegebenen Namen fest. Die Funktion cdback
(ebenfalls aus Sysadm::Install)
springt am Ende der for
-Schleife
wieder zurück ins ursprüngliche Verzeichnis, damit das nächste
cd
-Befehl in ein relativ angegebenes Verzeichnis erfolgreich vonstatten
geht.
Nach getaner Arbeit steht nun für jede Distro ein Container zur Verfügung. Abbildung 3 zeigt, wie Docker zum Beispiel eine Shell im Arch-Linux-Container öffnet und die installierte Version bestätigt.
Abbildung 3: Der User öffnet eine Shell im Arch-Linux-Container und verifiziert die Distro-Version. |
Das Skript smoke-me
in Listing 4 nimmt als Argument den Tarball
eines Perl-Moduls (entweder vom CPAN heruntergeladen oder per
"make tardist" erzeugt) und orgelt wie vorher conprep
durch alle
Distro-Verzeichnisse, prüft aber, ob sich das Modul problemlos
in der jeweiligen Distro installieren lässt.
01 #!/usr/bin/perl -w 02 use strict; 03 use Sysadm::Install qw(:all); 04 use Log::Log4perl qw( :easy ); 05 use File::Basename; 06 use Getopt::Std; 07 use File::Temp qw( tempdir ); 08 09 getopts( "v", \my %opts ); 10 11 my( $tarball_path ) = @ARGV; 12 die "usage: $0 tarball" if 13 !defined $tarball_path; 14 15 my $tempdir = tempdir( CLEANUP => 1 ); 16 cp $tarball_path, $tempdir; 17 my $tarball = basename $tarball_path; 18 19 my $log_level = 20 ( $opts{ v } ? $DEBUG : $INFO ); 21 22 Log::Log4perl->easy_init( $log_level ); 23 24 for my $container_path ( <containers/*> ) { 25 cd $container_path; 26 my $container = basename $container_path; 27 28 my @verbose = 29 ( $opts{ v } ? ("-v") : () ); 30 31 my $rc = 32 sysrun "docker", "run", "-i", 33 "-v", "$tempdir:/mnt/tmp", 34 "$container-perltest", 35 "cpanm", @verbose, "/mnt/tmp/$tarball"; 36 37 INFO "Test in container '$container' ", 38 ( $rc ? "failed" : "OK" ); 39 40 cdback; 41 }
Wie aber gelangt nun der Tarball des zu testenden Perl-Moduls vom Host-System in den Container? Mit der Option
docker run -v "/dir1:/dir2" -i -t cmd
aufgerufen erstellt Docker im Container einen Mount /dir2
, der auf
das Verzeichnis /dir1
auf dem Hostsystem zeigt. Das Skript in
Listing 4 kopiert den Tarball auf dem Hostsystem zunächst mit
der Funktion "cp"
(aus Sysadm::Install) in ein neu angelegtes
temporäres Verzeichnis und teilt dann Docker mit, dass es letzteres
unter /mnt/tmp
im Container vorzufinden wünscht. Prompt findet
cpanm
später im Container dort den Tarball. Die Option "-v" (für verbose)
des Skripts schnappt Zeile 29 auf und reicht sie zum Aufruf von cpanm
im Container durch. Die Funktion sysrun
(ebenfalls aus Sysadm::Install)
sorgt dann dafür, dass die Ergebniss der durchlaufenden Tests in
Echtzeit angezeigt werden. Abbildung 4 zeigt den Ablauf der Tests
in zwei verschiedenen Containern in der weniger gesprächigen Version
ohne -v
.
Abbildung 4: Der Tarball des Log4perl-Moduls absolviert seine Testsuite in den Arch-Linux- und Ubuntu-Containern. |
Offensichtlich traten keine Probleme auf, und das Modul wurde
erfolgreich auf zwei Linux-Distros zertifiziert.
Noch ein Trick:
Wenn sich zuviele gar nicht mehr laufende Docker-Container angesammelt haben
(docker ps -a
zeigt sie an), dann hilft etwas Unix-Foo auf der
Kommandozeile: Mangels einer entsprechenden Option für docker rm
sammelt
docker rm `docker ps -notrunc -a -q`
erst die IDs aller nichtlaufenden Container ein und schiebt diese dann zeilenweise dem Löschkommando unter.
Unter Ubuntu 12.10/13.04/13.10 installiert sich der Docker-Service ganz einfach mit
sudo apt-get install lxc-docker
Allerdings kommuniziert der Client mit dem nach einem Reboot des
Rechners funktionsfähigen Docker-Dämon (Stand Version 0.8.1)
über einen Root gehörenden
Linux-Socket (/var/run/docker.sock
), sodass der Anwender vor der Wahl
steht, die Docker-Kommandos alle
als Root abzusetzen oder alle Sicherheitsbedenken
über Bord zu werfen und aller Welt Schreibrechte auf dem Socket einzuräumen.
Langfristig sollte Docker vielleicht ausgefeiltere Eigentumsverhältnisse
ermöglichen, was allerdings zugegebenermaßen nicht ganz trivial werden
wird.
Insgesamt ist Docker wohl eines der derzeit heißesten Projekte auf Github, da die billige Pseudo-Virtualisierung deutliche Performancegewinne bei gleichzeitiger Komponenenten-Entkopplung verspricht. Wer mitwirken möchte, muss die neue Programmiersprache "Go" lernen, denn Docker ist darin abgefasst.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2014/05/Perl
"Volle Ladung", Rob Knight, Linux-Magazin 08/2013
"Frischmacher", Perl-Skript hält den PC mit virtuellen Maschinen spurenfrei sauber, Michael Schilli, http://www.linux-magazin.de/Ausgaben/2011/10/Perl-Snapshot
Docker-Installation auf Ubuntu, http://docs.docker.io/en/latest/installation/ubuntulinux/