Einstellungen zentral verwalten

Non-invasives Konfigurationsmanagement für Java-Applikationen

Jens Saade

Wir kennen das Prinzip der Trennung von wiederverwendbaren oder spezifischen Informationen. Wir wenden es in der Softwarearchitektur, im Softwaredesign oder auch beim Paketieren von Applikationen an. Auch im Konfigurationsmanagement spielt dieser Grundsatz eine entscheidende Rolle. Wie eine flexible Konfiguration von Applikationen mit Continuous Delivery mit dem Central Configuration Repository gelingen kann.

Konfigurationen sind häufig umgebungsspezifisch, die konfigurierten Artefakte meist jedoch nicht. Auch die Installationen von Applikationen laufen eigentlich immer nach dem gleichen Muster ab: Bezug des Artefakts, Entpacken oder Deployen, Konfigurieren und dann starten. Lediglich bei der Konfiguration des Artefakts greifen wir fast immer ein: den ein oder anderen Port einstellen, Tokens für externe Zugänge speichern oder Passwörter für den Administrator ändern. Also gleicht doch eine Installation der anderen, oder etwa nicht?

In diesem Artikel wollen wir den Aspekt Automatisierung im Konfigurationsmanagement beleuchten und einen alternativen Ansatz zu den gängigen Vorgehensweisen näher bringen. Wir beginnen dazu mit der Vorstellung des non-invasiven Konfigurationsmanagements und zeigen im weiteren Verlauf die Möglichkeiten anhand eines Praxisbeispiels auf: dem Rollout von Konfigurationen in einer Continuous Delivery.

Non-invasives Konfigurationsmanagement

Die Automatisierung von Installationen ist heute schlichtweg der Standardfall. Tools wie Puppet und Chef sorgen für den nahezu reibungsfreien Ablauf bei Erstinstallationen und Updates. Wir können ganze Verzeichnisse mit benötigten Einstellungen ausrollen oder Datenbanken vorab bestücken – oder wir checken unsere umgebungsspezifischen Property-Dateien direkt aus dem SCM aus.

Wie auch immer wir vorgehen, wir tun mindestens zwei Dinge: Wir liefern benötigte Informationen direkt auf das Zielsystem aus und wir verändern damit die lokale Umgebung. Wenn wir nun unseren Blick heben und eine Applikationslandschaft aus der Vogelperspektive betrachten, fällt uns zudem auf: Wir konfigurieren Applikationen vielleicht projektspezifisch oder im Verbund, jedoch selten applikationsübergreifend auf dieselbe Art und Weise. Für operative Pflegeprozesse bedeutet dies vielleicht sogar eine differenzierte Behandlungsweise der einzelnen Installationen. Wie können wir eine einheitliche Herangehensweise erreichen und vielleicht sogar ein wenig mehr Flexibilität gewinnen?

Der nachfolgende Artikel zeigt exemplarisch einen alternativen Weg auf: Wir drehen den Spieß um und belassen die Konfigurationen in einer zentral administrierbaren Instanz, aus der sich die Applikationen selbstständig bedienen: dem Central Configuration Repository (CCR).

Sicherlich ist dieser Weg mit Einschränkungen zu genießen und längst nicht überall anwendbar. Wir können beispielsweise keine Dateistrukturen ausrollen oder Datenbanken initialisieren. Für viele Szenarien wird dieser non-invasive Ansatz jedoch neue Möglichkeiten bieten.

Motivation und Anforderungen

Wir möchten unsere Applikationslandschaft universeller konfigurieren. Dazu basiert die Form unserer Konfiguration grundlegend auf klassischen Key-Value-Paaren, dem wohl einfachsten gemeinsamen Nenner. Konfigurationen sollen dabei weiterhin grundlegend automatisiert bereitgestellt werden, aber kontrollierte Änderungen unter bestimmten Konditionen zulassen. Benutzern soll die Möglichkeit für selektive Eingriffe eingeräumt werden, denn Anpassungen sind immer wieder nötig – z. B. nach dem Deployment einer Applikation in eine Testserverfarm. Hierzu sind sowohl ein ausgefeiltes Rechte- und Rollenmodell für den Zugriff im CCR als auch eine Versionierung oder ein Changelog der vorgenommenen Änderungen wichtig.

Auch möchten wir die grundlegende Möglichkeit haben, ein und dieselbe Konfiguration für mehrere Konsumenten bereitzustellen und damit den Nutzungsgrad zu erhöhen. Beispielweise kann ein Entwickler bereits während der Implementierung seine Konfiguration mit den Kollegen teilen, sie erweitern und anpassen und sie dann schlussendlich einem Release zuführen.

Hierarchische Konfigurationen ermöglichen eine Wiederverwendung von Key-Value-Paaren. Wir können damit unnötige Redundanzen vermeiden und erzielen eine effektive Vererbung von allgemeingültigen bis hin zu umgebungsspezifischen Einstellungen. Dabei können wir – analog zu Java – das Überlagern von Key-Value-Paaren entweder erlauben oder verbieten.

Konsumierende Applikationen

Was bedeutet nun non-invasiv in diesem Kontext? Wir wollen möglichst kein weiteres API benutzen, sondern die Laufzeitumgebung unserer JVM gezielt mit Properties bestücken. Die Applikation bezieht dann ihre Einstellungen aus dem Java-Systemkontext.

Das Mittel der Wahl ist für JVMs die Nutzung eines Java-Agents. Die eigentliche Applikation hat keine Kenntnisse oder Abhängigkeiten, und der Agent kann projektübergreifend seine Arbeit verrichten: Er verbindet sich zum CCR via HTTP, liest die Konfiguration über einen Web Service aus und injiziert anschließend die Key-Value-Paare in den Systemkontext (Abb. 1). Letzteres kann zu verschiedenen Zeitpunkten geschehen:

  • beim Bootstrapping der Applikation
  • nach Bedarf zur Laufzeit
  • durch zyklische Injektion per Timer
saade_1

Abb.1: Injektion in den Systemkontext der JVM

Je nach Kritikalität ist ein lokales Caching der bezogenen Konfigurationen sicherlich sinnvoll. Doch wie ist eine Identifikation der korrekten Konfiguration im CCR möglich? Hier gibt es mehrere Optionen: Wir können unserer Applikationsinstanz z. B. eine Konfigurations-ID mitgeben oder einen systemabhängigen, eindeutigen Schlüssel generieren, den wir dann im CCR eine Konfiguration zuordnen.

Identifiziert sich nun die Applikation am CCR, so können die entsprechenden Key-Value-Paare bezogen werden. Denkbar wäre auch eine erweiterte Sicherung und Authentifizierung mittels HTTPS-Client-Zertifikat, um die Konfigurationen zu schützen. Die Applikation selbst kann nun über gängige Mechanismen den konfigurierten Umgebungskontext auslesen und einsetzen.

Praxisbeispiel: Rollout von Konfigurationen in einer Continuous-Delivery-Umgebung

In dem Buch „Continuous Delivery“ beschreiben Jez Humble und Dave Farley den Einsatz eines CCR für die Build-Pipeline (Abb. 2) wie folgt: „The most efficient way to manage configuration is to have a central service through which every application can get the configuration it needs.“ (siehe Humble, Jez; Farley, Dave: „Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation“, Addison-Wesley 2010). Der eigentliche Schlüssel liegt hier nicht unbedingt allein im Abspeichern und Beziehen der Konfiguration, sondern wohl vor allem in der Administration der Konfiguration und den damit verbundenen organisatorischen Prozessen.

Abb. 2: Build-Pipeline mit Konfigurationshierarchie

Abb. 2: Build-Pipeline mit Konfigurationshierarchie

CCR-Implementierung

Die damalig im Buch referenzierte Software „Escape“ wird leider nicht mehr weiterentwickelt. Daher haben wir kurzerhand eine eigene einfache Lösung implementiert: Das CCR mit dem Namen „discoBit“ ist frei erhältlich. Der passende Java-Agent sowie das Jenkins-Plug-in zum Publizieren der Konfigurationen sind auf GitHub verfügbar.

Continuous Integration und CCR im Einklang

Anhand eines einfachen Beispiels wird nachfolgend der Aufbau einer Continuous Delivery mit zentralem Configuration Repository exemplarisch aufgezeigt. Wir gehen dazu Schritt für Schritt den Aufbau der Prozesskette innerhalb der Commit Stage durch. Für unseren Versuchsaufbau benötigen wir einige lokale Komponenten und bedienen uns dazu der gängigen Open-Source-/Freeware-Systeme:

  • Jenkins CI mit Git-Plug-in
  • discoBit als CCR
  • Gradle für Setup und Start der Beispielapplikation

Für die Anbindung von discoBit an Jenkins verwenden wir das auf GitHub zur Verfügung gestellte Jenkins AutoConfig plugin und für die Abfrage des CCR nutzen wir den o. g. discoBit Java Agent. Als Beispielapplikation nutzen wir einen Fork des allseits bekannten Spring Petclinic Samples von Pivotal. Es ist ebenfalls auf GitHub erhältlich.

Konfiguration des Central Configuration Repositorys

Das CCR ist unser Bindeglied zwischen der zu konfigurierenden Petclinic-Applikation und dem Jenkins-CI-Server. Damit der Austausch der Konfigurationen zwischen Petclinic und Jenkins stattfinden kann, legen wir im CCR einen Application Space und mehrere Configuration Container an. Letztere bilden durch eine Parent-Child-Beziehung einen Konfigurationsbaum ab. Die Installation des CCR ist auf Discobit.com ausführlich beschrieben und wird hier nicht weiter erläutert.

Abb. 3: Hierarchie der Configuration Container

Abb. 3: Hierarchie der Configuration Container

Container 1 enthält als automated space die Konfigurationen aus dem Build-Prozess und wird von Jenkins vollautomatisch bestückt. Manuelle Zugriffe sind hier nicht erlaubt. Container 2 ist der so genannte shared space. In ihm speichern wir Key-Value-Paare, die nicht zwangsläufig Teil der Standardkonfiguration sind, aber einen gemeinsamen Nutzen für die spezifischen Konfigurationen haben. Der shared space leitet vom automated space ab. Container 3 und 4 sind environment spaces. Sie leiten wiederum vom shared space ab und überlagern Key-Value-Paare, die umgebungsspezifisch sind. Über das Environment können somit Varianten der Gesamtkonfiguration abgebildet werden.

Insgesamt haben wir nun also vier miteinander verknüpfte Konfigurationscontainer, die alle einen dedizierten Verwendungszweck haben und über eine UUID referenziert werden können. Durch die Vererbung der Key-Value-Paare bilden sie je nach Zugriffspunkt eine andere Konstellation ab.

Unsere Konfigurationskette (Abb. 3) automated > shared > env ist nur ein Beispiel. Der tatsächliche Aufbau kann vielfältig und beliebig komplex gestaltet werden.

Petclinic-Installation

Der Petclinic Fork enthält neben spezifischen Änderungen für das Praxisbeispiel ein zusätzliches Gradle-Build-Skript, das wir anstatt des existierenden Maven POMs benutzen. Ein Hinweis noch an dieser Stelle: Zum Experimentieren bietet sich ein eigener, temporärer Fork innerhalb von GitHub an.

Nach dem Klonen der Beispielapplikation von GitHub starten wir diese testweise und nutzen dazu den Gradle-Tomcat-Runner wie folgt:

gradle tomcatRun

Im Browser sollten auf http://localhost:9966 nun die bekannten Haustiere zu sehen sein. Damit ist die Petclinic aber noch nicht wirklich von außen konfigurierbar. Um dies zu erreichen, binden wir nun den Java-Agent ein, der in der discoBit Client Library enthalten ist. Da wir Gradle nutzen und der Prozess embedded gestartet wird, können wir den Agent auch über die Umgebungsvariable JAVA_OPTS laden (Listing 1). Im Normalfall würde man die Parameter innerhalb eines Startskripts mitgeben. Sie sind übrigens die einzigen Applikationsparameter, die wir mitgeben müssen.

Listing 1
# Konfiguration des Java Agents
export JAVA_OPTS="-javaagent:libs/discobit-client-java-0.7.1.jar \
-Ddiscobit.repository.url=http://127.0.0.1:9999 \
-Ddiscobit.repository.username=discobit \
-Ddiscobit.repository.password=javamagazin \
-Ddiscobit.cfg.uuid=509b2a96-216d-41bb-82b2-862eb0d7552d"

Wir fahren Petclinic testweise mit gradle tomcatRun hoch und sollten im Logfile nun die folgenden Zeilen wiederfinden:

INFO: com.v3rticle.oss.discobit.client.agent.DiscobitAgent execute
INFO: Reading discobit configuration from http://127.0.0.1:9999::509b2a96-216d-41bb-82b2-862eb0d7552d
...
INFO: injected 0 properties to system context

Die Verbindung zum CCR konnte also aufgebaut werden, und die Konfiguration mit der UUID war ebenfalls auffindbar. Es sind lediglich noch keine Properties im System verfügbar. Dies holen wir nun nach. Wir stoppen Petclinic und fügen ein Key-Value über das CCR UI hinzu:

gradle.tomcat.httpPort = 9900

Beim erneuten Start fährt Tomcat nun den HTTP-Connector auf Port 9900 hoch. Wie man sieht, können wir mit dem non-invasiven Ansatz also auch unsere Laufzeitumgebung beeinflussen.

Jenkins, stets zu Diensten

Nun gehen wir einen Schritt weiter und übertragen unser Szenario auf die Continuous Integration. Wir wollen damit ermöglichen, dass ein Entwickler-Commit automatisiert Konfigurationsänderungen in das Repository überträgt und damit implizit neue Properties in die Umgebungen einspielt.

Über das AutoConfig-Plug-in (Abb. 4) wird Jenkins nun in die Lage versetzt, ausgewählte Konfigurationsdateien in einen spezifizierten Configuration Container im CCR hochzuladen. Die Installation des nötigen Plug-ins erfolgt manuell durch Hochladen des .hpi-Files im Jenkins-Plug-in-Manager. Nach der Installation sollte das Plug-in grundlegend noch konfiguriert werden. Wir geben hier den Server-URL sowie die Zugriffsinformationen auf das CCR an.

Wir legen uns nun in Jenkins einen Job an, der zunächst das Repository oder den Fork klont. Auf einen Build wird an dieser Stelle verzichtet. Wir wählen als SCM Provider Git und stellen den Repository-URL entsprechend ein. Den Trigger „Build-Auslöser“ stellen wir auf „SCM System abfragen“. Schließlich fügen wir noch einen weiteren Buildstep hinzu: „Push Configurations to discoBit Configuration Repository“.

Das Beispielprojekt liefert in seinen Sourcen eine Datei namens automated.properties mit, die wir als Configuration-File referenzieren. Zu guter Letzt stellen wir noch den richtigen Configuration Container ein (UUID des Configuration Container 1: automated).

Abb. 4: discoBit-AutoConfig-Plug-in in Jenkins

Abb. 4: discoBit-AutoConfig-Plug-in in Jenkins

Nun sind wir startklar für einen Testlauf. Nach dem Ausführen des Jobs sollte die Konsole nun folgende Zeilen enthalten:

[    *** discoBit AutoConf ***    ] 
[ Configuration Repository Plugin ] 
[       http://discobit.com       ]  
[autoConfig] discobit url: http://127.0.0.1:9999 
[autoConfig] discobit user: discobit 
[autoConfig] configurations: src/main/resources/discobit-sample/automated.properties [autoConfig] uuid: e9d89e74-2078-4e2e-8c6f-ab6fbb8584f5 
[autoConfig] validating file:src/main/resources/discobit-sample/automated.properties [autoConfig] pushing: e9d89e74-2078-4e2e-8c6f-ab6fbb8584f5

Wir können nun unsere Konfiguration im UI des CCR überprüfen und finden die Properties in allen Konfigurationen (1 bis 4) wieder. Unter anderem haben wir nun über die Datei automated.properties den Kontext der Webapplikation verändert. Petclinic ist nun unter petclinicextended erreichbar. Abschließend nehmen wir die Datei automated.properties und fügen die folgenden Properties ein:

petclinic.developer.mode=true

Dann committen wir die neue Version in das Git Repository und starten den Jenkins-Job erneut. Ein Blick in die neu gestartete Petclinic zeigt uns, dass der Debug Mode eingeschaltet ist (Abb. 5).

Abb. 5: Die Petclinic im Debug Mode

Abb. 5: Die Petclinic im Debug Mode

Wir fügen nun weitere Properties in den shared container ein und demonstrieren, wie man das CCR auch für Feature Toggling einsetzen kann. Bislang wird die Suche nach Tierhaltern ausgeblendet. Wir fügen nun ein weiteres Key-Value-Paar hinzu, um die Suche einzublenden:

feature.petclinic.ownersearch.enabled=true

Nach dem erneuten Laden ist die Navigation entsprechend angepasst. Wie man sieht, können Properties vielfältig verwaltet und in der Applikation angewendet werden. Wir verändern hier nie das eigentliche Deployment und können durch zentrale Administration die Applikationen steuern.

Risiken und Nebenwirkungen

Der Zugriff auf die Konfigurationen an zentraler Stelle birgt sowohl Vor- als auch Nachteile: Die direkte Kontrolle über Applikationen in den Händen von Entwicklern, Testern und Administratoren ist sicherlich effizient und hilfreich und kann die Zusammenarbeit in Projekten verbessern. Mindestens genauso wichtig ist jedoch auch die Etablierung einer robusten Zugriffskontrolle und eventuell nötiger Workflows zur Abnahme der Konfigurationsänderungen.

Auch der technische Zugriff verlangt gegebenenfalls Anpassungen im Netzwerk und eine geeignete Positionierung in der Nähe der nutzenden Systeme. Fehlende Konnektivität kann ohne persistentes Caching schnell zum Risiko für die Betriebsbereitschaft ganzer Applikationscluster werden. Daher ist hier sicherlich Vorsicht angebracht. Letztlich ist eine geeignete Backupstrategie des Repositorys von Bedeutung. Die Wiederherstellung von Konfigurationen sollte zeitnah möglich sein.

Wie man sieht, will der Einsatz eines CCR insgesamt gut durchdacht sein. Bei allen Vorteilen, die der Ansatz mit sich bringt, wird gleichzeitig auch ein gutes Betriebskonzept ein Kernpunkt bei der Einführung sein.

Fazit

Wir haben gesehen, welche Möglichkeiten ein non-invasives Konfigurationsmanagement mit sich bringt und wie es mithilfe eines Central Configuration Repositorys umgesetzt werden kann. In der Praxis kann sich das Konzept vor allem in automatisierten Umgebungen behaupten und einen echten Mehrwert für beteiligte Systeme und Anwender bringen. Eine gute Strategie für den Betrieb ist dabei der Schlüssel für den erfolgreichen Einsatz.

Ob Configuration Sharing in der Entwicklung, Massenkonfiguration von Applikationsfarmen oder als Schaltzentrale für Feature Toggles – die Anwendungsfälle sind vielfältig und beliebig komplex. Lassen Sie doch einmal Ihre vergangenen Projekte Revue passieren – hätte der Ansatz dort Erfolg gehabt? Vielleicht sehen Sie auch Optimierungspotenzial in der derzeit etablierten Toolchain Ihres Unternehmens? Wenn der nächste Proof of Concept vor der Tür steht, haben Sie vielleicht die Chance, diesen Ansatz einmal in die Praxis umzusetzen. Ich hoffe, ich konnte dazu einen Denkanstoß liefern.

Geschrieben von
Jens Saade
Jens Saade
Jens Saade arbeitet als Solution Architect E-Commerce/Hybris bei der youngculture AG in Zürich und blickt auf eine langjährige Erfahrung im Bereich PIM und Retail zurück. Er ist zertifizierter Scrum Master und treibt die technischen Initiativen – wie z. B. Continuous Delivery – im Unternehmen voran. Twitter: @jenssaade E-Mail: j.saade@youngculture.com
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: