Java Magazin   8.2016 - Machine Learning

Erhältlich ab:  Juli 2016

Autoren / Autorinnen: 
Lars Schwabe ,  
Peter Roßbach ,  
Arne Limburg ,  
Sebastian Meyen ,  
Gernot StarkePeter Hruschka ,  
Simon ScholzLars Vogel ,  
Klaus KreftAngelika Langer ,  
,  
Michael Müller ,  
Jochen Mader ,  
Steffen HeinzlMarcel Florian ,  
Nicolas BärDaniel Takai ,  
Josef AdersbergerMario-Leander Reimer ,  
Michael SchäferBernd RehwaldtCarol Gutzeit ,  
Manfred Steyer ,  
Jan StamerRené Grohmann

Machine Learning lautet das Titelthema der vorliegenden Ausgabe des Java Magazins. Machine was? Müssen die jetzt auf jeden Trend aufspringen? Die Antwort lautet: Ja. Ich bin fest davon überzeugt und werde auch nicht müde, darauf hinzuweisen, dass wir dabei sind, die Sphäre relativer Stabilität, in der sich die Java-Plattform die vergangenen eineinhalb Jahrzehnte so prächtig entwickeln konnte, zu verlassen.

Die Geschäftsziele dieser Periode waren mehr oder minder bekannt, und es galt, eine leistungsfähige und weit verbreitete Technologie schrittweise besser zu machen. Durch gelungene Kooperation von Konzernen, Start-ups und der Community, einen vorbildlichen Prozess offener Innovation und nicht zuletzt die Macht von Open Source haben wir uns praktisch jährlich eines schönen Stück Fortschritts in der Java-Welt freuen können.

Dieser Innovationsmotor ist heute beileibe nicht zum Stillstand gekommen, und er läuft im Grunde weiterhin wie geölt. Jedoch hat sich eines geändert: Mehr und mehr Branchen steuern durch ein Meer der Unsicherheit. Geschäftsziele sind nicht mehr so eindeutig wie in der Vergangenheit und unterliegen vor allem im wachsenden Maße dem Risiko abrupten Wandels.

Und deshalb bedeutet digitaler Wandel für uns nicht etwa, dass wir uns auf neue Geschäftsziele und mithin etwa neue Technologien einstellen müssen, sondern vielmehr, dass der ständige Wandel zur neuen Normalität wird – und in manchen Branchen schon geworden ist.

Für Sie als Entwicklerinnen und Entwickler bedeutet dies, dass es gut ist, Experte in einer technischen Domäne zu sein, es aber auch gut ist, auf den Wandel gefasst zu sein. Und das erreicht man am besten, wenn man über den Tellerrand schaut, sich über Themenfelder informiert, die aktuell vielleicht nicht zum eigenen beruflichen Fokus gehören.

Machine Learning (ML) ist so ein Thema. Ich nehme mal an, dass die Praxisrelevanz für die allermeisten von Ihnen heute gegen Null geht. Was aber, wenn ML von Ihrem Kunden plötzlich als Innovationspotenzial in Finance, Logistik, Gesundheit oder Fertigung entdeckt wird? Dann sind Sie gewappnet in einem Markt, in dem Tempo einer der wichtigsten Wettbewerbsfaktoren ist. Viel besser noch: Sie können Ihrem Kunden die notwendigen Impulse verleihen, damit er mit intelligentem Einsatz innovativer Technologien dem Wandel nicht nur hinterher eifert, sondern ihn aktiv gestaltet.

Lesen Sie daher in diesem Java Magazin, was die Grundlagen von Machine Learning und Deep Learning sind, welche Implementierungen es gibt und wie Sie als Java-Entwickler die ersten konkreten Schritte gehen können.

Viel Erfolg beim menschlichen Erlernen von maschinellem Lernen wünscht

meyen_sebastian_sw.tif_fmt1.jpgSebastian Meyen, Chefredakteur

Website Twitter Google Xing

Selbstorganisierte Teams entwickeln sich häufig zu Hochleistungsteams. Insofern ist es gut und richtig, auf diese Organisationsform zu setzen. Sind aber Selbstorganisation und Führung nicht Gegensätze, die einander ausschließen? Nein, erläutert der Autor, sofern Führung nicht in die Selbstorganisation eingreift und die äußere Leitung vornimmt. Und überhaupt, es gebe unterschiedliche Stufen der Selbstorganisation, von managergeführten Teams bis hin zu autonomen Teams, je nachdem, ob die Teams nur die Durchführung der Aufgaben selbst organisieren oder aber auch die Arbeitsprozesse selbst gestalten, den Organisationskontext selbst festlegen bis hin zur eigenverantwortlichen Vorgabe von Zielen.

Kaltenecker erläutert ein Modell zur Führung solch selbst organisierter Teams. Dabei kann der Manager eher die Rolle eines Coachs einnehmen, so wie beim Fußball der Trainer. Diese Analogie nutzt der Autor mehrfach. Das beschriebene Modell besteht aus drei Ebenen: Werte, Kompetenzen und Werkzeuge zur Führung. Bei den Werten hat der Autor Commitment, Einfachheit, Respekt und Mut ausgewählt. Diese Auswahl sei subjektiv begründet, und er erläutert auch warum. Die Fokussierung auf Werte bringt ihn in die Nähe von Mike Burrows, der bei seiner Beschreibung von Kanban ebenfalls sehr stark auf Werte, wenn auch teilweise etwas andere, fokussiert.

Während er die Werte im Kapitel des Modells beschreibt, widmet Kaltenecker den unterschiedlichen Führungskompetenzen jeweils ein eigenes. Konkret geht es um das Fokussieren, Designen, Moderieren und Verändern. Der Bezug zum Team lässt sich aus der dritten Kompetenz wörtlich herauslesen. Am schwierigsten erscheint dies beim Designen. Hier geht es nicht um Kunst oder ein Architekturmodell. Hier geht es um das Design des äußeren Rahmens, des Kontexts selbst organisierter Teams, Schaffung der Grenzen, innerhalb derer sich das Team frei bewegen kann, Hilfestellung beim Modellieren der Arbeitsabläufe, z. B. Unterstützung agiler Methoden wie Scrum oder Kanban.

Bei den Veränderungen unterscheidet der Autor zwischen Veränderungen auf der persönlichen Ebene, auf der Teamebene sowie auf der Organisationsebene. Mindestens bei Letzterem wird klar, dass dies den Kontext eines einzelnen Teams überschreitet und hier Managerqualitäten gefragt sind.

Zu jedem Kompetenzkapitel folgt im letzten Teil des Buchs ein korrespondierendes Werkzeugkapitel. Beispielhaft für Designwerkzeuge seien hier die Abschnitte zu Mikromanagement, Teamcharter, Teamentwicklung, Standortbestimmung oder Makromanagement genannt. Werkzeuge stellt der Autor meist stichwortartig oder in tabellarischer Form vor. Noch häufiger als im Rest des Buchs wird dies von farbigen Fotos realer Situationen unterstützt. Während sich die ersten Teile des Buchs flüssig lesen lassen, ist dies durch die genannte Form im Werkzeugteil weniger der Fall. Hier geht es aber auch nicht darum, konzentriertes Wissen zu vermitteln. Vielmehr ist dies eher ein Nachschlagewerk für die praktische Durchführung bestimmter Aktionen.

Auch wenn viel von Führung die Rede ist, so wendet sich das Buch nicht (nur) an Führungskräfte, sondern auch an Personen, die dem Team fachlich und organisatorisch zur Seite stehen, wie Scrum Master oder Coaches. Lesenswert ist es aber auch für Teammitglieder, die nicht nur performen, sondern auch ihre Umgebung kennen lernen wollen.

dpunkt_selbstorganisierte_teams.tif_fmt1.jpg

Siegfried Kaltenecker

Selbstorganisierte Teams führen

Arbeitsbuch für Lean & Agile Professionals

244 Seiten, 32,90 Eurodpunkt.verlag, 2015ISBN 978-3-86490-332-8

Websysteme sind komplex und anfällig gegen unterschiedliche Störungen. Dennoch ist Hochverfügbarkeit ein häufig gewünschtes Qualitätsmerkmal, insbesondere im E-Commerce. Eine hohe Verfügbarkeit bedeutet jedoch auch hohe Kosten im Betrieb und der Produktion eines Websystems. In diesem Artikel beschreiben wir, wie sich die Verfügbarkeit definieren und berechnen lässt, und einige Methoden, um sie zu verbessern.

Verfügbarkeit gehört zum Qualitätsmerkmal der Widerstandsfähigkeit eines Systems (Resiliency). Tatsächlich wird sie in vielen Fällen als Synonym betrachtet [1]. In Kombination mit der Diskussion um Prüfbarkeit (Monitoring) und Wiederherstellbarkeit (Recoverability) ist es dem Webarchitekten möglich, verschiedene Maßnahmen zu definieren, die eine höhere Uptime eines funktionierenden Systems ermöglichen. Für uns bedeutet Resiliency, dass möglichst viele Störungen eines Systems bekannt und wir dagegen gewappnet sind. Die Konzepte rund um die Verfügbarkeit sowie das Zusammenspiel mit der Resiliency zeigt Abbildung 1.

takai_verfuegbarkeit_1.tif_fmt1.jpgAbb. 1: Konzepte rund um die Verfügbarkeit

Es gibt verschiedene Gründe, warum eine Komponente in unserem System versagen kann. Nebenläufigkeitsprobleme kommen häufig vor, denn Deadlocks, Race Conditions, Livelocks und Starvations sind schwierig mit Testfällen nachzustellen. Generell hat das Timing-Verhalten in verteilten Systemen einen Einfluss auf das Verhalten seiner Komponenten. Denken Sie nur an einen überlasteten Service, der nur wenige KB pro Minute liefert, und extrapolieren Sie dies auf das Verhalten Ihrer Anwendung.

Auch Designfehler können problematisch sein: Der falsche Entwurf eines Systems kann ein System zum Absturz bringen oder es so langsam machen, dass es sich schlicht nicht für die angedachte Aufgabe einsetzen lässt. Designfehler in der Architektur sind leicht zu finden, aber sehr schwierig – da kostspielig – zu beheben, denn die Behebung erfordert ein Redesign des Systems. Denken Sie an eine Spaghettiarchitektur, die nicht mehr beherrschbar ist, und führen Sie dann nachträglich eine Middleware ein. Die Kosten hierfür sind höher, als wenn die Middleware von Anfang an mit dabei gewesen wäre. Designfehler entstehen durch Fehler in der Erhebung und Analyse der gewünschten Qualitätsmerkmale und deren Abbildung auf die Architektur eines Systems.

Auch Überlast kann ein Softwaresystem zum Absturz bringen. Wenn die benötigte Kapazität die verfügbare Kapazität sprengt, wird sich das System erratisch verhalten. Langsame Antwortzeiten sind dann nur ein Symp­tom der Überlast. Eine bekannte Ursache für Überlast sind Denial-of-Service-Angriffe.

Sneaks [2] sind Fehler, die in bestimmten Zuständen eines verteilten Systems zu Problemen führen. Nicht alle Zustände eines Systems lassen sich aufgrund der großen Anzahl möglicher Kombinationen testen. Zudem bestehen verteilte Systeme aus verschiedenen Komponenten, die aus praktischen Gründen nicht in ihrer Produktivkonfiguration vorab getestet werden können. Eine interessante Frage ist, welche Maßnahmen der Prüfbarkeit oder des Monitorings getroffen werden müssen, um Sneaks zur Laufzeit erkennen zu können.

Varianz in der Laufzeitumgebung kann ein System kompromittieren. Sie entsteht beispielsweise durch das Versagen oder den Verschleiß physischer Komponenten unseres Systems. In einer Cloud haben wir keine Kontrolle über die unmittelbare Laufzeitumgebung und wir leiden am Noisy-Neighbor-Syndrom. Auch können wir nicht verhindern, dass unser Cloud-Provider mehr Kapazität verkauft, als verfügbar ist. Wir müssen damit rechnen, dass unsere Komponenten ihr Antwortverhalten plötzlich ändern. Eine Änderung am Laufzeitverhalten kann wiederum Nebenläufigkeitsprobleme zutage fördern, die auf der Testumgebung nicht auftreten. Uns ist mal ein Deadlock auf Produktion untergekommen, der nur auftrat, wenn das Logging eingeschaltet war.

Auch funktionale Fehler können ein System kompromittieren. Dabei müssen sie nicht unbedingt von einem Entwickler gemacht worden sein, es kann auch sein, dass falsche oder widersprüchliche Anforderungen zu solchen Fehlern führen. Bei Websystemen mit geringen Budgets für die Benutzerschnittstelle kann auch eine Fehlbedienung durch den Benutzer einen Absturz verursachen.

Methode 1: Verfügbarkeit definieren

Verfügbarkeit ist die Fähigkeit eines Systems, Fehler zu verbergen oder zu reparieren, sodass die kumulative Dauer eines Ausfalls nicht einen benötigten Wert über einem Zeitintervall übersteigt [3]. Es geht also darum, Ausfallzeiten möglichst zu minimieren. Der Grund für einen Ausfall (Outage) ist eine Störung (Fault). Störungen können vermieden, toleriert, behoben oder vorhergesagt werden. Je besser ein System in der Behandlung von Störungen wird, desto widerstandsfähiger wird es. Die Unterscheidung zwischen Ausfall und Störung erlaubt es uns, automatische Reparaturen sinnvoll ins Feld zu führen. Wenn Code eine Störung enthält, wir diese Störung aber automatisch erkennen und reparieren können, dann entsteht kein Ausfall.

Die Verfügbarkeit wird häufig als Prozentzahl angegeben, zum Beispiel 99,5 Prozent, die angibt, wie wahrscheinlich es zu einem gewissen Zeitpunkt ist, dass das System noch funktioniert. Tabelle 1 gibt einen Überblick über die verschiedenen Klassen von Verfügbarkeit. Hochverfügbarkeit meint üblicherweise five Nines, also 99,999 Prozent. Die Verfügbarkeit bestimmt sich aus der folgenden Formel [2]: V = MTBF / MTBF + MTTR

Verfügbarkeit

Ausfallzeit pro Quartal

Ausfallzeit pro Jahr

99 %

21,6 Stunden

3,65 Tage

99,5 %

10,8 Stunden

1,83 Tage

99,9 %

2,16 Stunden

8,76 Stunden

99,99 %

12,96 Minuten

52,6 Minuten

99,999 %

1,3 Minuten

5,26 Minuten

Tabelle 1: Verfügbarkeit nach Google [4]

Dabei bezeichnet MTBF die Mean Time Between Failures und MTTR steht für Mean Time To Repair. Fällt ein System beispielsweise monatlich aus (alle 720 h) und die Wiederherstellung benötigt vier Stunden, so ergibt sich für die Verfügbarkeit V = 720 / 720 + 4 = 99,45 %.

Logischerweise gehen alle Ausfälle eines Systems in die tatsächliche Verfügbarkeit ein, wobei man generell zwischen geplanten und ungeplanten Ausfällen unterscheidet. Da die Stakeholder eines Systems oft eine sehr hohe Verfügbarkeit fordern und diese auch gerne vertraglich festhalten möchten, sollte man genau definieren, welche Ausfälle in die Messung der Verfügbarkeit einfließen. Hierfür lassen sich Service Level Indicators definieren (SLI) und mit etwas Erfahrung Service Level Targets (SLT), also gewünschtes Minimum und Maximum eines SLIs. Zu einem SLI gehört eine exakte Definition der Metriken, der Messfrequenz sowie der Perspektive, aus der gemessen wird. In einem Service Level Agreement (SLA) können SLIs und SLTs vertraglich festgelegt werden. Oft enthält ein SLA auch Strafen, falls die SLTs nicht eingehalten werden, z. B. zehn Prozent Discount bei einer Unterschreitung von fünf bis zehn Prozent. Da während der Entwicklung eines Systems noch keine Daten über den Produktivbetrieb vorliegen, kann die vertragliche Verankerung von vernünftigen SLAs im Vorfeld schwierig sein. Deswegen sollten SLAs, die Strafen enthalten, besser nur auf der Grundlage von handfesten Daten geschlossen werden.

Denken Sie auch daran, dass die Messungen und Zusammenstellung der Indikatoren für den Nachweis der Einhaltung aufwendig sein können. Ist eine hohe Verfügbarkeit in Argentinien gefordert, so muss auch ein Indikator in Argentinien gemessen werden, d. h. Sie müssen dort einen Server für die Messung installieren. Wir haben bei uns hierfür auf allen Kontinenten der Erde einen Server installiert, der jedes von uns betriebene System misst. Damit erhalten wir wertvolle Daten über Performance und Verfügbarkeit und können bestimmte SLIs preiswert nachweisen. Bei Einsatz eines Content Delivery Networks (CDN) kann bei Messung von Edge und Origin Server sogar der Nutzen desselben bewiesen werden.

Generell sollte bei vertraglicher Fixierung die geplante Ausfallzeit keinen Einfluss auf die Verfügbarkeitsberechnung haben, da diese Ausfallzeiten zum Beispiel durch Deployments fremdgesteuert sind und Sie keinen Einfluss darauf haben. Denken Sie auch daran, dass Sie beim Hosting in fremden Rechenzentren oder in der Cloud oft keine Verfügbarkeitsvereinbarung gegenüber dem Betreiber geltend machen können und kalkulieren Sie das Risiko entsprechend. Die Prognose der Verfügbarkeit ist viel schwieriger als ihre nachträgliche Berechnung. Dies gilt auch für externe Dienste, die integriert werden sollen.

Methode 2: Maintenance Page

Die Hauptursache für Ausfälle sind in vielen Fällen Deployments. Solange das System aktualisiert wird, kann es nicht benutzt werden. Anstatt Requests eines Benutzers in einen hässlichen Time-out laufen zu lassen, schaltet man hierfür eine so genannte Maintenance Page (Wartungsseite) auf, die den Benutzer freundlich und konform zur Corporate Identity auf diesen Umstand aufmerksam macht. An das Design und den Prozess zur Aktivierung dieser Seite sollte man stets denken, es sei denn, Sie planen unterbrechungsfreien Betrieb auch bei Deployments, z. B. via Continuous Delivery [5] .

Methode 3: Reliability Mathematics

Um die Widerstandsfähigkeit eines Systems quantifizieren zu können, benötigen wir Wahrscheinlichkeitsrechnung und Statistik [2]. Wir können nicht wissen, wann ein System versagt, aber wir können versuchen, die Wahrscheinlichkeit davon zu bestimmen. Sammeln wir Daten über tatsächliche Ausfälle von Systemkomponenten, so können wir daraus eine Statistik erstellen, die unsere Annahmen über die Wahrscheinlichkeit stützt. Insbesondere können wir statistische Methoden verwenden, um die Varianz einer Umgebung zu bestimmen. In Cloud-Umgebungen kann die Varianz der Umgebung unserer Maschinen hoch sein (Noisy-Neighbor-Problem). Die Culling-Methode, bei der eine große Anzahl virtueller Maschinen gestartet und gemessen wird und dann alle bis auf die schnellste Maschine wieder entsorgt werden, ist ein Beispiel für den Einsatz statistischer Methoden zur Verbesserung der Qualität eines Systems.

Die Wahrscheinlichkeit, dass ein Ereignis eintritt, bezeichnen wir mit P(A). Sind zwei Ereignisse unabhängig, so beträgt die Wahrscheinlichkeit, dass beide gleichzeitig eintreten, nach der Produktregel P(AB) = P(A) * P(B).

Angenommen, wir haben ein System mit zwei aktiven Knoten, die Seiten ausliefern: Knoten A und B. Die Verfügbarkeit eines Knotens legen wir für dieses Beispiel arbiträr fest: P(A) = 95 % = 0,95 = P(B). Wie groß ist nun die Wahrscheinlichkeit, dass das gesamte System noch am Leben ist? Das Komplement der Wahrscheinlichkeit eines Ereignisses wird folgendermaßen notiert: P(A) = 5% = 0.05. Die Wahrscheinlichkeit, dass beide gleichzeitig ausfallen ist demnach P(AB) = P(A)P(B) = 0.05 ∙ 0.05 = 0,0025 = 0,25%.

Wenn wir also zwei Knoten mit 95 Prozent Verfügbarkeit verwenden, steigt die Verfügbarkeit des gesamten Systems auf 97,5 Prozent. Nicht schlecht! Allerdings benötigen wir noch einen Load Balancer (L), der eingehende Anfragen auf beide Knoten verteilt. Unser Load Balancer hat eine Verfügbarkeit von 99 Prozent. Wie hoch ist nun die Gesamtverfügbarkeit? Wir nennen den Verbund von Knoten A und B im Folgenden K. Die Wahrscheinlichkeit, dass nun entweder L oder K ausfallen, berechnet sich so: P(L+K) = P(L) + P(K) – P(LK) = 0.01 + 0.25 – (0.01 ∙ 0.25) = 2.575%

Die Gesamtverfügbarkeit wäre demnach 97,425 Prozent (Abb. 2). Dadurch, dass wir eine weitere Schicht hinzufügen, sinkt also die Verfügbarkeit. In einem Schichtensystem von Komponenten senkt jede Schicht die Verfügbarkeit des gesamten Systems, da keine Schicht eine Verfügbarkeit von 100 Prozent oder mehr haben kann.

takai_verfuegbarkeit_2.tif_fmt1.jpgAbb. 2: Rechenbeispiel für Verfügbarkeit

Methode 4: Komponentenverfügbarkeit verbessern

Die Verfügbarkeit einer Komponente bestimmt sich aus der physischen Laufzeitumgebung in Kombination mit der Qualität der Software. Physische Komponenten versagen auf gänzlich andere Weise als Software. Strom kann ausfallen oder der Serverschrank umkippen, es gibt Erdbeben und Wasserrohrbrüche. Kurz, die Möglichkeiten sind endlos. Aber sicher ist, dass Hardware versagt. Und ein einzelnes physisches System kann nie eine hundertprozentige Verfügbarkeit haben. Die Verfügbarkeit eines physischen Systems kann durch den Einkauf teurer Hardware erhöht werden, die eine längere und verlässlichere Lebensdauer hat.

Bezüglich der Software sprechen wir der Einfachheit halber vom gesamten Softwarestack einer Maschine. Wir gehen davon aus, dass dieser Stack in Gänze getestet wurde. Software fällt aufgrund von Fehlern in der Entwicklung aus und Vorhersagen von Verfügbarkeit basieren auf der Menge von gefundenen Fehlern [2]. John Musa hat das Musa-Modell [6] entworfen, bei dem aus den bei einem Testlauf gefundenen Fehlern bis zu einem Testabbruch die notwendige Zeit für weitere Tests zur Erreichung einer bestimmten Verfügbarkeit abgeleitet werden kann. Wir finden das hochinteressant, aber praktisch schwierig umzusetzen, da das Modell von richtigen Eingaben abhängt und einen bestimmten formalen Prozess im Testing voraussetzt.

Trotzdem gibt uns Musa einen wichtigen Hinweis, nämlich, dass die Verfügbarkeit von funktionalen Fehlern im System abhängig ist. Angenommen, wir haben unsere Defects nach Blocker, Critical, Major und Minor kategorisiert. Ein kritischer Fehler erfordert in unserem System ein neues Release. Für ein neues Release benötigen wir eine Stunde Downtime. Wir finden jede Woche zwei kritische Fehler, also haben wir zwei Stunden Downtime pro Woche. Nun können wir über die Variablen Zeit pro Release und Anzahl kritischer Fehler die Verfügbarkeit steuern. Für zwei Stunden Ausfallzeit ergibt sich eine Verfügbarkeit von 98,81 Prozent für die Software. In Kombination mit der Hardwareverfügbarkeit von 99,999 Prozent kommen wir dann auf P(S+H)=P(S)+P(H)–P(SH)=1.19+0.001–(1.19∙0.001)=98.81019%.

Offenbar hat die Hardware also nur einen sehr geringen Einfluss auf die Verfügbarkeit, weswegen wir sie auch ignorieren können und uns nur auf die Qualität unserer Software konzentrieren können. Wie wir gesehen haben, können wir nun die Anzahl kritischer Fehler reduzieren oder die Deployment-Zeit verkürzen. Um dies zu erreichen, wählen wir einen iterativen Verbesserungsprozess, um schrittweise die Perfektionierung unseres Produkts zu erreichen [7]. Dies folgt der Kaizen-Philosophie, die in Japan nach dem zweiten Weltkrieg eine Qualitätsrevolution begleitete. Wir analysieren also im Team unseren Prozess und die Qualität und kommen so zu stetigen kleinen Verbesserungen, die am Ende zu weniger Fehlern führen.

Methode 5: Load Balancing Strategies

Load Balancer sind essenzielle Komponenten einer hoch verfügbaren Umgebung. Sie stellen sicher, dass die Last von eingehenden Anfragen auf verschiedene Systeme verteilt wird und dass Anfragen nicht an überlastete Nodes weitergeleitet werden. Die Nodes innerhalb eines Load Balancers werden Pool genannt. Für jeden Node im Pool muss stetig geprüft werden, ob die Kapazität ausgeschöpft ist. Dazu eignet es sich, eine spezifische Health-Check-Seite in der Webapplikation zu bauen, die einen Systemcheck durchführt. Wenn der Health Check negativ ausfällt, kann der Node aus dem Pool entfernt werden.

Es können verschiedene Strategien angewendet werden, um die Last auf die Nodes im Pool zu verteilen. Round Robin ist eine verbreitete Methode, die auch bei DNS eingesetzt wird. Anfragen werden zirkulierend an Nodes im Pool verteilt. Die Kapazität der einzelnen Nodes wird dabei nicht berücksichtigt, und Anfragen können unfair verteilt werden. Weighted Round Robin adressiert dieses Problem und gibt den Nodes im Pool eine Gewichtung. Nodes mit mehr Kapazität (durch bessere Hardware) werden höher gewertet. Die Anfragen werden dann anhand der Gewichtung an die Nodes im Pool verteilt. Least Connections zählt die offenen Verbindungen zu den Nodes und leitet eingehende Anfragen an den Node mit den wenigsten Verbindungen weiter. Ähnlich der Round-Robin-Methode kann Weighted Least Connections die Kapazität der einzelnen Nodes berücksichtigen. Least Response Time misst die durchschnittliche Antwortzeit der Nodes im Pool und leitet neue Anfragen an den Node mit der besten Zeit und den wenigsten offenen Verbindungen. Diese Methode berücksichtigt die Latenz zum Zielsystem. Entscheidend für die Wahl der Load-Balancing-Methode sind vor allem zwei Faktoren: die Kapazität der einzelnen Systeme und die Latenz zwischen den Systemen.

Wenn eine Webapplikation Session-relevante Daten im Memory von einem Node speichert, z. B. den Warenkorb eines Benutzers, muss der Load Balancer sicherstellen, dass alle Requests von diesem Benutzer an den gleichen Node weitergeleitet werden. Dies wird Sticky Sessions genannt und als Session-Cookie implementiert. Sticky Sessions sind mit Nachteilen verbunden. Einerseits kann eine unfaire Lastverteilung entstehen, da die Load-Balancing-Methode für alle Anfragen von einem Benutzer umgangen wird. Andererseits gehen Daten verloren, wenn ein Node aus dem Pool entfernt wird. Queue Draining kann verwendet werden, um einen Node langsam aus dem Pool zu entfernen. Es werden keine neuen Sessions mehr auf dem Node erstellt, und die bestehenden Verbindungen laufen langsam aus. Wenn ein Node aus dem Pool entfernt wurde, um z. B. ein Canary Deployment durchzuführen, sollte der Node langsam wieder in den Pool aufgenommen werden. Das heißt, die Last auf dem Node sollte iterativ vergrößert werden (Slow Ramp-up), da ein Big Bang oft zu Ausfällen führen kann, da z. B. ein Cache noch nicht warm ist.

Qualitätsszenarien

Um Maßnahmen zugunsten der Verfügbarkeit kommunizieren zu können, bieten sich folgende Qualitätsszenarien an. Qualitätsszenarien zur Verfügbarkeit drehen sich meistens um die Entdeckung von Störungen sowie gewünschte Ausfallzeiten des Systems. Ein paar Beispiele:

  • Eine Störung in einem Modul soll zur Laufzeit durch einen Eintrag im Log der Komponente gemeldet werden, damit der Logmonitor diese Störung an den Service-Operator melden kann.

  • Wird auf der Produktionsumgebung die Software aktualisiert, so kann der Service-Operator vorher auf dem Load Balancer manuell eine Wartungsseite schalten, die den Designvorgaben der Kommunikationsabteilung entspricht.

  • Ausfälle während der normalen Geschäftszeiten zwischen 7 und 20 Uhr von montags bis samstags sind unerwünscht, weswegen geplante Ausfälle außerhalb dieser Zeitfenster stattfinden sollten.

Fazit

Wir haben gesehen, dass sich Verfügbarkeit theoretisch berechnen lässt. Jedoch sind solche Prognosen schwierig, denn Softwaresysteme sind komplex und lassen sich nicht auf wenige Variablen reduzieren. Je nachdem, wie kritisch ein System ist, sollte mehr in die Sicherstellung von Verfügbarkeit investiert werden. Ist eine Unternehmung auf ihren Webshop angewiesen, so ist sie gut beraten, wenigstens einen zweiten Knoten für die Shopkomponente zu finanzieren. Dies erhöht auch die Kapazität und kann die Performance des Shops verbessern, und das steht in direkter Korrelation zum Erfolg des Systems.

baer_nicolas_sw.tif_fmt1.jpgNicolas Bär arbeitet als IT-Architekt bei der Unic AG in Zürich. Als Performanceenthusiast optimiert er Webapplikationen und engagiert sich im Open-Source-Umfeld.

takai_daniel_sw.tif_fmt1.jpgDaniel Takai ist Technologiemanager bei der Unic AG in Bern. Er ist dort für die Entwicklungsprozesse, Technologieentwicklung und Softwarearchitekturen verantwortlich.

Kubernetes (griechisch „Steuermann“) ist ein Open-Source-Projekt aus der Feder von Google und im Prinzip ein Applikationsserver der Ära Cloud: für Microservices und alle anderen Anwendungen, die sich in Container zwängen lassen und auf einem Cluster laufen. Anwendungen sollen dabei so skalieren, resilient und effizient zu betreiben sein wie diejenigen von Google. Ein großes Versprechen, das wir im Rahmen dieses Artikels beleuchten.

Kubernetes (kurz: K8s [1]) ist ein quelloffener Clusterorchestrierer, der maßgeblich von Google entwickelt wird und Mitte 2015 in der Version 1.0 erschienen ist. Das bedeutet, dass K8s für den Einsatz in Produktion freigegeben ist, was Google selbst und weitere Unternehmen wie die New York Times bereits tun. Rund um K8s hat sich mittlerweile die Cloud Native Computing Foundation (CNCF [2]) unter dem Dach der Linux Foundation formiert. K8s ist die CNCF-Referenzimplementierung eines Clusterorchestrierers. Damit stehen nun hinter K8s neben Google auch weitere namhafte Unternehmen wie Mesosphere, Cisco, IBM und Intel.

Doch was ist K8s genau? K8s ist ein Clusterorchestrierer, ein Applikationsserver, der Anwendungen auf einem potenziell sehr großen Cluster ausführt – auf einer Cloud. K8s agiert dabei auf der Abstraktionsebene von Anwendungen und ihren angebotenen Services, sitzt also exakt an der Schnittstelle zwischen Devs und Ops. Die Anwendungen sind dabei oft Microservices. Es sind jedoch auch andere Anwendungen gern auf K8s gesehen, Hauptsache man kann sie in einem Docker- oder rkt-Container verpacken, wie es auch für klassische JEE-Anwendungen gelingt.

K8s betreibt Anwendungen automatisch. Er besitzt eine Steuerschnittstelle (REST-API, Kommandozeile und Web-UI), mit der die Automatismen angestoßen werden können und der aktuelle Status abgerufen werden kann. Was K8s automatisiert:

  • Container auf dem Cluster ausführen

  • Netzwerkverbindungen zwischen Containern aufbauen

  • Persistenten Speicher (Persistent Volumes) für zustandsbehaftete Container bereitstellen

  • Konfigurationsparameter, Schlüssel und Passwörter definieren, ändern und bereitstellen

  • Roll-out-Workflows wie Canary Roll-outs automatisieren

  • Performance und Verfügbarkeit von Serviceendpunkten überwachen und Container bei zu geringer Performance skalieren (Auto-Scaling) und im Fehlerfall reschedulen (Self-Healing)

  • Services managen: Service Discovery, Naming und Load Balancing

Anwendungen deklarativ beschreiben

K8s macht all das auf Basis einer Anwendungsblaupause, die beim Deployment einer Anwendung mit überreicht wird. Diese Anwendungsblaupause beschreibt den Zielzustand einer Applikation im Cluster. Aufgabe von K8s ist es dann, das Cluster vom aktuellen Zustand in den Zielzustand zu überführen. Die Anwendungsblaupause macht dabei keine Annahmen über das Cluster, sondern stellt lediglich Ressourcenforderungen. Somit sind Anwendungen portierbar: Egal ob K8s nur einen Laptop, eine Private Cloud oder gar die große weite Public Cloud unter seinen Fittichen hat, solange genügend Ressourcen zur Verfügung stehen, läuft die Anwendung ohne Anpassung überall. Eine Anwendungsblaupause besteht bei K8s aus den folgenden Elementen, die über YAML- oder JSON-Dateien beschrieben werden (Abb. 1):

  • Pod: Gruppe an Containern, die auf demselben Knoten laufen und sich eine Netzwerkschnittstelle inklusive einer dedizierten IP, persistente Volumes und Umgebungsvariablen teilen. Ein Pod ist die atomare Scheduling-Einheit in K8s. Ein Pod kann über sogenannte Labels markiert werden. Das sind frei definierbare Schlüssel-Wert-Paare.

  • Service: Endpunkt unter einem definierten DNS-Namen, der Aufrufe an Pods verteilt. Die für einen Service relevanten Pods werden über ihre Labels selektiert (z. B. role = apache, env != test, tier in (web, app)).

  • ReplicaSet (bzw. veraltetes Konstrukt Replication Controller): Stellt sicher, dass eine spezifizierte Anzahl an Instanzen pro Pod ständig läuft. Ist für Reaktionen im Fehlerfall, Skalierung und Roll-outs zuständig.

  • Deployment: Klammer um einen gewünschten Zielzustand im Cluster in Form eines Pods mit dazugehörigem ReplicaSet. Ein Deployment bezieht sich nicht auf Services, da diese in der K8s-Philosophie einen von Deployments unabhängigen Lebenszyklus haben.

adersberger_kubernetes_1.tif_fmt1.jpgAbb. 1: Elemente einer Anwendungsblaupause

Die Architektur von Kubernetes

K8s selbst ist eine verteilte Anwendung. Abbildung 2 zeigt die Architektur von K8s. Der Masterknoten ist das Gehirn des Clusters, die normalen Nodes die Muskeln. Der Master orchestriert das Cluster, auf den Nodes laufen die Pods und Services. Auf dem Masterknoten läuft ein API-Server, über den die K8s-Automatismen per REST-API angestoßen werden können. Er bietet die zentrale Administrationsschnittstelle. Der Controllermanager steuert die eigentliche Orchestrierung. Er führt Deployments und ReplicaSets (bzw. Replication-Controller) aus. Der Scheduler ist dafür zuständig, Pods auf die passenden Knoten zu verteilen. Er macht das entsprechend des Ressourcenbedarfs der Pods und den noch freien Ressourcen auf den Nodes. etcd ist ein verteilter konsistenter Konfigurationsspeicher – das Gedächtnis von K8s, in dem der aktuelle Clusterzustand verwaltet wird.

adersberger_kubernetes_2.tif_fmt1.jpgAbb. 2: Architektur von Kubernetes

Auf den Nodes laufen drei Komponenten: Der kube-­proxy, der sich um die Weiterleitung von Ser­vice­auf­rufen an die passenden Container kümmert; die Container-Engine, im Default ist es Docker, das Container ausführt; und das kubelet, ein Agent des Masters, der den Knoten verwaltet, steuert und mit dem Master kommuniziert. Neben K8s als Kernplattform ist mittlerweile ein breites Ökosystem an Werkzeugen, Erweiterungen und Frameworks rund um K8s entstanden:

  • Prometheus [3], Weave Scope [4] und sysdig [5] für das Monitoring und die Fehlerdiagnose

  • Helm [6] als Paketmanager für wiederverwendbare Applikationsteile

  • Maven- und Gradle-Plug-ins sowie ein Java-API zur Fernsteuerung von K8s (http://fabric8.io)

Erste Schritte: Hands-on Kubernetes

Der Schritt hin zu ersten Erfahrungen mit K8s ist ein leichter. Es stehen mehrere vorgefertigte K8s-Installationen zur Verfügung, z. B. über die Google-Container-Engine [7], für diverse Public IaaS Clouds oder als Vagrant-Boxen [8]. Unter dem URL https://get.k8s.io steht ein Shellskript für die automatische Installation bereit. Listing 1 zeigt ein Skript, mit dem ein lokales K8s-Cluster per Vagrant erstellt wird. Das funktioniert nur unter Linux und Mac, eine Installation unter Windows wird aktuell noch nicht unterstützt.

Listing 1

#!/bin/sh
export KUBERNETES_PROVIDER=vagrant
export KUBE_ENABLE_CLUSTER_MONITORING=none
curl -sS https://get.k8s.io | bash

Sobald die Installation erfolgreich abgeschlossen ist, kann anschließend über das kubectl.sh-Kommandozeilenwerkzeug mit dem Cluster interagiert werden (Listing 2). In Zeile 1 und 2 werden sowohl Informationen über das Cluster als auch die aktuell laufenden Pods, Services und Deployments abgefragt. In Zeile 4 legen wir ein Deployment und einen Service an und in Zeile 6 skalieren wir das Deployment von zwei auf fünf Replicas.

Listing 2

kubectl.sh cluster-info
kubectl.sh get pods,services,deployments
 
kubectl.sh create -f nginx-deployment.yml
kubectl.sh get pods,services,deployments
kubectl.sh scale deployment nginx – replicas=5
kubectl.sh get pods

Listing 3 zeigt die hierfür verwendete Anwendungsblaupause: einen nginx-Server in zwei Instanzen. Zusätzlich zum eigentlichen Deployment ist ein Service definiert, um auf die nginx-Instanzen über eine externe IP zugreifen zu können und um die Last auf die beiden laufenden Pods zu verteilen. Über das Templateelement im Deployment wird der Pod definiert. Dabei können auch mehrere Container mit angegeben werden. Das ReplicaSet wird implizit über die Anzahl der Replicas definiert.

Listing 3

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
        tier: web
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
    tier: web
spec:
  # external load-balanced IP if provider supports it
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: nginx
    tier: web

Die Kubernetisierung von Zwitscher

Im nächsten Schritt wollen wir nun unseren Zwitscher-Showcase aus den vorherigen Artikeln dieser Serie mithilfe von K8s orchestrieren. Der erste Schritt ist die Containerisierung: Hier werden die einzelnen Microservices in Docker-Container gepackt und per Docker Registry verfügbar gemacht. Listing 4 zeigt beispielhaft das Dockerfile für einen Zwitscher-Microservice, Listing 5, wie man daraus einen Container erzeugt und in eine Docker Registry lädt. Abbildung 3 zeigt alle Container sowie die verwendeten Basis-Images von Zwitscher.

adersberger_kubernetes_3.tif_fmt1.jpgAbb. 3: Dockerized Zwitscher

Listing 4

FROM qaware-oss-docker-registry.bintray.io/base/debian8-jre8
MAINTAINER QAware GmbH <qaware-oss@qaware.de>
 
RUN mkdir -p /opt/zwitscher-service
 
COPY build/libs/zwitscher-service-1.1.0.jar /opt/zwitscher-service/zwitscher-service.jar
COPY src/main/docker/zwitscher-service.* /opt/zwitscher-service/
 
RUN chmod 755 /opt/zwitscher-service/zwitscher-service.jar; chmod 755 /opt/zwitscher-service/zwitscher-service.sh

Listing 5

EXPOSE 8761
ENTRYPOINT exec /opt/zwitscher-service/zwitscher-service.sh
$ docker build -t zwitscher-service:1.1.0 .
$ docker tag <IMAGE_ID> qaware-oss-docker-registry.bintray.io/zwitscher/zwitscher-service:1.1.0
$ docker push qaware-oss-docker-registry.bintray.io/zwitscher/zwitscher-service:1.1.0

Bei der Containerisierung sollte ein Punkt dringend beachtet werden: Kenne dein Basisimage! So bringt z. B. das java:8-Docker-Basis-Image stolze 800 MB auf die Waage. Bei sechs Microservices-Containern ist das eine Menge Holz, der Download der Images durch K8s dauert später unnötig lange. Abhilfe schafft hier z. B. die dockerisierte und kubernetisierte Version von Alpine Linux [9] oder ein eigenes maßgeschneidertes Image wie in Listing 5 zu sehen.

Nun steht die eigentliche Kubernetisierung unserer Anwendung an. Hierfür teilen wir unsere Container auf Pods auf. Alle Container eines Pods teilen dasselbe Schicksal: Sie werden gemeinsam gestartet und gemeinsam skaliert. Stirbt ein Container, sterben alle. Welche Container man zusammen in einen Pod packt, ist eine Architekturentscheidung: Welche Container haben einen gemeinsamen Lebenszyklus? Welche Container sollten möglichst nah beieinander laufen, um Overhead zu vermeiden? Ein bekanntes Pattern ist das Sidekick-Pattern, bei dem neben einem funktionalen Container (wie einem Microservice) noch weitere Container laufen, die dessen Funktionalität anreichern (z. B. ein Monitoring-Agent oder ein Logextraktor).

Abbildung 4 zeigt die Pods sowie die definierten Services und Abhängigkeiten von Zwitscher in der Übersicht. Listing 6 zeigt die Definition für den Service und das Deployment unserer Zwitscher-Applikation.

adersberger_kubernetes_4.tif_fmt1.jpgAbb. 4: Kubernized Zwitscher

Listing 6

apiVersion: v1
kind: Service
metadata:
  name: zwitscher-service
  labels:
    zwitscher: service
spec:
  # use NodePort to be able to access the # port on each node
  type: NodePort
  ports:
  - port: 8080
  selector:
    zwitscher: service
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: zwitscher-service
spec:
  replicas: 3
  minReadySeconds: 30
  template:
    metadata:
      labels:
        zwitscher: service
    spec:
      containers:
      - name: zwitscher-service
        image: "qaware-oss-docker-registry.bintray.io/zwitscher/zwitscher-service:1.1.0"
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "750m"
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /admin/health
            port: 8080
          initialDelaySeconds: 90
          timeoutSeconds: 30
        env:
        - name: EUREKA_HOST
          value: zwitscher-eureka
        - name: JAVA_OPTS
          value: -Xmx196m

Der Service kann später direkt über seinen Namen angesprochen werden. Beim Typ stehen ClusterIP, NodePort und LoadBalancer zur Auswahl. ClusterIP exponiert den Service nur innerhalb des Clusters. Im Fall von NodePort werden die Containerports eines Pods auf jeden K8s Node exportiert. Bei Verwendung von LoadBalancer wird eine externe, lastverteilte IP für diesen Service akquiriert, sofern das vom Cluster unterstützt wird. Das Deployment definiert die Eigenschaften der Pods mit ihren Containern. In unserem Beispiel setzen wir die initiale Anzahl an Replicas auf drei. Über die Template Spec definieren wir das Docker Image, die benötigten Ressourcen, Ports, Liveness Probes und die Umgebungsvariablen unserer Container. Die Liveness Probes nutzt K8s, um herauszufinden, ob ein Pod erfolgreich gestartet ist.

Alle YAML-Dateien für das kubernetisierte Zwitscher sind im Zwitscher-GitHub-Repository zu finden [10]. Nun können wir die Deployments und Services in K8s per Kommandozeile erzeugen (Listing 7).

Listing 7

$ kubectl.sh create -f zwitscher-eureka/k8s-zwitscher-eureka.yml
$ kubectl.sh create -f zwitscher-config/k8s-zwitscher-config.yml
$ kubectl.sh create -f zwitscher-service/k8s-zwitscher-service.yml
...
$ kubectl.sh get deployments,pods
$ kubectl.sh scale deployment zwitscher-service --replicas=3
$ kubectl.sh get deployments,pods

K8s kennt derzeit keine Startabhängigkeiten und keine Startreihenfolge der einzelnen Deployments. Dennoch benötigt der Zwitscher-Microservice eine gewisse Spring-Cloud-Infrastruktur, um sauber zu starten, wie Eureka und den Configuration-Service. Eine Lösung für dieses Problem ist es, den Start der Services per Retry-Mechanismus resilienter zu gestalten (Listing 8). Spring versucht dann in definierten, immer länger werdenden Abständen den Configuration-Service zu erreichen. Erst wenn man eine maximale Anzahl an Versuchen erreicht, schlägt der Start fehl.

Listing 8

spring:
  cloud:
    config:
      enabled: true
      failFast: true
      retry:
        initialInterval: 1500
        maxInterval: 5000
        maxAttempts: 5
        multiplier: 1.5
      discovery:
        enabled: true
        serviceId: ZWITSCHER-CONFIG

Eine Alternative zu dieser Lösung ist es, nichts weiter zu tun: Sollte ein Service aufgrund von fehlenden Infrastrukturdiensten nicht starten, wird K8s nach einer gewissen Zeit den Dienst neu starten, da er nicht antwortet. Das passiert so lange, bis alle Dienste die benötigte Infrastruktur vorfinden und sauber starten. Das klingt zunächst merkwürdig, aber das System heilt sich quasi allein.

Für Zwitscher haben wir die gesamte Spring-Cloud-Infrastruktur eins zu eins auf K8s portiert. Alternativ dazu kann auch auf Teile der Spring-Cloud-Infrastruktur verzichtet und stattdessen die K8s-Infrastruktur genutzt werden. Der Netflix-Eureka-Server kann durch den K8s-DNS-Server ersetzt werden. Der Zuul-Edge-Server lässt sich außerdem durch Ingress von K8s ersetzen, etcd kann als Konfigurationsmechansimus genutzt werden. Es gibt ein Spring-Cloud-Projekt, das die K8s-Bausteine besser integrieren soll, um genau so etwas zu bewerkstelligen. Es befindet sich aktuell in einem sehr frühen Stadium und ist noch nicht für ernsthafte Projekte geeignet.

Fazit

Da K8s unter dem Dach der Linux Foun­dation weiterentwickelt wird, ist es wahrscheinlich, dass sich damit ein Standard für Clusterorchestrierung etabliert. Die Aufmerksamkeit und die Entwicklungsgeschwindigkeit ist bei K8s schon jetzt sehr hoch. K8s ist produktionsreif, wenngleich auch manche Features wie Auto-Scaling nur in der Google Cloud verfügbar sind. Das wird sich aber schnell ändern. Wir konnten nicht auf alle spannenden Features eingehen. Bei Interesse sei auf die K8s-Dokumentation [1] und das Buch von Kelsey Hightower zu K8s verwiesen [11], das demnächst erscheinen wird.

adersberger_joseph_dr_sw.tif_fmt1.jpgDr. Josef Adersberger ist technischer Geschäftsführer der QAware GmbH, einem IT-Projekthaus mit Schwerpunkt auf Cloud-native ­Anwendungen und Softwaresanierung. Er hält seit mehr als zehn Jahren Vorlesungen und publiziert zu Themen des Software-Engineerings, aktuell insbesondere zu Cloud Computing.

reimer_mario-leander_sw.tif_fmt1.jpgMario-Leander Reimer ist Cheftechnologe bei der QAware. Er ist Spezialist für den Entwurf und die Umsetzung von komplexen System- und Softwarearchitekturen auf Basis von Open-Source-Technologien. Als Mitglied im Java Community Process (JCP) ist sein Ziel, die Java-Plattform weiter zu verbessern und praxistaugliche Spezifikationen zu entwickeln.

Beim Thema Continuous Integration gehört Jenkins nach wie vor zu den Besten. Aber den Takt geben immer öfter andere an. Wir geben ihm den Groove zurück. Wir zeigen, wie man mit der Groovy-Job-DSL Builds erstellt und komplexe Abläufe mit der Groovy-Workflow-DSL beschreibt. So let’s get groovy!

Die Capitol Versicherung setzt auf Java zur Entwicklung ihrer Backofficeanwendungen. Schon vor Jahren hat sie die erste Anwendung ReBeTol für die Reiseversicherung als Java-Webanwendung realisiert, damals mit Jenkins als Build-Server. Der Fachbereich drängte auf immer kürzere Releasezyklen, der Projektleiter setzte utopischere Zeitpläne, und die Entwickler gingen jeden Tag pünktlich nach 7,5 Stunden nach Hause und alles war gut. Bis der Abteilungsleiter zum Monatsersten den neuen Entwickler Steve W. anschleppte. Der kam frisch von der Uni und kannte jemanden, dessen Mitbewohner mal ein Praktikum bei Google gemacht hatte. Dort, so hatte er aufgeschnappt, liefern sie zehnmal am Tag aus und nennen es „Continuous Delivery“. Dies drang bis zur Führungsriege durch und verlieh ­Steve augenblicklich den Ruf als High Potential aus dem Silicon Valley. Sofort wurde inoffiziell ein Krisenstab aus erfahrenen Entwicklern und Architekten einberufen, die sich nach Dienstschluss trafen, um zu besprechen, wie mit den Flausen des neuen Lieblings vom Chef zu verfahren sei. Man entschloss sich zu einer Flucht nach vorne, bestellte drei Exemplare des Buchs „Continuous Delivery“ [1] und gründete eine Arbeitsgruppe, um dem jungen Hüpfer zu zeigen, dass sie es ebenfalls draufhaben.

Für die Webanwendung ReBeTol wird Jenkins als Build-Server genutzt. Der Kommentar von Steve dazu: „Na immerhin macht ihr schon Continuous-Integra­tion.“ Für ReBeTol gibt es zwei Jenkins-Jobs. Der Job rebetol-build kompiliert die Anwendung und führt die Unit-Tests aus. Der andere rebetol-acceptance-test installiert die Anwendung auf einer Testumgebung und lässt dort die Integrationstests laufen. Die Jenkins-Jobs hat das Team von Hand erstellt und nutzt dabei diverse Plug-ins, die im Jenkins installiert sind.

Builds als Infrastruktur, Infrastruktur als Code

Die Arbeitsgruppe stellt fest, dass die Jenkins-Jobs für das Projekt ReBeTol zur Infrastruktur des Projekts gehören. Infrastruktur, so haben sie sich schlau gemacht, ist auch als Code zu betrachten und wie solcher zu behandeln. Was heißt das konkret? Die Konfiguration der Jobs gehört wie Code in die Versionskontrolle. Änderungen an der Konfiguration werden eingecheckt und dann automatisch in die Infrastruktur übernommen. Oha! Erstes Stirnrunzeln in der Arbeitsgruppe.

In der Zwischenzeit beschloss der Vorstand, auch für die Lebensversicherung eine neue Backofficeanwendung zu entwickeln und startete das Projekt LeBeTol. Hierfür sollte auf bewährte Verfahren und Technologien aus dem Projekt ReBeTol zurückgegriffen werden. Also wurde ein neues Projekt aufgesetzt, und im Jenkins wurden weitere Jobs für das neue Projekt eingerichtet. Dazu wurden die vorhandenen Jobs von ReBeTol kopiert und angepasst. Damit hat es die Arbeitsgruppe nun schon mit vier Jobs zu tun, wobei sich die Jobs für ReBeTol und LeBeTol nur durch den Namen und den Pfad in der Versionskontrolle unterscheiden. Im Grunde genommen gibt es also nur zwei Arten von Jobs, nämlich Build-Jobs und Jobs für die Integrationstests.

Die Arbeitsgruppe wünscht sich, dass Änderungen an den Jobs immer für beide Projekte übernommen werden. Sie fragen sich, wie sie das erreichen können. Idealerweise sollten die Gemeinsamkeiten der Jobs herausgezogen und parametrisiert werden. Denn so würden sie das auch im normalen Programmcode tun. Sie suchen also nach einer einfachen Möglichkeit, Jenkins-Jobs zu beschreiben und zu erzeugen. Bei ihren Recherchen stoßen sie auf die Jenkins-Job-DSL [2]. Mit der Jenkins-Job-DSL lassen sich Jobs mit einer Groovy-DSL beschreiben. Wie das geht, schauen wir uns nun an. Im Anschluss daran kommen wir auf unsere Arbeitsgruppe zurück und schauen, ob Ihnen das weiterhilft.

Jenkins-Jobs per Groovy-Job-DSL

Mit der Job-DSL [2] können Jenkins-Jobs in einem Groovy-Skript beschrieben werden, anstatt sie einzeln von Hand anzulegen und bei jeder Änderung einzeln zu aktualisieren. Wie das aussieht, zeigt Listing 1. Dort wird ein Job erstellt, der ein Git Repository klont und mit Maven baut. Die einfachste Art, diese Jobs auszuführen, ist wiederum, einen Jenkins-Job mit einem Build-Step für die Job-DSL einzurichten. Dazu wird zunächst ein Freestylejob (Abb. 1) mit einem Build-Step Process Job DSLs erstellt (Abb. 2). In der Konfiguration gibt es dann die Möglichkeit, mit Use the provided DSL script direkt ein Skript einzugeben (Abb. 3). Die Jobbeschreibung ist ein gewöhnliches Groovy-Skript, in dem Ausdrücke der Job-DSL verwendet werden können. Das bedeutet, es können Schleifen, Variablen und Klassen genutzt werden, um Jenkins-Jobs zu erstellen. So zeigt Listing 2, wie für eine Liste von Projekten Maven-Jobs im Jenkins angelegt werden. Auch REST-Zugriffe sind mit Groovy möglich, wie Listing 3 zeigt. Dort wird aus GitLab eine Liste von Projekten abgefragt und für jedes Projekt ein Jenkins-Job erstellt.

stamer_groovyjenkins_1.tif_fmt1.jpgAbb. 1: Freestylejob erstellen
stamer_groovyjenkins_2.tif_fmt1.jpgAbb. 2: Job-DSL „Build Step“ einrichten
stamer_groovyjenkins_3.tif_fmt1.jpgAbb. 3: „Build Step“ mit Job-DSL-Skript

Listing 1

job('rebetol-build') { 
  scm { 
    git('git://capitol.de/rebetol.git') 
  } 
  triggers { 
    scm('*/15 * * * *') 
  } 
  steps { 
    maven('clean verify') 
  } 
}

Listing 2

def projects = ['rebetol', 'lebetol'] 
projects.each { project -> 
  maven("${project}-build") { 
    scm { 
      git("git://capitol.de/${project}.git") 
    } 
    goals('clean verify') 
  } 
}

Listing 3

def projectsApi = new URL("http://gitpro.capitol.de/api/v3/projects") 
def projectsJson = new groovy.json.JsonSlurper().parse(projectsApi.newReader()) 
projectsJson.each { 
   def project = it.name 
  maven("${project}-build") { 
    scm { 
      git("git://capitol.de/${project}.git") 
    } 
    goals('clean verify') 
  } 
} 

Die Job-DSL unterstützt Freestyle- und Maven-Jobs, Build-Pipeline-Views und vieles mehr. Kurzum: Die meisten Arten von Jobs und Views werden unterstützt. Eine vollständige Dokumentation findet sich unter [4]. Auch Trigger, Publisher oder Wrapper können über die DSL konfiguriert werden. In Listing 1 haben wir bereits die Konfiguration eines Triggers gesehen. Für einen Job kann eine bestimmte Maven-Version mit mavenInstallation('M3') angegeben werden oder ein JDK mit jdk('jdk1.8'). Soll ein Jenkins-Plug-in verwendet werden, für das keine direkte Unterstützung durch die DSL existiert, so ist das letzte Mittel, den dafür notwendigen XML-Schnipsel von Hand über die Job-DSL zu konfigurieren [3].

Mit der Job-DSL wird aus dem Jenkins-Job ein Groovy-Skript, und wir sind dem Ziel, Builds als Code zu behandeln, einen Schritt näher gekommen. Jetzt gehören die Groovy-Skripte nur noch in ein Git Repository. Auch das unterstützt die Job-DSL. Anstatt die Groovy-Skripte direkt im Jenkins-Job anzugeben, können sie auch aus dem Dateisystem gelesen werden. Wird dann der Jenkins-Job so konfiguriert, dass er ein Git Repository klont, haben wir unsere Jenkins-Jobs in Form von Groovy-Skripten als Code in einem Git Repository abgelegt. Dazu erstellen wir wie zuvor einen Jenkins-Job mit dem Build-Step Process Job DSLs. Nur wählen wir diesmal die Variante Look on Filesystem (Abb. 4). Geben wir dort das Muster *.groovy an, verarbeitet die Job-DSL alle im Workspace vorhandenen Groovy-Skripte. Nun kann für Jobs und Views noch eingestellt werden, was passieren soll, falls ein über die DSL erzeugter Job oder eine View wegfällt. Es gibt die Möglichkeit, dies zu ignorieren, den Job oder die View zu deaktivieren oder zu entfernen. Letzteres ist besonders praktisch, da so erzeugte Jobs und Views einfach mit den eingecheckten Groovy-Skripten synchronisiert bleiben.

stamer_groovyjenkins_4.tif_fmt1.jpgAbb. 4: Job-DSL-Skripte aus Dateisystem

Job-DSL bei der Capitol Versicherung

Unsere Arbeitsgruppe kann die Jenkins-Jobs der Capitol Versicherung mit der Job-DSL in Form von Groovy-Skripten ablegen. Dazu erstellt sie ein Groovy-Skript, das in einer Schleife pro Projekt zwei Jobs per Job-DSL erzeugt (Listing 4). Der erste Job ${project}-build kompiliert die Anwendung und führt die Unit-Tests aus. Der zweite Job ${project}-acceptance-tests installiert die Anwendung auf einer Testumgebung und lässt die Integrationstests laufen. Das Groovy-Skript wird in Git eingecheckt und vom Jenkins-Job capitol-jenkins-seed verarbeitet. Änderungen an den Builds werden ausschließlich im Groovy-Code vorgenommen. Diese Änderungen werden dann durch einen Build des Jobs capitol-jenkins-seed verarbeitet, und die Jenkins-Jobs aller Projekte werden automatisch aktualisiert. Also geht doch, denkt die Arbeitsgruppe. Nun hat sie Feuer gefangen. Mal schauen, wo das endet.

Listing 4

def projects = ['rebetol', 'lebetol'] 
projects.each { project -> 
 
  maven("${project}-build") { 
    scm { 
      git("git://capitol.de/${project}.git") 
    } 
    goals('clean verify') 
  } 
 
  maven("${project}-acceptance-test") { 
    scm {et 
      git("git://capitol.de/${project}.git") 
    } 
    goals('jboss-as:deploy integration-test') 
  } 
 
}

Auf dem Weg zur Deployment-Pipeline

Sechs Monate später steht das nächste Treffen unserer Arbeitsgruppe an. Die Nutzung der Job-DSL zur Anlage neuer Jenkins-Jobs hat sich inzwischen etabliert und wird von allen als sehr positiv betrachtet. Aber Stillstand bedeutet ja bekanntermaßen Rückschritt, denkt sich unsere Arbeitsgruppe. Und stand in dem Buch über Continuous Delivery nicht noch irgendetwas von Deployment-Pipelines? Frank, Mitglied unserer Arbeitsgruppe und bisher eher für seine Zurückhaltung bekannt, wird hellhörig und wittert seine Chance, endlich auch mal für Aufsehen zu sorgen. Er hat gerade vor Kurzem einen Artikel im Java Magazin zum Thema Docker und Jenkins gelesen [5], und da wurde am Rande etwas über ein Workflow-Plug-in berichtet. Das müsste doch in etwa passen, denkt er sich, und überzeugt die Arbeitsgruppe, das Plug-in zu evaluieren.

Jenkins-Workflows per Groovy-DSL

Mit dem Jenkins-Workflow-Plug-in [8] können komplexe Build-Abläufe in einer Groovy-DSL beschrieben werden. Damit lassen sich zum Beispiel Deployment-Pipelines einfacher und eleganter realisieren, als dies mit normalen Jenkins-Bordmitteln möglich wäre.

Ein Jenkins-Job mit dem Workflow-Plug-in wird, wie in Abbildung 5 gezeigt, angelegt. Danach wird der Workflow wie in Abbildung 6 dargestellt konfiguriert. Im Feld Definition wird festgelegt, ob das Skript direkt als Text in der Konfiguration hinterlegt oder aus dem Repository geladen wird. Ganz im Sinne von „Infrastructure as Code“ entscheiden wir uns selbstverständlich für die Repository-Variante. Um die Konfiguration abzuschließen, muss neben den Standardparametern für den Git-Zugriff mindestens noch der Script-Path eingestellt werden, der den Pfad zum Groovy-Skript angibt.

stamer_groovyjenkins_5.tif_fmt1.jpgAbb. 5: Anlage eines Workflowjobs
stamer_groovyjenkins_6.tif_fmt1.jpgAbb. 6: Konfiguration eines Workflowjobs

Workflow zur Verkettung von Jenkins-Jobs

Zum Einstieg zeigt Listing 5 einen einfachen Workflow, der zwei bestehende Jenkins-Jobs nacheinander ausführt.

Listing 5

node('master') { 
  build job: "rebetol-build" 
  build job: "rebetol-acceptance-test" 
}

Im Beispiel wird zunächst ein Node-Block deklariert, der auf dem Masterknoten des Jenkins ausgeführt werden soll. Ein Node-Block im Skript erzeugt während seiner Ausführung einen eigenen Auftrag in der Build-Queue des angegebenen Knotens. In einem Workflow lassen sich beliebig viele Node-Blöcke deklarieren. Jeder Block erhält während seines Ablaufs einen eigenen Workspace auf dem Knoten. In unserem Beispiel werden dann mithilfe des Befehls build job die beiden über die Job-DSL angelegten Jenkins-Jobs aufgerufen. Damit ist unser erster kleiner Workflow fertig und kann ausgeführt werden.

Eine erste Deployment-Pipeline

Für den Anfang gar nicht schlecht, denkt sich Frank, aber irgendwie muss da doch noch mehr gehen. Er versucht sich an einem ersten Entwurf einer einfachen Deployment-Pipeline (Abb. 7). In der Commit-Stage wird der aktuelle Code gebaut, Unit-Tests und Codeanalysen durchgeführt und im Erfolgsfall eine neue Version im Git und Maven Repository erzeugt. Den zugehörigen Code zeigt Listing 6.

stamer_groovyjenkins_7.tif_fmt1.jpgAbb. 7: Aufbau einer einfachen Deployment-Pipeline

Listing 6

def majorVersionNumber = 1.0
def version = "${majorVersionNumber}.${env.BUILD_NUMBER}"
 
node('master') {
  def mvnHome = tool 'M3'
  env.PATH = "${mvnHome}/bin:${env.PATH}"
 
  stage 'Commit Stage' 
  git url: 'git@myhost:cap/continuous-delivery-example.git'
 
  // Create release branch
  sh "git checkout -b ${releaseBranch}"
  // Set build version
  sh "mvn versions:set -DnewVersion=${version}"
  try {
    // Execute maven build
    sh "mvn -f ${webAppHome} verify"
    commitAndPushReleaseBranch()
    deployToArtifactRepository(mvnHome)
  } catch(e) {
    // Delete release branch if the maven build fails
    deleteReleaseBranch()
    error "BUILD FAILED"
  }
}

Im ersten Teil des Node-Blocks wird zunächst mithilfe des Befehls tool das Maven-Home-Verzeichnis ermittelt. Dies ist notwendig, um für spätere Shellaufrufe den Pfad zu Maven bekannt zu machen. Als Nächstes wird mit dem Befehl stage die Commit-Stage eingeleitet. In einem Workflow definiert eine Stage einen bestimmen Abschnitt eines Builds, wir kommen hierauf nochmal im Kontext der Acceptance-Test-Stage zurück. Als ersten Schritt des Abschnitts wird nun das Git Repository geklont. Während man für das Klonen eines Repositories direkt den Befehl git verwenden kann, wird für die restlichen Teile dieses Blocks im Wesentlichen der Befehl sh verwendet, um direkt Kommandos auf der Shell auszuführen. So wird zum Beispiel ein neuer Release-Branch im lokalen Repository angelegt. Mithilfe des Versions-Maven-Plug-ins [6] wird dann die Versionsnummer des Beispielprojekts aktualisiert, damit jeder Build eine neue und eindeutige Version erzeugt. Dazu wird die Build-Nummer des Jenkins-Jobs, die unter ${env.BUILD_NUMBER} im Skript verfügbar ist, als dritte Stelle der Maven-Versionsnummer übernommen. Danach wird mit mvn verify ein Build gestartet. Hierdurch werden in unserem Beispielprojekt der Code kompiliert, die Tests ausgeführt und dabei über JaCoCo [7] die Testabdeckung ermittelt. Natürlich wären an dieser Stelle auch weitere Schritte denkbar, zum Beispiel zur statischen Codeanalyse. War der Build erfolgreich, wird der aktuelle Stand eingecheckt und ins Remote-Repository zurückgespielt. Zum Abschluss der Commit-Stage werden das generierte Artefakt und die unter target/site befindlichen Analyseergebnisse zu JaCoCo in ein Maven Repository zwecks Archivierung übertragen.

Aber was passiert eigentlich, falls bei der Ausführung eines Aufrufs ein Fehler auftritt? In diesem Fall wird wie in Groovy üblich, eine Exception geworfen, die über einen ganz normalen Try-Catch-Block behandelt werden kann. In unserem Beispiel würde ein fehlgeschlagener Maven Build dazu führen, dass das Shell-Kommando mvn verify einen Return-Code ungleich null zurückliefert, was dann automatisch zu einer Exception führen würde. Im Catch-Block wird in diesem Fall der lokale Release-Branch entfernt und der Build mithilfe des Befehls error als fehlerhaft gekennzeichnet.

Staging zur Einschränkung von parallelen Ausführungen

Mithilfe einer Stage-Deklaration lassen sich Einschränkungen bezüglich der parallelen Ausführung eines Workflowabschnitts definieren. Listing 7 zeigt den Code für eine Acceptance-Test-Stage, innerhalb derer die in der Commit-Stage erstellten Artefakte in einem Docker-Container mit Tomcat deployt und die integrativen Tests gegen diesen Container ausgeführt werden. Zum Abschluss wird der Docker-Container wieder heruntergefahren.

Listing 7

stage name: 'Acceptance Test Stage', concurrency: 3
node('master') {
  def mvnHome = tool 'M3'
  deployOnDockerTomcat("tomcat-cd-auto-${version}", '')
  try {
    runTestsOnDockerTomcat(mvnHome, "tomcat-cd-auto-${version}")
  } finally {
    sh "docker stop tomcat-cd-auto-${version}" 
  }
}

Zu beachten ist hier der Parameter concurrency der Stage-Deklaration. Die 3 besagt, dass dieser Abschnitt maximal von drei Instanzen des Workflows gleichzeitig durchlaufen werden darf. Dies ist sinnvoll, um z. B. die maximale Last auf dem System zu begrenzen. Falls keine Einschränkungen über den Parameter concurrency einer Stage definiert werden, können beliebig viele Instanzen des Workflows den Bereich gleichzeitig durchlaufen.

Benutzerbestätigungen anfordern

Abschließend wollen wir das Beispiel mit der in Listing 8 dargestellten User-Acceptance-Test-Stage testen. Ziel dieser Stage ist es, eine Umgebung für die manuellen Fachbereichstests zur Verfügung zu stellen. Diese Umgebung soll immer nur genau einmal existieren und unter einem definierten URL erreichbar sein. Aus diesem Grund wird die maximale Anzahl der parallel auszuführenden Workflowinstanzen in diesem Bereich mit 1 festgelegt. Jedes neue Deployment auf dieser Umgebung muss zunächst durch eine Benutzereingabe bestätigt werden. Erreicht wird dies durch den Befehl input, mit dem eine entsprechende Abfrage definiert werden kann. Der Benutzer hat dann in der Konsolenausgabe des Jenkins Builds die Möglichkeit, mit Proceed den Ablauf des Workflows fortzusetzen oder ihn mit Abort an dieser Stelle zu beenden. Aber was passiert, falls durch mehrere Commits hintereinander diverse Instanzen des Workflows gestartet werden und niemand die entsprechende Abfrage bestätigt? Kein Problem, denn die Parametrisierung der Stage mit concurrency 1 verhindert, dass mehr als eine Instanz bis zur Benutzerabfrage durchkommt. Darüber hinaus sorgt die Deklaration der Stage dafür, dass immer nur die neueste Workflowinstanz auf die Beendigung der aktuell ausgeführten Instanz wartet. Ältere Instanzen werden an dieser Stelle automatisch mit dem Hinweis Canceled since #XXX got here beendet. Zur Verdeutlichung zeigt Abbildung 8 die Konsolenausgabe zu einem Build #127, der zunächst den bereits an der Stage-Grenze wartenden Build #126 beendet und dann seinerseits auf den Build #125 wartet. Nachdem der Build #125 erfolgreich abgeschlossen wurde, wird der Build #127 fortgesetzt und wartet dann seinerseits auf eine Benutzerbestätigung. Zum Abschluss der User-Acceptance-Test-Stage wird dann noch der URL der Testumgebung mithilfe des Befehls echo in der Konsolenausgabe als Link ausgegeben.

Listing 8

stage name: 'User Acceptance Test Stage', concurrency: 1
input "Deploy to User Acceptance Test Stage?"
 
node('master') { 
  deployOnDockerTomcat('tomcat-cd-user', 8083)
  echo "http://localhost:8083/continuous-delivery-example-webapp"
}
stamer_groovyjenkins_8.tif_fmt1.jpgAbb. 8: Konsolenausgabe zum Thema Staging und Benutzer­bestätigung
stamer_groovyjenkins_9.tif_fmt1.jpgAbb. 9: Neu dazugekommen in Jenkins 2.0 ist die Visualisierung der Pipeline Stage View

Workflows bei der Capitol Versicherung

Als Frank unserer Arbeitsgruppe zwei Wochen später seine Evaluierungsergebnisse vorstellt, sind alle von den neuen Möglichkeiten und der Flexibilität des Workflow-Plug-ins begeistert. Die Arbeitsgruppe beschließt, exemplarisch eine Deployment-Pipeline für das Projekt ReBeTol aufzusetzen. Echtes Continuous Delivery bis in Produktion trauen sich die doch eher konservativ geprägten Kollegen von der Capitol Versicherung zwar noch nicht zu, aber zumindest der Weg bis zu den Fachbereichstests soll auf diese Art und Weise automatisiert werden. Kurz vor Fertigstellung der ersten Deployment-Pipeline dann der Schock! Jenkins 2.0 wurde veröffentlicht, und als Hauptneuerung steht in den Release Notes etwas von „Built-in support for delivery pipelines“. Hat man etwa auf das falsche Pferd gesetzt? Das muss Frank sich näher ansehen. Nach einer kurzen Begutachtung des Jenkins-2.0-Docker-Images [9] dann die Erleichterung. Das Workflow-Plug-in wurde lediglich in Pipeline-Plug-in umbenannt und sogar in die Liste der empfohlenen Basis-Plug-ins aufgenommen. Die Konfiguration hat sich nicht wesentlich geändert, und es gibt jetzt zusätzlich noch eine ansehnliche Visualisierung in Form einer Stage-View (Abb. 9). Und da die neue Version sogar vollständig abwärtskompatibel sein soll, kann unsere Arbeitsgruppe ihre erste Deployment-Pipeline beruhigt abschließen.

Nach den letzten aufregenden Wochen in unserer Arbeitsgruppe ist eines klar: Das Feuer für eine kontinuierliche Verbesserung wurde neu entfacht, und wer weiß, ob nicht einer der Kollegen beim nächsten Termin in sechs Monaten mit einer weiteren Idee um die Ecke kommt, wie sich der Deployment-Prozess weiter vereinfachen lässt.

Groove your own Jenkins!

Wir haben mit der Job-DSL und dem Workflow-Plug-in einige Möglichkeiten kennengelernt, um Jenkins mithilfe von Groovy-Skripten fit für Continuous Delivery zu machen. Das hat unserer Arbeitsgruppe bei der Capitol Versicherung viel Spaß gemacht und sie dem Ziel einer vollautomatisierten Deployment-Pipeline ein gutes Stück näher gebracht. Wir hoffen, auch bei Ihnen Interesse am Thema Infrastrukturautomatisierung mit Jenkins und Groovy geweckt zu haben, und vielleicht setzen Sie ja sogar Ihre eigene Vision von Continuous Delivery mit Jenkins und Groovy um. Nutzen Sie doch als Inspiration das begleitende Git Repository zu diesem Artikel [9]. Dort finden Sie den vollständigen Code und ein lauffähiges Beispiel einer Deployment-Pipline mit dem Workflow-Plug-in. Also viel Spaß beim grooven!

stamer_jan_sw.tif_fmt1.jpgJan Stamer ist Senior-Softwareentwickler bei red6 in Hamburg, einem Spin-off der HanseMerkur Versicherung. Dort entwickelt er innovative Insurance-Lösungen mit Java und Node.js

grohmann_rene_sw.tif_fmt1.jpgRené Grohmann ist Leading Software Engineer bei der PPI AG ­Informationstechnologie in Hamburg. Er beschäftigt sich dort schwerpunktmäßig mit der Konzeption und Umsetzung von Java-­En­ter­prise-Anwendungen für die Finanzbranche.