Läuft im gerade auf den Schild gehobenen Docker-Container PHP 7.1 oder doch noch eine ältere Version? Und nutzt MySQL im Datenbank-Container tatsächlich die benötigte Konfiguration? Fragen, die ein neues Testwerkzeug von Google dem Containerbetreuer schnell beantwortet.
Will ein Zollbeamter wissen, ob der Frachter aus Brasilien tatsächlich Bananen an Bord hat, wirft er einfach einen prüfenden Blick in den entsprechenden Container. Nach dem gleichen Prinzip arbeitet auch Googles Testwerkzeug mit dem etwas sperrigen Namen Container Structure Tests (CST, [1]): Das Tool betritt einen vorgegebenen Docker-Container und führt dort verschiedene vom Nutzer vorgegebene Prüfungen aus.
Die Ergebnisse der einzelnen Checks protokolliert das Werkzeug dann ausführlich auf der Kommandozeile. Auf diese Weise lässt sich unter anderem automatisiert sicherstellen, dass ein Container tatsächlich MySQL mitbringt und keine faulen Bananen.
Kleiner Vierbeiner
CST erlaubt derzeit vier verschiedene Testarten. Zunächst inspizieren Entwickler mit ihm das Dateisystem. So finden sie heraus, ob die Konfigurationsdatei für die Datenbank am richtigen Ort liegt und über die passenden Zugriffsrechte verfügt. Daneben wirft das Tool auf Wunsch einen Blick in Textdateien und sucht dort nach vorgegebenen Zeichenketten.
Die nutzergenerierten Tests überprüfen beispielsweise, ob die Konfigurationsdatei der Datenbank genügend Pufferspeicher reserviert. Bei Bedarf rufen die Container Structure Tests im Docker-Container zudem ein beliebiges Kommando auf und untersuchen dann dessen Ausgaben. Auf diesem Wege ließe sich etwa der Paketmanager befragen, ob ein ganz bestimmtes Paket installiert ist.
Abschließend prüft Googles Werkzeug auf Wunsch auch Konfiguration und Metadaten des Containers. So lässt sich etwa sicherstellen, dass der Betreiber im Container alle notwendigen Umgebungsvariablen korrekt gesetzt hat.
Mit den passenden Tests sorgt die Testsuite schon vor der Inbetriebnahme eines neuen Docker-Containers dafür, dass dieser die benötigte beziehungsweise vorgeschriebene Spezifikation einhält. Bringt etwa ein vorkonfektionierter MySQL-Container aus dem Docker Hub plötzlich eine neue Version der Datenbank mit, fällt dies Googles Werkzeug sofort auf – und nicht erst im Betrieb, wenn die Webanwendung aufgrund des geänderten API plötzlich mysteriöse Fehlermeldungen liefert.
Die Container Structure Tests sorgen so nebenbei auch für einen robusteren Betrieb der kompletten Infrastruktur. Das Google-Werkzeug lässt sich zudem leicht in einen vorhandenen Workflow einbinden und automatisch anwerfen – beispielsweise nach jeder Aktualisierung eines Containers.
Bereits laufende Docker-Container nimmt Googles Werkzeug allerdings nicht unter die Lupe. Zudem hängt die Qualität der Prüfungen stark von den hinterlegten Tests ab. Vergisst der Entwickler etwa, die MySQL-Version zu kontrollieren, bemeckert das Testwerkzeug auch keine veraltete Datenbank.
Kommandantur
Zwar spricht Google von einem Test-Framework, tatsächlich bestehen die Container Structure Tests aber nur aus einem in Go geschriebenen Kommandozeilenwerkzeug. Es führt alle vorgesehenen Tests aus und liefert am Ende ein Prüfprotokoll zurück. Der Quellcode untersteht der Apache-Lizenz 2.0 und ist auf Github [1] hinterlegt.
Für 64-Bit-Systeme steht das fertig übersetzte Werkzeug auf den Google-Servern bereit [2]. Dieses Programm muss der Nutzer lediglich herunterladen und ausführbar machen, eine Installation ist nicht erforderlich. Wer das Tool systemweit nutzen möchte, dem empfehlen die Google-Entwickler die Installation über den Einzeiler aus Listing 1.
Listing 1
Container Structure Tests
01 curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && \ 02 chmod +x container-structure-test-linux-amd64 && \ 03 sudo mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test
Alle nachfolgenden Beispiele gehen davon aus, dass das Testwerkzeug den Dateinamen »container-structure-test« trägt und über den Suchpfad erreichbar ist.
Lieferscheine
Die durchzuführenden Tests notieren Entwickler in einer Textdatei, die wahlweise der Yaml- oder Json-Notation folgt. Hierdurch lassen sich die Tests sogar von weiteren Tools oder Skripten generieren beziehungsweise ändern. Ein einfaches Test-Beispiel in Yaml-Notation zeigt Listing 2. Es prüft erst, ob im Container der MySQL-Server installiert ist. Dann knöpft es sich die zugehörige Konfigurationsdatei vor, die am passenden Ort liegen und eine spezielle Einstellung enthalten muss. Alle mit einem Hash-Symbol »#« startenden Zeilen ignoriert »container-structure-test« später.
Listing 2
Beispielkonfiguration für einen MySQL-Container
01 schemaVersion: "2.0.0" 02 03 globalEnvVars: 04 - key: "MYSQL_USER" 05 value: "dev" 06 - key: "MYSQL_PASSWORD" 07 value: "123456" 08 09 commandTests: 10 11 # Ist das MySQL-Paket installiert? 12 - name: "MySQL-Paket installiert" 13 command: "rpm" 14 args: ["-q", "mysql-community-server-minimal"] 15 expectedOutput: ["mysql-community-server-minimal*"] 16 17 fileExistenceTests: 18 19 # Ist der MySQL-Client im Container vorhanden? 20 - name: "mysql Client existiert" 21 path: '/usr/bin/mysql' 22 shouldExist: true 23 24 # Existiert die MySQL-Konfigurationsdatei am richtigen Ort? 25 - name: "my.cnf existiert und hat passende Zugriffsrechte" 26 path: '/etc/my.cnf' 27 shouldExist: true 28 permissions: '-rw-r--r--' 29 30 fileContentTests: 31 32 # Besitzt die Konfigurationsdatei my.conf den richtigen Inhalt? 33 - name: 'Inhalt von my.cnf' 34 path: '/etc/my.cnf' 35 expectedContents: ["datadir=/var/lib/mysql\n"] 36 37 metadataTest: 38 env: 39 - key: "PATH" 40 value: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 41 exposedPorts: ["3306", "33060"] 42 volumes: ["/test"] 43 entrypoint: [/entrypoint.sh] 44 cmd: ["mysqld"]
Wie Entwickler die einzelnen Tests notieren, schreibt eine entsprechende Spezifikation vor. Am Anfang von Listing 2 verrät »schemaVersion: 2.0.0«, dass die folgenden Testbeschreibungen der aktuellen Spezifikation 2.0.0 folgen. Ohne diese Angabe verweigert das Google-Werkzeug gleich seinen Dienst.
Unter »globalEnvVars:« lassen sich noch vor dem Start der eigentlichen Tests Umgebungsvariablen im Container setzen. Hinter »key« steht dabei die Umgebungsvariable, ihr Inhalt neben »value«. Listing 2 nutzt dies aus, um die Anmeldedaten für die Datenbank in den Container durchzureichen. Dort warten dann eine Umgebungsvariable »MYSQL_USER« mit dem Inhalt »dev« sowie »MYSQL_PASSWORD« mit dem Passwort »123456«. Auf diese Umgebungsvariablen können dann auch die Tests zurückgreifen.
Unter »commandTest:« listet der Entwickler alle Prüfungen auf, bei denen das Google-Tool im Docker-Container ein Kommando ausführen muss. Listing 2 definiert nur einen einzigen Test. Dieser fragt beim Paketmanager RPM nach, ob das Paket »mysql-community-server-minimal« im Container installiert ist.
Wie auch alle anderen Tests erhält er zunächst hinter »name:« eine kurze Beschreibung – in Listing 2 ist dies »MySQL-Paket installiert«. Diese verwendet das Test-Framework später auch im Protokoll, daher sollte der Anwender sie sprechend und eindeutig wählen.
Befehlskette
Hinter »command:« folgt das auszuführende Programm, dem das Testwerkzeug gleich noch alle Parameter aus der Liste hinter »args:« mit auf den Weg gibt. Im Yaml-Format besteht eine Liste aus eckigen Klammern, wobei die einzelnen Elemente jeweils ein Komma trennt. Im Listing würde das Google-Tool folglich das Kommando »rpm -q mysql-community-server-minimal« aufrufen.
Die von diesem Befehl zurückgelieferte Ausgabe muss mit dem hinter »expectedOutput:« notierten Text übereinstimmen. Nur dann gilt der Test als bestanden. Wie auch bei allen anderen Textvergleichen interpretiert das Testwerkzeug den Begriff zwischen den Anführungszeichen als regulären Ausdruck. Das nutzt auch das Beispiel in Listing 2: Es genügt für einen erfolgreichen Test, wenn der Name des gefundenen Pakets mit »mysql-community-server« beginnt.
Neben »command« und »expectedOutput« gibt es noch »setup« und »teardown«. Die jeweils dahinter in einer Liste aufgeführten Befehle startet das Google-Tool vor oder nach dem Test. Mit ihnen lässt sich beispielsweise im Container eine Virtualenv für Python erstellen und dann über den Paketmanager »pip« das Framework Django hinzuholen:
setup: [["virtualenv", "/env"], ["pip", "install", "django"]]
Jeden Befehl müssen die Entwickler in einer eigenen Yaml-Liste kapseln und dabei jeden zu übergebenden Parameter separat in der Liste speichern. Im Beispiel würde das Testwerkzeug erst »virtualenv /env« absetzen, um dann »pip install django« aufzurufen.
Gesichtskontrolle
Alle Prüfungen im Dateisystem sammelt der Nutzer unter »fileExistenceTests:«. Das Beispiel aus Listing 2 testet dort, ob der MySQL-Client und die Konfigurationsdatei der Datenbank existieren. Hinter »path:« steht dabei die Datei, die das Google-Werkzeug untersuchen soll. In Listing 2 gelten die beiden Tests jeweils als bestanden, wenn die Datei existiert (»shouldExist: true«). Die Konfigurationsdatei muss zusätzlich noch die hinter »permissions:« aufgelisteten Zugriffsrechte besitzen.
Unter »fileContentTests:« finden sich alle Prüfungen, bei denen das Testwerkzeug einen Blick in eine Textdatei werfen soll. Die zu inspizierende Datei steht wiederum hinter »path:«. Der Test gilt als bestanden, wenn die Datei den hinter »expectedContents:« aufgelisteten Text enthält. Ihn interpretiert »container-structure-test« wiederum als regulären Ausdruck. In Listing 2 sucht das Werkzeug in der Konfigurationsdatei für die Datenbank nach einer Zeile mit dem Inhalt »datadir=/var/lib/mysql\n« und stellt so sicher, dass der MySQL-Server seine Daten unter »/var/lib/mysql« ablegt. Das »\n« steht wie gewohnt für den Zeilenumbruch.
Die abschließend unter »metadataTest:« hinterlegten Checks nehmen die Konfiguration des Containers unter die Lupe. Während Entwickler in den anderen Abschnitten beliebig viele weitere Tests nach dem jeweils gezeigten Schema hinterlegen dürfen, ist unter »metadataTest:« jeder Test nur einmal erlaubt. Der Parameter »env:« prüft zunächst, ob die angegebenen Umgebungsvariablen im Container gesetzt sind. Hinter »key:« steht wieder die Umgebungsvariable, ihr benötigter Wert wartet neben »value:«. Listing 2 lässt also testen, ob es im Container eine Umgebungsvariable »PATH« mit den angegebenen Pfaden gibt.
Der Eintrag »exposedPorts:« stellt anschließend sicher, dass die aufgelisteten Ports freigegeben sind. Analog testet »volume:«, ob das angegebene Volume existiert, »entrypoint:« kontrolliert den Entrypoint und »cmd:« das beim Start des Containers automatisch aufgerufene Kommando (»CMD« in der Docker-Konfigurationsdatei).
Containerinspektion
Sind alle Tests eingerichtet, holt der Entwickler das zu untersuchende Docker-Image schließlich auf die Festplatte. Um beispielsweise das offizielle MySQL-Docker-Image »mysql/mysql-server« aus dem Docker-Hub zu überprüfen, muss er zunächst den Befehl »docker pull mysql/mysql-server« ausführen.

Abbildung 1: Hier ist das Volume »/test« nicht verfügbar, alle anderen Tests waren hingegen erfolgreich. Das Werkzeug führt zudem immer alle Tests aus, auch wenn einer fehlschlägt.
Anschließend füttert er das ausführbare Werkzeug »container-structure-test« mit dem Namen des zu untersuchenden Container-Image sowie mit der Datei, welche die auszuführenden Tests enthält (Abbildung 1). Im folgenden Beispiel würden die Container Structure Tests das Docker-Image »mysql/mysql-server« mit den Tests aus der Konfigurationsdatei »mysql-tests.yaml« malträtieren:
container-structure-test test --image mysql/mysql-server --config mysql-tests.yaml
Um die Tests durchzuführen, startet »container-structure-test« den Container. Das ist jedoch nicht immer erwünscht, etwa im Rahmen eines Continuous-Integration-Prozesses. In solchen Fällen kann der Admin »container-structure-test« auch direkt auf das Image ansetzen:
container-structure-test test --driver tar --image mysql/mysql-server --config mysql-tests.yaml
Der zusätzliche Parameter »–driver tar« sorgt dafür, dass »container-structure-test« das Container-Image in einem »tar«-Archiv verpackt und dann direkt im Archiv die Tests ausführt. Prinzipbedingt absolviert das Google-Werkzeug auf diesem Weg allerdings nur die Datei-basierten Tests und untersucht den Container selbst, führt aber keine Kommandos aus (Abbildung 2).

Abbildung 2: Darf das Testwerkzeug den Container nicht starten, verzögert das Verpacken in ein Tar-Archiv die Tests nicht nur erheblich, auch das Ausführen von Kommandos schlägt fehl.
Altertumsforschung
Neben den in Listing 2 vorgestellten Testmöglichkeiten gibt es noch einige weitere. So kontrollieren Entwickler nicht nur die Zugriffsrechte einer Datei, sondern mit »Uid« und »Gid« auch die User- und Gruppen-IDs. Alle derzeit möglichen Tests erläutert die offizielle Dokumentation auf Github [1], die zu Redaktionsschluss allerdings nur aus einer knappen Referenz bestand.
Für die ersten Gehversuche bietet Google das Docker-Image »gcr.io/google-appengine/python« an, das im Wesentlichen einen Python-Interpreter beherbergt. Dazu passende Test finden sich in einem eigenen Github-Repository [3].
Die Parameter des Kommandozeilentools »container-structure-test« haben sich in der Vergangenheit jedoch leicht verändert. Viele Beispiele im Internet beziehen sich noch auf eine ältere Version. Sogar die offizielle Dokumentation auf Github enthält einige nicht mehr funktionierende Beispielbefehle.
Gegenüber älteren Versionen müssen Anwender vor allem die Parameter immer mit zwei Bindestrichen einleiten und »-test.v« durch das kurze »test« ersetzen. Des Weiteren ist die Yaml-Datei explizit über den Parameter »–config« zu laden. In vielen veralteten Beispielen heißt »container-structure-test« zudem noch schlicht »structure-test«. Der häufig anzutreffende Beispielaufruf
structure-test -test.v -image gcr.io/google-appengine/python python_command_tests.yaml
lautet folglich in der aktuellen Version 1.4.0:
container-structure-test test --image gcr.io/google-appengine/python --config python_command_tests.yaml
Die jeweils gültigen Parameter verrät im Zweifel der Aufruf »container-structure-test help«.
Fazit
Mit den Container Structure Tests validieren Entwickler und Administratoren Container extrem schnell. Dazu müssen sie diese meist noch nicht einmal starten. Dank der knappen Yaml- oder Json-Notation sind die Tests zudem ohne langwierige Einarbeitung flott notiert.
Der einfache Aufbau der Testbeschreibungen beschränkt jedoch die Möglichkeiten. So lässt sich nur mit einigen kleinen Verrenkungen herausfinden, ob MySQL mindestens in der Version 5.7 installiert ist. Des Weiteren sind die Container Structure Tests darauf ausgelegt, Container und Images vor ihrem Einsatz und nicht während des laufenden Betriebs zu testen. Wer solche umfangreicheren Tests ausführen möchte, sollte daher einen Blick auf ein Konkurrenzprodukt wie Testinfra [4] werfen.
Infos
-
Container Structure Tests: https://github.com/GoogleContainerTools/container-structure-test
-
64-Bit-Version für Linux-Systeme: https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64
-
Beispiele: https://github.com/nkubala/structure-test-examples
-
Testinfra: http://testinfra.readthedocs.io/en/latest/






