Cloud Computing / DevOps / Software-Entwicklung / Software-Test / Test Center / Testmanagement

Testumgebungen auf einen Klick

Sie fordern eine bestimmte Testumgebung an, diese wird auf Knopfdruck bereitgestellt und nach dem Test wieder gelöscht. Sie können jederzeit wieder eine Testumgebung anfordern, wann immer und so oft Sie wollen. Großartige Vorstellung – aber nicht möglich? Falsch gedacht! Oft fehlen bei Test- und Operation-Teams lediglich der Mut und die Fantasie die Bereitstellung als ausführbaren Code zu realisieren – welchen man regelmäßig selbst testen und nach Bedarf erweitern kann.

Testumgebungen auf Knopfdruck mit Docker und Puppet

 

Zeitgemäßes Testumgebungsmanagement als Herausforderung und Lösung – das war Brennpunktthema bei meinem gestrigen Vortrag auf den Software Quality Days 2017 in Wien. Einen ersten Teaser dazu konnten Sie bereits in meinem letzten Beitrag lesen. Heute möchte ich mit Ihnen ins Detail gehen und die Ansätze bzw. Technologien von Puppet und Docker unter die Lupe nehmen. Sehen Sie diesen Artikel als Denkanstoß, diesem Thema beim nächsten Quartalsmeeting einen eigenen Punkt auf der Agenda einzuräumen.

 

Die Voraussetzung

Grundvoraussetzung: Testumgebungen müssen automatisch deployed werden können. Dafür gilt es die manuellen Schritte, die dafür notwendig sind, zu eliminieren. Im Grunde soll es nur zwei manuelle Schritte geben:

  • Die Auswahl der Version von dem zu liefernden Software-Artefakt (z.B. eine App oder ein Service)
  • Die Auswahl der Umgebung, in welche dieses Artefakt geliefert werden soll

Alles andere erfolgt im Idealfall automatisiert. Die Anweisungen, wie die Installation und Konfiguration der Testumgebung erfolgt, muss dafür als Code vorliegen.

 

Was bedeutet Infrastructure as a Code (IaC)?

Die Bedeutung von IaC kann sich je nach Technologie stark unterscheiden. Das Wichtigste ist aber, dass jeglicher Code unter Versionskontrolle steht und somit versioniert ist.

In vielen Organisationen wird bereits IaC genutzt, nämlich in Form von Shell-, Batch-, oder Powershell- Scripts. Diese werden jedoch rasch zu komplex und sind damit nur unter großem Aufwand wartbar.

Wie eine moderne Herangehensweise an IaC aussehen kann, wird in diesem Artikel mittels der Ansätze bzw. Technologien von Puppet und Docker beschrieben. Diese sind zwar unterschiedlich, schließen sich aber gegenseitig nicht aus und können durchaus sinnvoll kombiniert werden.

 

Puppet

Puppet ist ein auf Ruby basierendes Framework. Es ermöglicht eine Software samt notwendiger Umgebung automatisch bereitzustellen, zu aktualisieren und zu überwachen.

Dabei wird der gewünschte Zustand in einer eigenen Domain-Specific-Language (DSL) beschrieben und Puppet kümmert sich darum, dass dieser Zustand mit allen Abhängigkeiten erreicht wird. Dazu muss als erstes Puppet auf der Zielplattform installiert sein. Puppet verwendet wiederverwendbare Module, wobei sich die Module um bestimmte Aufgaben kümmern (z.B. Installation und Konfiguration eines Apache Webservers oder einer Datenbank). Es können eigene (custom) Module geschrieben werden oder auch auf tausende von der Community bereitgestellte Module aus der sogenannten „Forge“ zurückgegriffen werden.

Ausgangspunkt ist meist das site.pp Manifest, das z.B. so aussehen kann:

# /etc/puppetlabs/puppet/manifests/site.pp

node 'sut.example.com' {

include my-app

}

node 'db1.example.com' {

include postgres

}

Dieses File beschreibt, dass auf der Maschine mit dem Domainnamen „sut.example.com“ die Klasse my-app angewendet werden soll. Diese Klasse könnte sich z.B. in einem Custom-Modul befinden in welchem beschrieben ist, wie my-app installiert und konfiguriert wird und welche Abhängigkeiten my-app hat. Dies könnte z.B. so aussehen:

class my-app (String $version = '1.8.2') {

nginx {'nginx':

ensure => 1.9.15

}

mybase::package {"Install my-app $version":

package_name    => my-app,

package_version => $version,

destination_folder => /usr/share/nginx/html,

require  => nginx

}

file {'/etc/nginx/nginx.conf':

ensure  => file,

owner   => 'httpd',

content => template('nginx/nginx.conf.erb'),

require  => nginx

}

service {'nginx':

ensure    => running,

enable    => true,

subscribe => File['/etc/nginx/nginx.conf']

}

}

Alle notwendigen Ressourcen wie Files, Services, User, Packages etc. werden mit den Properties aufgelistet und Puppet sorgt dafür, dass diese Ressourcen (auch in der richtigen Reihenfolge) auf dem Zielrechner vorhanden sind.

Im Beispiel oben benötigt my-app nginx als Webserver. Die Installation erfolgt, indem mybase::package verwendet wird. Diese Klasse „weiß“ alles Notwendige, um ein Package zu installieren – sie kann innerhalb der Organisation Packages aus dem Binary Repository herunterladen und diese installieren (z.B. im destination_folder entpacken).

Es können auch verschieden Abhängigkeiten beschrieben werden. „Require“ beschreibt, dass eine bestimmte Ressource benötigt wird, bevor eine andere angewendet wird (Zuerst nginx installieren, bevor my-app installiert wird.) Subscribe beschreibt, dass das Service nginx neu gestartet werden muss, falls sich /etc/nginx/nginx.conf ändert.

Weitere Konzepte:

Facter: liefert vorgefertigte oder individuelle Informationen über das System, auf das gerade ein Puppet-Run angewendet wird (z.B. Architektur, Netzwerkkonfiguration, OS Version, Umgebungsvariablen). Diese Facts werden in den Modulen verwendet, um beispielsweise je nach Architektur andere Pfade zu verwenden. Dies erhöht die Wiederverwendbarkeit von Modulen, da diese nicht für jede Zielplattform bzw. Architektur neu geschrieben werden müssen.

Hiera: ist ein Key-Value-Lookup Tool für Konfigurationsdaten.  Alle umgebungsspezifischen Daten – wie URLs, Ports, Heap Space, usw. – sollen in den Modulen nicht hardcodiert sein, sondern aus einer Konfigurationsquelle gelesen werden. Dadurch verbessert sich die Flexibilität der verwendeten Module, weil Parameter dadurch dynamisch gesetzt werden können.

 

Docker

Docker ist im Kern eine Betriebssystemvirtualisierung in standardisierten, übertragbaren Containern, die in einer sogenannten Docker Engine laufen.

Ein Image, welches alle umgebungsunabhängigen Vorbedingungen inkludiert, wird einmal erstellt und in allen Umgebungen wiederverwendet. Von diesem Image ausgehend werden Container gestartet – denen z.B. umgebungsspezifische Parameter übergeben werden können. Das Erzeugen und Starten dieser Container ist extrem schnell, da nur ein Prozess gestartet wird – im Gegensatz zu einer neuen Virtuellen Maschine, bei der ein ganzes Betriebssystem gestartet wird. Deshalb können Container einfach entsorgt und wieder neu erstellt werden.

Hier ein Beispiel wie ein Docker-File aussehen könnte, von dem aus ein Image erstellt wird:

FROM nginx:1.9.15

MAINTAINER: devopsteam@anecon.com

ENV MY_APP_VERSION 0.7.3

RUN wget http://nexus:8081/content/repositories/my_app/my_app-$MY_APP_VERSION.tar.gz \

&& tar -C /usr/share/nginx/html -xvzf my_app-$MY_APP_VERSION.tar.gz

COPY ./app/

EXPOSE 80

VOLUME /var/log/nginx/

ENTRYPOINT ["/app/docker-entrypoint.sh"]

Die FROM Anweisung gibt ein Basisimage vor, in dem nginx in einer definierten Version bereits vorhanden ist. Mit Docker Build wird aus diesem Docker-File ein Image gebildet und in einem Image Store (Image Registry) abspeichert. Über Tags kann auf bestimmte Versionen dieser Images zugegriffen werden.

Um z.B. eine Testumgebung zu erstellen in der my-app und eine Postgres Datenbank verwendet wird, genügt ein yaml-File, das die Infrastruktur beschreibt. Mit docker-compose können die Container dann gestartet werden:

version: '2'

services:

sut:

image: my-app:0.7.3

depends_on:

- db

ports:

- "8080:80"

db:

image: postgres:9.4.9

environment:

- POSTGRES_PASSWORD: mypostgrespwd

- POSTGRES_INITDB_ARGS: --data-checksums

volumes:

- datavolume:/var/lib/postgresql/data

Docker bietet aber noch viel mehr – zum Beispiel ein komplettes Ecosystem und Toolset zur Verwaltung dieser Container. Dieses enthält:

  • Build-Umgebung, um Images aus Source-Files (Docker-File) zu erstellen.
  • Docker Hub: reichhaltiges Angebot an vorgefertigten Images – sowohl offizielle, als auch von der Community zur Verfügung gestellte.
  • docker-compose: Definieren und Starten von Multi-Container Applikationen
  • docker-swarm: zur Verwaltung von Docker Clustern (Zusammengefasste Docker-Hosts)
  • docker-machine: Provisionierung von Docker Hosts

 

Unterschiede und Gemeinsamkeiten

Bei Docker wird ein definiertes Image unverändert in verschiedenen Umgebungen wiederverwendet. In einem Image ist immer nur ein Service (z.B. Webserver oder Datenbank) enthalten. Alle notwendigen Ressourcen sind im Image enthalten. Das Starten eines neuen Containers basierend auf diesem Image, ist wie das Starten eines neuen Prozesses.

Zum Vergleich: Bei Puppet wird ein gewünschter Zustand des Systems definiert und dieser auf den Zielplattformen hergestellt. Startpunkt ist dabei z.B. ein „golden Image“ (ein genau spezifiziertes Image, das für die Erstellung weiterer Images verwendet wird).

Mit Docker verglichen, ist Puppet eher für Infrastruktur, die länger erhalten wird, geeignet – beispielsweise Testumgebungen in einer späteren Phase der Entwicklung (System-Integrationsumgebung, Pre-Produktion, Hotfix Umgebung). Die Umgebung wird für jede Stage neu erstellt oder eventuell auch aktualisiert, falls das Provisioning von VMs oder physischer Maschinen zu aufwendig ist.

Bei Docker können neue Container ganz einfach ohne Ressourcenverbrauch aus dem Image wieder erzeugt werden – und werden daher auch einfach nach Ende der Tests gelöscht. Dieses Vorgehen ist gut geeignet für Entwicklungsumgebungen, die häufig beliefert werden und sich daher täglich ändern können. Aus Testsicht eignen sich diese Umgebungen gut für Unit- oder Komponententests.

Eines haben jedoch beide Technologien gemeinsam: Es werden Zielmaschinen benötigt, auf denen ein Deployment durchgeführt werden kann. Für das Hosting von Testumgebungen existieren heutzutage einige Möglichkeiten – die spannendste ist wohl die unendliche Weite einer Cloud. Hier zu mehr in meinem nächsten Artikel „Testumgebungsmanagement mit der Cloud„!

Passende Artikel

2 Kommentare

  1. Komponententests mit Docker sind technisch relativ einfach zu handeln (mit Puppet habe ich noch keine Erfahrung machen können), doch man hört oft (ich selber kann das leider nicht beurteilen), dass Docker-Container schwerwiegende Sicherheitslücken wie Heartbleed und ShellShock enthalten können. Freue mich in jedem Fall auf Deinen nächsten Artikel! Grüße

    • Hallo,

      Vielen Dank für dein Feedback.
      Zum Thema Sicherheitslücken bei Docker gibt es rege Diskussionen im Netz.
      Dadurch, dass die Commuinity freie Docker-Container zur Verfügung stellen kann, besteht natürlich die Möglichkeit, dass hier mit weniger Vorsicht vorgegangen wird. Auf der anderen Seite stehen die offiziellen Docker-Container, welche ziemlich strengen Security Guidelines unterliegen. Manche Container enthalten aber auch absichtlich veraltete Versionen (oder bereits bekannte Sicherheitslücken) um die Möglichkeit zu haben, diesbezügliche Problemstellungen nachvollziehen zu können.

      Hier ein hilfreicher Artikel zu dem Thema
      https://jpetazzo.github.io/2015/05/27/docker-images-vulnerabilities/

      Mein nächster Artikel ist übrigens schon online, und ich freue mich über weitere Fragen 😉
      http://www.anecon.com/blog/testumgebungsmanagement-mit-der-cloud/

      Herzliche Grüße, Max