Wer Programme in der weiten Welt zu verteilen gedenkt, sollte sie vorab fit für fremde Kulturen machen. Um seine selbst verfassten Perl-Module auf mehreren Linux-Distributionen in einem Rutsch zu testen, schnappt sich Perlmeister Schilli die Linux-Containers-Technik und das ressourcenschonende Docker-Projekt.
Virtualisierung als Allheilmittel? Nein! 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, [2]) auf und isoliert diese auf Prozessebene. Für Container statt für die klassische Virtualisierung sprechen erhebliche Einsparungen beim Speicherbedarf und beträchtliche Performancegewinne. Wenn auf jedem Server mühelos ein Vielfaches an voneinander abgekapselten Applikationen läuft, eröffnen sich ganz neue Möglichkeiten im Rechenzentrum.
Wie bei echter Virtualisierung entkoppeln isolierte Container ihre Komponenten voneinander. Der Vorteil: Benutzen zum Beispiel zwei Applikationen die gleiche Library, diese aber in unterschiedliche Versionen, ist dies kein Hinderungsgrund, denn jeder Container bringt alles Notwendige selbst mit.
Das Docker-Projekt [3] setzt auf den LXC-Features neuerer Linux-Kernels auf und fährt einen Daemon hoch, der alle Docker-Container verwaltet [4]. Er läuft auf dem Hostsystem genauso wie in einer VM. Wer also noch ein älteres System betreibt, das Docker noch nicht im Kernel unterstützt, erzeugt einfach mit Vagrant [5] beispielsweise ein Ubuntu-13-Image und installiert dort Docker mit den Anweisungen aus [6].
Docker-Container bieten viel mehr als LXC allein, angefangen von einem standardisierten Applikationsformat, das es erlaubt, auf einem Linux-System definierte Container auf jedem anderen Docker-System zu benutzen, bis hin zu geschichteten Dateisystemen. Dabei setzt eine Applikation auf einem Base-Image auf, Docker definiert nur die Differenzen und versioniert sie angenehmerweise mit Git-ähnlicher Präzision.
Docker definiert zudem eine Netzwerkschnittstelle zwischen den Containern, sodass die darin laufenden Applikationen über Unix-Ports miteinander kommunizieren können. Dass User einmal definierte Docker-Container in einem öffentlichen oder privaten Repository zur Nachnutzung ablegen dürfen, mehrt die Akzeptanz in der Docker-Community zusätzlich. Das Projekt ist gut ein Jahr alt und die Software laut seinen Entwicklern seit Version 0.8 produktionsreif.
Dieser Perl-Snapshot macht sich die Docker-Technik für Tests an selbst geschriebenen CPAN-Modulen zu Nutze. Dazu wird er mehrere Container mit verschiedenen Linux-Distributionen anlegen, um dann automatisch zu testen, ob sich der Tarball eines CPAN-Moduls darauf installieren lässt.
Online PLUS
In einem Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/2014/05/plus
Wohncontainer mit Inneneinrichtung
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 docker run -i -t ubuntu /bin/sh
ein etwa 200 MByte großes Ubuntu-Image aus dem Netz und öffnet dann eine Shell im Container. In ihr darf der User mit »apt-get update« und »apt-get install« nach Herzenslust neue Ubuntu-Pakete installieren. Die Option »-i« legt den interaktiven Modus fest, »-t« öffnet ein Pseudoterminal zum Bedienen der Shell.
Um Container hingegen maschinell einzurichten, gibt es die Datei »Dockerfile« , wie Listing 1 eine als Beispiel zeigt. 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 Images zutage, die zum Download bereitstehen.
Die mit dem Schlüsselwort »RUN« eingeleiteten Zeilen im Dockerfile schicken Kommandos, mit denen »docker« den Container anpassen wird. Nach einem »apt-get update« zum Auffrischen des Paketindex installieren die Zeilen 5 und 6 im Perltest-Container die Pakete »libwww-perl« zum Herunterladen von CPAN-Modulen und das Utility »make« zum Bauen und Testen derselben.
Listing 1
ubuntu/Dockerfile
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
CPAN minus als Plus
Das Paket »cpanminus« aus Zeile 4 bringt das Utility »cpanm« mit, das nicht nur CPAN-Module testet und installiert, sondern auch Distributions-Tarbälle entpackt und deren Unit-Testsuite 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 darf.
Alle Container, mit denen dieser Snapshot rangiert, installieren dieses Utility entsprechend den Erfordernissen der jeweiligen Linux-Distribution. Damit kann das Testskript, das den Tarball in allen konfigurierten Containern testet, dort jeweils ein einheitliches Kommando aufrufen.
Auto-Yes
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 Pseudoterminal, führen Rückfragen automatisch zu einem Fehler und zum Abbruch des Kommandos. So fragt Ubuntu-»apt-get« den Superuser stets mit »Do you want to continue [Y/n]?« , ob er die Installation eines angeforderten Pakets denn mit »y« wirklich befürwortet. Das »Dockerfile« in Listing 1 ruft »apt-get« deshalb mit der Option »-y« auf, das die Abfrage unterbindet und 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 Repository. In der Distribution heißt der Paketmanager »pacman« , er installiert Pakete normalerweise nur, 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, ihm fehlen aber 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« . In Arch Linux sucht die Shell genau dort nach Kommandos, was die Plattformparität sicherstellt.
Listing 2
arch/Dockerfile
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
Einrichtung festzementiert
Die zwei bislang vorgestellten Container-Konfigurationen zum Testen von Perl-Modulen (und etwaige weitere) erwartet das Skript »conprep« zur Containervorbereitung (Listing 3) nun unterhalb des Verzeichnisses »containers« jeweils in einem Unterverzeichnis mit dem Namen der Distribution (»arch« , »ubuntu« , siehe Abbildung 1). Es springt sogleich ins Verzeichnis mit dem »Dockerfile« der Distribution 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 wirklich Schritt für Schritt passiert und Docker keine Abkürzung über eventuell auf dem Host zwischengespeicherte Images von früheren Installationen nimmt. Nun holt Docker das entsprechende Image aus dem Repository und führt die angegebenen Kommandos der Reihe nach aus.
Listing 3
conprep
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 }
Container suchen
Damit aus einem heruntergeladenen Image ein Container wird, muss »docker run« laufen, was implizit dank der im »Dockerfile« aufgelisteten »RUN« -Direktiven passiert. Falls sich dort keine befinden, würde beispielsweise der Aufruf »docker run -i -t ubuntu ls« dafür sorgen, dass tatsächlich ein Container entsteht, »docker« nach der Ausführung des »ls« -Kommandos aus dem Container rausspringt und die Kontrolle zur Shell des Hosts zurückgibt.
Aber wo sind die neuen Container? Die Ausgabe der Docker-Kommandos verrät darüber überraschenderweise nichts, sie liefert nur die Image-IDs. Die Container-IDs braucht man aber, um (über einen Umweg) in den Container einzusteigen und dort Kommandos auszuführen. Zu Hilfe kommt hier das Kommando »docker ps« , das alle laufenden Container mit ihren IDs anzeigt, mit der Option »-l« gibt es nur den zuletzt erzeugten an (Abbildung 2).
Die Hex-Zahl in der ersten Spalte ist die ID des Containers. Sie zu wissen, reicht ebenfalls nicht, um in den Container hineinzuspringen. Vielmehr besteht Docker darauf, dass das Kommando »docker commit ID Name« diese ID nun festzimmert und der Version einen Namen zuweist. Erst mit ihm kann der User per
docker run -i -tName /bin/sh
in den Container einsteigen. Das Skript in Listing 3 vollführt den vorgeschriebenen Regentanz und weist jedem neuen Container den Namen »distname-perltest« zu, wobei »distname« für den Namen der verwendeten Distribution steht, »ubuntu« beispielsweise.
Zeile 20 trennt die Ausgabezeilen des über »tap()« ausgeführten Kommandos »docker ps -l« , Zeile 22 schnappt sich die zweite und die letzte Zeile und extrahiert die am Zeilenanfang stehende Hex-ID. Das Commit-Kommando in Zeile 25 zementiert die Version unter dem angegebenen Namen.
Die Funktion »cdback« (auch sie kommt aus Sysadm::Install) am Ende der For-Schleife springt zurück ins ursprüngliche Verzeichnis, damit dem nächsten »cd« -Befehl in ein relativ angegebenes Verzeichnis Erfolg beschieden ist.
Nach getaner Arbeit steht für jede Distribution ein Container bereit. Abbildung 3 zeigt, wie Docker zum Beispiel eine Shell im Arch-Linux-Container öffnet und die installierte Version bestätigt.
Rauch steigt auf aus den Containern
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 Distributions-Verzeichnisse. Im Unterschied zu diesem prüft es aber, ob sich das Modul in dem jeweiligen Linux installieren lässt.
Bleibt zu klären, wie der Tarball des zu testenden Perl-Moduls vom Hostsystem in den Container gelangt: Mit der Option
docker run -v "/dir1:/dir2" -i -t cmd
aufgerufen erzeugt Docker im Container einen Mount »/dir2« , der auf das Verzeichnis »/dir1« im Hostsystem zeigt. Das Skript in Listing 4 kopiert den Tarball auf dem Hostsystem zunächst mit der »cp« -Funktion aus Sysadm::Install in ein frisch angelegtes temporäres Verzeichnis. Dann teilt es Docker mit, dass es Letzteres unter »/mnt/tmp« im Container anzutreffen wünscht – und prompt findet »cpanm« später im Container genau dort den richtigen Tarball.
Die Option »-v« des Skripts (für Verbose) schnappt Zeile 29 auf und reicht sie zum »cpanm« -Aufruf im Container durch. Die sich anschließende Funktion »sysrun« (ebenfalls aus Sysadm::Install) sorgt dafür, dass das Skript die Ergebnisse der durchlaufenden Tests in Echtzeit anzeigt. Abbildung 4 exerziert den Ablauf der Tests in zwei verschiedenen Containern ohne »-v« durch, also in der weniger gesprächigen Ausprägung.
Offensichtlich traten keine Probleme auf, was das Modul erfolgreich für zwei Linux-Distributionen zertifiziert. Noch ein Trick: Wenn sich zu viele gar nicht mehr laufende Docker-Container angehäuft 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 nicht laufenden Container ein und schiebt diese zeilenweise dem Löschkommando unter.
Listing 4
smoke-me
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 }
Lerne Go und wirke mit
Unter Ubuntu 12.10, 13.04 und 13.10 installiert sich der Docker-Service ganz einfach mit diesem Befehl:
sudo apt-get install lxc-docker
Allerdings kommuniziert der Client mit dem nach einem Reboot des Rechners funktionsfähigen Docker-Daemon (Stand: Version 0.8.1) über den Root gehörenden Linux-Socket »/var/run/docker.sock« . Der Anwender steht deshalb vor der Wahl, 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 akzeptablere Eigentumsverhältnisse ermöglichen, was zugegebenermaßen allerdings nicht ganz trivial realisierbar sein wird.
Insgesamt ist Docker wohl eines der derzeit heißesten Projekte auf Github, da die Pseudo-Virtualisierung deutliche Performancegewinne bei gleichzeitiger Komponenenten-Entkopplung verspricht. Wer mitwirken möchte, muss die Programmiersprache Go [7] lernen, denn Docker ist darin abgefasst.
Infos
- Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2014/05/Perl
- Eva-Katharina Kunst, Jürgen Quade, “Container-Virtualisierung mit LXC auf einem Ubuntu 10.04”: Linux-Magazin 02/11, S. 82
- Docker: http://docker.io
- Rob Knight, “Linux Containers verwalten mit Docker”: Linux-Magazin 08/13, S. 64
- Michael Schilli, “Frischmacher – Perl-Skript hält den PC mit virtuellen Maschinen spurenfrei sauber”: https://www.linux-magazin.de/Ausgaben/2011/10/Perl-Snapshot
- Docker-Installation auf Ubuntu: http://docs.docker.io/en/latest/installation/ubuntulinux/
- Marcus Nutzinger, Rainer Poisel, “Google erfindet neue Programmiersprache”: Linux-Magazin 06/10, S. 118









