Test-driven development mit Chef und Vagrant

Wenn es darum geht, auch komplexe Infrastrukturen automatisiert bereitzustellen und zu konfigurieren, ist Chef ein mächtiges Werkzeug. Getreu dem Infrastructure as Code (IaC)-Gedanken bietet es mehr als klassisches Configuration Management und kann dazu verwendet werden, um manuelle Verfahren durch schnellere sowie mess- und wiederholbare Automatismen zu ersetzen. Bei der Entwicklung solcher Automatismen kommt häufig Vagrant zum Einsatz, um schnell temporäre VM-Instanzen unabhängig vom Hypervisor automatisiert bereitzustellen.

Was genau test-driven development (TDD) in diesem Kontext bedeutet und wie sich die erwähnten Tools hier sinnvoll benutzen lassen, erfahrt ihr in den nächsten Abschnitten. 🙂

Aufbau und Grundlagen

Bevor wir uns jedoch in die Theorie stürzen, möchte ich ein Beispiel-Szenario benennen.

In diesem Szenario soll die Bereitstellung neuer Webserver vollständig automatisiert werden. Im Wesentlichen geht es um die folgenden Teilschritte:

  • Bereitstellung einer neuen CentOS-VM
  • Installation des Apache-Webservers
  • Konfiguration und Starten des Dienstes
  • Hinterlegen von Inhalten

Infrastructure-as-Code

Zugegebenermaßen erscheinen die oben genannten Teilschritte trivial - man könnte sie auch in Form eines Skriptes automatisieren. Was hierbei aber eindeutig zu kurz käme, wären die Faktoren Versionierung, Messbarkeit und Reproduzierbarkeit.

Gehen wir mal davon aus, dass wir sukzessive 500 Webserver über ein Skript bereitgestellt haben. Nun gibt es Änderungen in der Software-Auswahl und neue Systeme werden mit zusätzlichen Software-Paketen betankt. Die alten Systeme sind hiervon nicht betroffen und es muss ein neuer Mechanismus gefunden werden, auch diese zu aktualisieren. Klassische Skripte werden sequentiell abgearbeitet, potentielle Fehler müssen von vornherein bedacht werden, um Ausnahmefälle zu definieren. In puncto Messbarkeit ist ein klassisches Skript ebenfalls deutlich benachteiligt, da es erstmal einfach alle Instruktionen erneut ausführt (in diesem Beispiel würde ein Webserver möglicherweise neuinstalliert werden) anstatt Kontext-bezogen relevante Aktionen wiederholt.

Der Sinn hinter IaC ist es, Infrastruktur-Ressourcen über maschinenlesbare Dateien zu definieren. Die Konfiguration und Verwaltung dieser Ressourcen kann dann von intelligenten Software-Lösungen übernommen werden. Durch den Einsatz von Versionen und Test-Katalogen können so auch komplexe Infrastruktur-Szenarien automatisiert bereitgestellt werden. Da die gesamte Infrastruktur als Code definiert ist, lassen sich (geprüfte) vorherige Versionen leicht wiederherstellen.

Hier bedient sich IaC in der Software-Entwicklung erprobten Techniken, wie beispielsweise git zur Versionsverwaltung oder test-driven development.

Infrastructure as Code

Test-driven development

Test-driven development, oder testgetriebene Entwicklung, ist eine Methode, die häufig bei der agilen Software-Entwicklung zum Einsatz kommt, sich zunehmend aber auch bei Infrastruktur-Themen großer Beliebtheit erfreut. Bei der Software-Entwicklung ist es unabdingbar, entwickelten Code anhand definierter Tests auf Funktionsfähigkeit und Erfüllung notwendiger Kriterien zu überprüfen.

Beim test-driven development werden die Tests konsequent vor der Implementation des Codes definiert, während bei der Wasserfall-Methode die Tests während bzw. nach der Entwicklung definiert werden. Durch die sehr frühe Test-Definition wird verhindert, dass Funktionalität nicht implementiert oder Testkriterien nachträglich reduziert werden, um das Projektziel zu erreichen. Darüber hinaus ist ausgeschlossen, dass Tests für entwickelte Funktionalität vergessen werden.

Ein wichtiges Merkmal ist auch die kontinuierliche Überprüfung und Verbesserung von Tests und Codes. Während in einer ersten Iteration grundlegende Tests und Code hinterlegt werden, beinhalten weitere Iterationen Optimierungen und zusätzliche Funktionalitäten.

Test-driven development

Infrastruktur-Deployments profitieren von dieser Vorgehensweise: durch klar definierte Soll-Zustände ist sichergestellt, dass zu entwickelnde Automatismen erst dann als vollständig gelten, wenn alle Kriterien erfüllt sind. Durch die Tests lassen sich auch bestehende Systeme überprüfen und im Zweifelsfall korrigieren - beispielsweise nach einer Iteration, die neue Funktionalität beinhaltet. Vor allem großen Systemlandschaften kommt das zugute. Während kleinere Umgebungen mit wenigen Servern manuell noch gut zu überblicken sind, ist das Vorhaben bei mehreren hunderten Servern unmöglich.

Chef

Chef ist eine Automatisierungsplattform, die es erlaubt nicht nur Konfigurationen, sondern gesamte Infrastruktur-Szenarien in Code abzubilden. Die Liste der unterstützen Ressourcen ist lang; neben klassischen Betriebssystem-Komponenten (Dateien, Pakete, Benutzer,...) lassen sich auch ganze Cloud-Ressourcen (AWS Workloads, Azure VMs,...) verwalten. Auf GitHub gibt es eine Sammlung zahlreicher Zusatz-Erweiterungen.

Chef ist agentenbasiert; zu verwaltende Entitäten (Server, Storage, Cloud-Ressource) benötigen eine Client-Software zur Kommunikation mit einem Chef-Server oder - optional - zur Regelanwendung ohne Server. Für die folgenden Plattformen stehen Software-Pakete bereit:

  • IBM AIX
  • Debian und Ubuntu
  • Red Hat Enterprise Linux und binärkompatible Ableger wie CentOS
  • Apple macOS
  • SUSE Linux Enterprise und openSUSE
  • Oracle Solaris
  • Microsoft Windows

Innerhalb Chef wird Konfigurationslogik in einem sogenannten Cookbooks gebündelt - dieses besteht aus:

  • Recipes - Sammlung von Ressourcen und deren Spezifika zur Konfiguration
  • Attribute - benutzerdefinierte Variablen, die das Verhalten von Recipes steuern, ohne deren Code anpassen zu müssen
  • Dateien und Vorlagen - für statische Dateien oder Konfigurationsvorlagen

Im Gegensatz zu anderen Configuration Management-Tools, wie beispielsweise Puppet, verwendet Chef einen nahezu unverändertes Ruby.

In der sogenannten Chef Workstation werden neben dem Chef-Agenten unter anderem noch die folgenden zusätzlichen Programme gebündelt, um die Entwicklung zu erleichtern:

  • Berkshelf - Verwaltung von Abhängigkeiten (z.B. andere Cookbooks)
  • Cookstyle - Tool zur Überprüfung des Quellcode-Stils
  • Foodcritic - Statische Code-Analyse
  • Test Kitchen - Integrationstest-Werkzeug, welches Cookbooks auf mehreren Plattformen testet (z.B. verschiedene Betriebssysteme)
  • Inspec - Framework zur Auditierung von Anwendungen und Infrastruktur, Soll-/Ist-Analysen

Insbesondere Test Kitchen ist in Kombination mit Vagrant, Chef und Inspec äußerst nützlich. In diesem Szenario wird es dazu verwendet, automatisiert zu testende VMs mit Vagrant bereitzustellen, die Cookbooks mittels Chef anzuwenden und die Einhaltung des Soll-Zustands durch Inspec sicherzustellen. Manuell ausgeführt, würden diese Teilschritte einen großen Zeitaufwand bedeuten.

Chef Workflow

Vagrant

Vagrant ist ein Tool zur Erstellung und Konfiguration von virtuellen Maschinen und dient vor allem in in der Software-Entwicklung als Abstraktionsschicht zwischen Hypervisor (u.a. VMware, VirtualBox, Hyper-V, AWS,...) und Configuration Management (u.a. Chef, Salt, Ansible, Puppet).

Nach der Bereitstellung einer VM kann Vagrant diese auch konfigurieren. So können Skripte übergeben und gestartet oder auch Inhalte per FTP/SFTP kopiert werden. Durch die Integration in Configuration Management-Systeme können beispielsweise auch Rollen und Cookbooks zugewiesen werden.

Vagrant bietet auf der Webseite eine Sammlung vorkonfigurierter VM-Vorlagen, welche auch Boxes genannt werden. Nach einer Registrierung ist es möglich, eigene Boxes anzubieten. Bei der Erstellung eigener Boxes ist im Wesentlichen zu beachten:

  • Installation des Gastbetriebssystems
  • Installation des entsprechenden Hypervisor-Tools (z. B. VMware Tools oder VirtualBox Guest Additions)
  • Erstellen eines vagrant-Benutzers für die Verbindung mittels SSH

Für die folgenden Abschnitte ist es notwendig, einen der unterstützten Hypervisor (VirtualBox, VMware, Hyper-V) zu installieren.

Note

Bei der Verwendung von VMware-Hypervisoren werden ein dediziertes Plugin und eine Lizenz benötigt!

Die wichtigsten Befehle sind in der folgenden Tabelle zu finden:

Befehl Erklärung
vagrant box add (box) Hinzufügen einer Box aus dem Katalog
vagrant box list Auflisten heruntergeladener Boxen
vagrant box remove (box) Entfernen einer Box
vagrant box update (box) Aktualisieren einer Box
vagrant init Erstellen eines Vagrantfiles
vagrant reload Erneutes Einlesen des Vagrantfiles nach Anpassung
vagrant validate Validieren des Vagrantfiles
vagrant up Bereitstellen und Starten einer Instanz
vagrant provision Erneutes Bereitstellen einer Instanz
vagrant destroy Entfernen einer bereitgestellten Instanz
vagrant port Auflisten von definierten Port-Weiterleitungen
vagrant ssh-config Anzeigen der SSH-Konfiguration
vagrant ssh Herstellen einer SSH-Verbindung zur Instanz
vagrant powershell Herstellen einer Verbindung zur Instanz über Powershell-Remoting
vagrant rdp Herstellen einer RDP-Verbindung zur Instanz
vagrant status Anzeigen des Instanz-Status
vagrant suspend Anhalten der VM
vagrant resume Fortsetzen der VM
vagrant halt Herunterfahren der VM

Mithilfe des folgenden Kommandos wird eine Box importiert - in diesem Fall die CentOS 7-Box, die es gleich für verschiedene Hypervisor gibt:

 1$ vagrant box add centos/7
 2==> box: Loading metadata for box 'centos/7'
 3    box: URL: https://vagrantcloud.com/centos/7
 4This box can work with multiple providers! The providers that it
 5can work with are listed below. Please review the list and choose
 6the provider you will be working with.
 7
 81) hyperv
 92) libvirt
103) virtualbox
114) vmware_desktop

Das Eintippen des entsprechenden Providers kann wie folgt vermieden werden:

1$ vagrant box add centos/7 --provider virtualbox

Heruntergeladene Boxes sind unter unixoiden Systemen im Ordner ~/.vagrant.d/boxes zu finden. Unter Microsoft Windows hilft ein Blick in den Ordner C:\Users\<benutzer>.vagrant.d\boxes.

Da eine Box lediglich eine Art Vorlage darstellt, ist in VirtualBox noch keine VM zu sehen. Hierfür benötigt Vagrant noch ein Vagrantfile, welches die Instanz näher spezifiziert, es wird mit dem folgenden Kommando erstellt:

1$ mkdir centos7 ; cd $_
2$ vagrant init centos/7

Anschließend ist im aktuellen Verzeichnis eine Datei namens Vagrantfile zu finden. Diese enthält weitere Einstellungen, wie beispielsweise das zu verwendende Netzwerk oder beim Bereitstellen auszuführende Kommandos.

Das eigentliche Erstellen der Instanz wird mit dem folgenden Kommando gestartet:

1$ vagrant up

Eine per Vagrant gestartete VM-Instanz

Anschließend wird auf dem Rechner eine virtuelle Maschine ausgeführt.

Eine gängige Zugriffsmöglichkeit auf die Instanz ist SSH - mit dem ssh-config-Befehl kann die entsprechende SSH-Konfiguration angezeigt werden:

1$ vagrant ssh-config
2Host default
3  HostName 127.0.0.1
4  User vagrant
5  Port 2222
6  PasswordAuthentication no
7  IdentityFile /Users/christian/Documents/Vagrant/centos7/.vagrant/machines/default/virtualbox/private_key
8  IdentitiesOnly yes
9  LogLevel FATAL

Ein direkter Verbindungsaufbau ist ebenfalls möglich:

1$ vagrant ssh
2[vagrant@localhost ~]$ cat /etc/redhat-release
3CentOS Linux release 7.5.1804 (Core)

Zeit für einen Test - auf dem System soll ein Webserver betrieben werden:

1[vagrant@localhost ~]$ sudo yum install -y httpd
2[vagrant@localhost ~]$ sudo systemctl enable httpd ; sudo systemctl start httpd

Während der Zugriff lokal funktioniert, ist ein Zugriff außerhalb der VM nicht möglich. Das liegt daran, dass keine Port-Weiterleitung definiert wurde. Hierzu muss die folgende Zeile des Vagrantfiles angepasst werden:

1config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
Note

Hinweisen zu weiteren Einstellungen finden sich in der offiziellen Dokumentation

Damit die Änderungen übernommen werden, muss das reload-Kommando verwendet werden. Hierbei wird die neue Konfiguration eingelesen und VM neu gestartet:

1$ vagrant reload

Anschließend ist ein Zugriff über die Adresse http://localhost:8080 möglich:

Testen des Webservers

Das Cookbook

We can cook it

Kernstück der Logik dieses Beispiels ist das Cookbook, welches es nun zu erstellen gilt. Wie bereits erwähnt besteht ein Cookbook aus einem oder mehreren Recipes, sowie verschiedenen Test-Definitionen. Der erste Schritt ist es, Chef Workstation zu installieren. Anschließend lassen sich mithilfe des chef generate-Kommandos entsprechende Vorlagen bereitstellen. Natürlich ließen sich diese auch manuell erstellen - jedoch rate ich davon ab. Wird mit erwähntem Mechanismus beispielsweise ein Cookbook erstellt, generiert Chef alle benötigten Ordner und Dateien. Bei der Generierung eines Recipes werden gleich die benötigten Unit- und Integrationstests erstellt. Man spart sich also jede Menge Tipparbeit und Zeit.

1$ mkdir cookbooks ; cd $_
2$ chef generate cookbook basic-webserver
3$ cd basic-webserver

Anschließend finden sich die folgenden Unterordner im aktuellen Ordner:

  • recipes
  • spec - Unittests
  • test - Integrationstests

Zur Dokumentation werden die Dateien CHANGELOG.md und README.md erstellt - hier gilt es die Änderungen bzw. den Zweck des Cookbooks (in Markdown) zu dokumentieren. Die Datei metadata.rb ist von ebenfalls hoher Wichtigkeit - sie enthält Metadaten, wie die Cookbook-Version, sowie Informationen zu Nutzen und Autor.

Integrationstests mit InSpec

Inspec ist unser Werkzeug zur Auditierung der Recipes, es wurde von den Mitgliedern des DevSec Hardening Framework Teams entwickelt. Hier wird der Soll-Zustand definiert, den es mit Code abzudecken gilt. Über chef generate erstellte Recipes erhalten automatisch InSpec-Vorlagen unterhalb test/integration/default. Das Beispiel (default_test.rb) sieht wie folgt aus:

 1# # encoding: utf-8
 2...
 3unless os.windows?
 4  # This is an example test, replace with your own test.
 5  describe user('root'), :skip do
 6    it { should exist }
 7  end
 8end
 9
10# This is an example test, replace it with your own test.
11describe port(80), :skip do
12  it { should_not be_listening }
13end

Hier werden zwei beispielhafte Tests definiert, die jedoch aufgrund der :skip-Direktive nicht aktiv abgefragt werden - sie werden übersprungen. Der erste Test setzt die Existenz des root-Benutzers voraus, sofern kein Windows-Betriebsystem vorliegt. Der zweite Test erfordert, dass der TCP-Port nicht auf Anfragen reagiert.

Inspec versteht sämtliche zu überprüfende Entitäten (z. B. Dienst, Datei, Web-Anwendung,...) als Ressourcen, deren erwartetes Verhalten mit Matchern deklariert wird. Neben allgemeinen Matchern (soll/soll + ähnlich zu, beinhalten, größer/kleiner) gibt es je nach Ressource auch noch spezifische Matcher (z. B. HTTP-Request sollte Code xyz zurückgeben). Die Tests sind sehr simpel gestrickt und aufgrund der einfachen Sprache nahezu selbsterklärend.

Nun ist es an der Zeit, die folgenden, eigenen Tests zu definieren:

  • Das Paket httpd sollte installiert sein
  • Der TCP-Port 80 sollte offen sein
  • Ein Aufruf der URL http://localhost sollte eine gültige Antwort empfangen

Hierfür sind die InSpec-Ressourcen package, port und http notwendig:

 1# # encoding: utf-8
 2
 3# Inspec test for recipe basic-webserver::default
 4
 5# Make sure that the httpd package is installed
 6describe package('httpd') do
 7  it { should be_installed }
 8end
 9
10# Make sure that the web server port is listening
11describe port(80) do
12  it { should be_listening }
13end
14
15# Make sure that the homepage is accessible
16describe http('http://localhost') do
17  its('status') { should cmp 200 }
18end

Die wichtigsten Spezifika erhalten die Ressourcen direkt als Parameter: so enthält package das genaue Paket, port den konkreten Netzwerkport und http die exakte URL, die es zu prüfen gilt. Mittels it/its-Filter und should-Matchern wird das erwartete Verhalten konkretisiert.

Note

Hinweisen zu weiteren Ressourcen und Matchern finden sich in der offiziellen Dokumentation

Wenn diese Tests nun ausgeführt werden, schlagen diese fehl:

 1$ inspec exec default_test.rb
 2
 3Profile: tests from default_test.rb (tests from default_test.rb)
 4Version: (not specified)
 5Target:  local://
 6
 7  System Package httpd
 8 ×  should be installed
 9     expected that `System Package httpd` is installed
10  Port 80
11 ×  should be listening
12     expected `Port 80.listening?` to return true, got false
13  http GET on http://localhost
14 ×  status
15     Failed to open TCP connection to localhost:80 (Connection refused - connect(2) for "localhost" port 80)
16
17Test Summary: 0 successful, 3 failures, 0 skipped

Keiner der Tests war erfolgreich. Das liegt daran, dass wir die Tests auf dem ausführenden InSpec-Host und nicht der VM, die es zu überprüfen gilt ausgeführt haben. Abhilfe schafft die Auditierung über Test Kitchen.

Test Kitchen

Test Kitchen ist in der Lage, für zu prüfende Umgebungen mittels Vagrant entsprechende VMs zu erstellen. Anschließend werden Chef Cookbooks umgesetzt und die dazugehörigen Integrationstests mit Inspec durchgeführt. Hierfür muss die Konfigurationsdatei .kitchen.yml im cookbooks-Ordner angepasst werden:

 1---
 2driver:
 3  name: vagrant
 4
 5provisioner:
 6  name: chef_solo
 7
 8verifier:
 9  name: inspec
10
11platforms:
12  - name: centos/7
13
14suites:
15  - name: default
16    run_list:
17      - recipe[basic-webserver::default]
18    verifier:
19      inspec_tests:
20        - test/integration/default
21    attributes:

Als Treiber muss vagrant gewählt werden. Die provisioner-Direktive steuert die für die Konfiguration zu verwendende Software. Neben klassischen Shell-Skripten, werden auch Konfigurationssysteme, wie Ansible oder Chef unterstützt. Unterhalb platforms sind die Umgebungen, auf denen Konfiguration und Überprüfung stattfinden sollen, anzugeben - in diesem Fall der Name der Vagrant-Box. Inspec dient hier als verifier, unterhalb suites werden neben den anzuwenden Cookbooks auch die relevanten Integrationstests angegeben. In diesem Fall wird das Haupt-Recipe sowie alle unterhalb test/integration/default erstellten Tests einbezogen.

Mit dem folgenden Aufruf werden alle definierten Test-Szenarien aufgelistet:

1$ kitchen list
2Instance          Driver   Provisioner  Verifier  Transport  Last Action    Last Error
3default-centos-7  Vagrant  ChefSolo     Busser    Ssh        <Not Created>  <None>

In diesem Fall gilt es lediglich eine Umgebung zu überprüfen - die noch zu erstellenden CentOS 7-VM. Der folgende Befehl erstellt diese und führt anschließend die Tests aus:

 1$ kitchen verify
 2...
 3  System Package httpd
 4 ×  should be installed
 5     expected that `System Package httpd` is installed
 6  Port 80
 7 ×  should be listening
 8     expected `Port 80.listening?` to return true, got false
 9  http GET on http://localhost
10 ×  status should cmp == 200
11
12     expected: 200
13          got:
14
15     (compared using `cmp` matcher)
16
17Test Summary: 0 successful, 3 failures, 0 skipped
18>>>>>> ------Exception-------
19>>>>>> Class: Kitchen::ActionFailed
20>>>>>> Message: 1 actions failed.
21>>>>>>     Verify failed on instance .  Please see .kitchen/logs/default-centos-7.log for more details
22>>>>>> ----------------------
23>>>>>> Please see .kitchen/logs/kitchen.log for more details
24>>>>>> Also try running `kitchen diagnose --all` for configuration

Kitchen verzeichnet hier drei fehlgeschlagene Tests - und das ist völlig korrekt, da die Soll-Zustände bisher in keinem Recipe umgesetzt werden.

Ein Ansatz ist es, diese Logik im Haupt-Recipe recipes/default.rb abzubilden. Größere Cookbooks bestehen in der Regel aus mehreren Recipes, die über das Haupt-Recipe kontrolliert aufgerufen werden. In diesem Beispiel ist der Use-Case jedoch überschaubar und kann im Haupt-Recipe abgebildet werden:

 1#
 2# Cookbook:: basic-webserver
 3# Recipe:: default
 4#
 5# Copyright:: 2018, The Authors, All Rights Reserved.
 6
 7# Install httpd package
 8yum_package 'httpd' do
 9  action :install
10end
11
12# Stage content
13template '/var/www/html/index.html' do
14  source 'index.html.erb'
15  owner  'root'
16  group  'root'
17  mode   '0755'
18end
19
20# Start and enable service
21service 'httpd' do
22  action [:enable, :start]
23end

Ähnlich wie in der InSpec-Definition werden hier wieder Ressourcen referenziert: yum_package, template und service. Während Inspec die zu verwendete Paket-Architektur selbst erkennt, muss diese bei Recipes konkret angegeben werden. Da CentOS die YUM-Paketverwaltung nutzt, kommt yum_package zum Einsatz.

Die template-Ressource platziert eine Datei-Vorlage unter Berücksichtigung von Datei-Berechtigungen und ersetzt Makros - sofern nötig - durch Variablen oder Ruby-Code. Das kann vor allem bei komplexen Konfigurationsdateien von großem Nutzen sein. So muss man IP-Adressen und andere host-spezifischen Informationen nicht hart kodieren, sondern kann diese dynamisch laden.

Abschließend wird die service-Ressource dazu verwendet, den installierten Webserver augenblicklich und bei jedem Systemstart zu starten.

Note

Hinweisen zu weiteren Ressourcen finden sich in der offiziellen Dokumentation

Die Vorlage für die Standard-Seite des Webservers muss noch erstellt werden - hierfür wird eine entsprechende Datei im noch zu erstellenden templates-Ordner gespeichert:

1mkdir templates ; echo 'Hello World' > templates/index.html.erb

Anschließend lässt sich mit dem folgenden Aufruf eine erneute Anwendung des Cookbooks erzwingen:

 1$ kitchen converge
 2...
 3       Converging 3 resources
 4       Recipe: basic-webserver::default
 5         * yum_package[httpd] action install
 6           - install version 0:2.4.6-80.el7.centos.1.x86_64 of package httpd
 7         * template[/var/www/html/index.html] action create
 8           - create new file /var/www/html/index.html
 9           - update content in file /var/www/html/index.html from none to d2a84f
10           --- /var/www/html/index.html  2018-09-13 17:58:15.092708946 +0000
11           +++ /var/www/html/.chef-index20180913-3554-be1pv.html  2018-09-13 17:58:15.091708900 +0000
12           @@ -1 +1,2 @@
13           +Hello World
14           - change mode from '' to '0755'
15           - change owner from '' to 'root'
16           - change group from '' to 'root'
17           - restore selinux security context
18          service[httpd] action enable
19           - enable service service[httpd]
20          service[httpd] action start
21           - start service service[httpd]

Hier ist klar zu erkennen, dass Chef die definierten Ressourcen wie gewünscht umsetzt.

Die anschließende erneute Auditierung liefert nun zufriedenstellendere Eregebnisse:

 1$ kitchen verify
 2...
 3  System Package httpd
 4 ✔  should be installed
 5  Port 80
 6 ✔  should be listening
 7  http GET on http://localhost
 8 ✔  status should cmp == 200
 9
10Test Summary: 3 successful, 0 failures, 0 skipped

Drei Tests wurden erfolgreich durchgeführt, klasse! Der geschriebene Recipe-Code erfüllt somit die vorher definierten Soll-Kriterien. In einer nächsten Iteration ließen sich Kriterien und Code weiter konkretisieren.

Die vorhin aufgelisteten Test-Szenarien haben nun einen gültigen Zustand:

1$ kitchen list
2Instance          Driver   Provisioner  Verifier  Transport  Last Action    Last Error
3default-centos-7  Vagrant  ChefSolo     Busser    Ssh        Verified       <None>

In einem agilen Umfeld wäre das nun der Zeitpunkt, den geänderten Cookbook-Code in die Versionsverwaltung einzureichen und somit verwaltete Infrastruktur zu aktualisieren. Bei der Generierung von Cookbooks übernimmt Chef auch die Erstellung eines Git-Repositories, sodass der direkten Versionierung nichts im Wege steht:

1$ git add *
2$ git commit -m "Initial commit"

Nach der Ausführung von Tests lassen sich die temporär erzeugten Ressourcen, in diesem Fall die CentOS-VM, wie folgt löschen:

1$ kitchen destroy

Ausblick

Wir haben soeben unser erstes Chef-Cookbook erstellt, Akzeptanzkriterien definiert und in einem Recipe umgetzt - super! Mit den vorgestellten Werkzeugen lassen sich zeitsparend auch komplexe Konfigurationen auf mehreren Plattformen parallel testen - für agile, DevOps-getriebene Umgebungen ein nahezu essentielles Vorgehen. Abschließend wäre noch anzumerken, dass es noch einige Dinge in diesem Szenario gibt, die man optimieren könnte:

  • Recipe könnte distributionsunabhängiger gestaltet werden, um einen ähnlichen Webserver beispielsweise auch auf openSUSE bereitstellen zu können
  • Man könnte einige zu überprüfende Instanzen über Cloud-Ressourcen anstatt über Vagrant bereitstellen
  • Es könnten Security-Checks integriert werden, um unsichere Standard-Konfigurationen zu verhindern (DevSec bietet hier hervorragende Profile und Cookbooks zur automatischen Korrektur an)
  • Es könnten weitere Inhalte platziert werden, beispielsweise eine vollständige Datenbank inklusive PHP-Modul
  • Der gesamte Prozess könnte in einer CI-/CD-Pipeline abgebildet werden, beispielsweise um die Cookbooks nach erfolgter Code-Analyse auf einen Chef-Server zu übertragen

Die Möglichkeiten sind also groß - dieser Artikel sollte lediglich einen Überblick und Vorgeschmack bieten. Happy converging! 🙂

Übersetzungen: