Artikel

22.02.2017 von Dr. Silke Horn

Continuous Integration und Continuous Development bei einer mittelständischen Bank

In unserem Projekt zum Internet-Banking bei einer mittelständischen Bank haben wir es mit einer ziemlich großen Anzahl von Servern zu tun. Insgesamt waren (und sind noch) etwa 50 (virtuelle) Server neu aufzusetzen.

Dabei sind auf jedem dieser Rechner unter anderem folgende Aufgaben zu erledigen:

  • Installation und Konfiguration von Standardsoftware (Webserver, Application Server, Java, ...)
  • Installation und Konfiguration von Produkten (HBCI-Server, MQ-Server, Portalserver, ...)
  • Einrichten und Verwalten von Benutzern und Gruppen
  • Einrichten und Verwalten von Services
  • Deployment (und Konfiguration) von Eigenentwicklungen

Die Rechner sind jeweils zu sogenannten "Strängen" zusammengeschaltet. Jeder Strang gehört zu einer Umgebung und bildet alle (in der jeweiligen Umgebung) relevanten Teile des Internet-Banking-Systems ab und besteht unter anderem aus jeweils einem Rechner für Webserver, Portalserver, HBCI-Server und Datenbank-Server.

Wir haben die folgenden Umgebungen:

  • Jeder Dienstleister hat eine Entwicklungsumgebung.
  • Die verschiedenen Entwicklungsstände werden zum Release in der Integrationsumgebung. zusammengeführt. 
  • Nach erfolgreichen Entwicklertests wird der Stand in die Testumgebung übernommen, wo das System durch die Fachabteilung getestet wird. 
  • Schließlich erfolgt die Übernahme in die Produktivumgebung durch den internen Betrieb der Bank.

Die Entwicklungs- und Integrationsumgebungen bestehen dabei aus je einem Strang; zur Gewährleistung der Ausfallsicherheit gibt es in der Test- und Produktivumgebung jeweils zwei parallele Stränge.

Status Quo Pre-Puppet

Bisher wurden alle Server von Hand administriert. Auslieferung von Konfigurationsänderungen erfolgten über Prosa-Beschreibungen im Releaseletter (z. B. "in der Datei [...] ungefähr in Zeile [...] folgendes einfügen: [...]"), teilweise auch durch Verweise auf weitere Dokumente.

Diese Anleitungen für jeden Rechner umzusetzen, ist natürlich extrem viel manuelle, stupide und vor allem fehlerträchtige Arbeit. Außerdem führte dieses Vorgehen auf Dauer zu einer Divergenz der verschiedenen Umgebungen.

Enter: Puppet und Hiera

Im aktuellen Projekt wurde vieles von dieser manuellen Arbeit automatisiert und ich will in diesem Artikel einen Highlevel-Überblick darüber geben, wie wir das gemacht haben.

Die virtuellen Maschinen werden uns vom Betrieb der Bank mit einer Minimalinstallation bereitgestellt.

Von uns gibt es zunächst einen custom puppet fact, der die Rolle des Rechners angibt (also z. B. webserver, portalserver, ...). Das geschieht entweder anhand des Rechnernamens oder (mit oberster Priorität) anhand des Inhalts einer bestimmten Datei. Auf den Umgebungen mit parallelen Strängen gibt es außerdem einen Fact zur Bestimmung des jeweiligen Strangs. Die Fakten Rolle, Umgebung und ggf. Strang bestimmen somit, welchen Zustand der Rechner haben soll.

Unsere Module

Unsere Puppet-Module übernehmen den größten Teil der oben beschriebenen Aufgaben.

Konfigurationsdateien mit umgebungsspezifischen Parametern (z. B. Passwörter, URLs, ...) sind als Template im Puppet-Modul enthalten. Damit es auch mit wenig bis keinen Puppet-Kenntnissen leicht möglich ist, Konfigurationen anzupassen, haben wir bevorzugt komplette Konfigurationsdateien (im files- oder templates-Ordner) abgelegt und darauf verzichtet, Dateiinhalte innerhalb der Manifest-Dateien anzugeben oder Dateien gar von Puppet aus Fragmenten zusammenbauen zu lassen, wie es etwa mit dem concat-Modul möglich ist.

Versionsverwaltung und Merge-Strategie

Unsere Puppet-Module sind in einem git-Repository mit folgender Ordnerstruktur eingecheckt:

.
|-- hiera.yaml
|-- hieradata
|-- manifests
`-- modules

Es gibt je einen git-Branch für jede der oben aufgeführten Umgebungen. Diese sind auf dem Puppet Master alle parallel im environments-Ordner von Puppet geklont und werden über einen Cron-Job alle fünf Minuten aktualisiert. Der /etc/puppet-Ordner auf dem Master sieht ungefähr so aus:

.
|-- environments
|   |-- common        (enthält Module, die vom Rechnerbetrieb der Bank bereitgestellt werden; Versionsverwaltung durch den Betrieb)
|   |-- dev-iteratec  (Klon des Branches dev-iteratec, Entwicklungsbranch von interatec)
|   |-- dev-other     (Klon des Branches dev-other, Entwicklungsbranch eines anderen Dienstleisters)
|   |-- integration   (Klon des Branches integration)
|   |-- production    (Klon des Branches production)
|   `-- test          (Klon des Branches test)
|-- hiera.yaml -> environments/dev-iteratec/hiera.yaml
|-- modules           (enthält externe Module, die mit puppet install installiert wurden; nicht unter Versionsverwaltung)
`-- puppet.conf

Zum Release erfolgen Merges analog zum oben beschrieben Vorgehen:

Die Branches dev-<dienstleister> werden in den Branch integration gemerged. Nach erfolgreichen Entwicklertests erfolgt ein Merge von integration nach test. Nach erfolgreichen Abnahmetests durch die Fachabteilung erfolgt schließlich der Merge von test nach production. Außerdem wird abschließend "rückwärts" ein Merge von integration in die Entwicklungsbranches durchgeführt.

Hiera, Passwörter und Geheimhaltung

Umgebungsspezifische Parameter wie Passwörter oder URLs werden mit Hiera verwaltet und beim Puppet Run in die Puppet-Template-Files eingesetzt. 

Dabei werden alle Parameter der Entwicklungs-, Integrations- und Testumgebung sowie nicht-geheime Produktionsparameter ins git-Repository eingecheckt. Für die Produktionsumgebung gibt es ein zusätzliches Hiera-File secure.yaml, das nur auf dem Puppet Master liegt, vom Betrieb der Bank verwaltet wird (somit braucht niemand sonst Zugriff auf Produktionspasswörter!) und alle Passwörter für Produktion enthält.

Puppet Runs

Puppet Runs werden auf dem Integrationsstrang automatisch, periodisch ausgeführt, auf allen anderen Strängen manuell. Auf der Entwicklungsumgebung ist das sinnvoll, damit man Konfigurationsänderungen oder Patches manuell testen kann, bevor man sie in die Puppet-Module übernimmt.

Auf Produktion brauchen wir die volle Kontrolle darüber, wann genau ein Release eingespielt wird. (Bei der Frequenz, mit der das momentan und in absehbarer Zukunft geplant ist, ist das auch okay so.) Die Testumgebung soll schließlich genauso verwaltet werden wie Produktion.

Der Puppet Master

Der Puppet Master wird (zurzeit) nur für unser Projekt benutzt. Er wurde zu großen Teilen von uns konfiguriert und wird jetzt vom Betrieb der Bank verwaltet; momentan noch manuell, für die Zukunft wäre aber ein "Master Master" wünschenswert, der "unseren" Master (und ggf. weitere Master-Server für andere Projekte) verwaltet.

Auslieferung von entwickelten Artefakten

Die von uns selbst entwickelten Artefakte (Liferay-Plugins und Webapps) werden auf unserer Entwicklungsumgebung automatisch nach jedem Build vom Build-Server deployt. Auf der Integrationsumgebung werden ebenfalls vom Build-Server festgelegte Versionen der Artefakte (von allen Dienstleistern) aus dem Artefakt-Repository deployt.

Für die Test- und Produktionsumgebung erfolgt die Auslieferung über ein eigenes Puppet-Modul, das die neuen Artefakte im files-Ordner enthält, vom Jenkins gebaut und vom Betrieb auf den Puppet Master kopiert wird.