#5: Build Workflow mit docker

Bisher haben wir uns um die wichtigsten Facts gekümmert, um überhaupt eine Java Anwendung bauen zu können. Im den nächsten Schritten konzentrieren wir uns auf den Build Workflow mit Docker. Dieser bezeichnet den Prozess von der Eingabe des Codes bis hin zur Auslieferung in die produktive Umgebung inkl. aller zusätzlich notwendiger Schritte wie Code-Analyse, Tests, etc.

Der klassische Build Workflow mit docker

Grade mit der Einführung der Container Technologie haben sich viele neue Möglichkeiten ergeben, den Build Workflow mit Docker zu optimieren, die leider oft nicht vollständig genutzt werden. Oft sehe ich es sogar, dass die bestehenden Abläufe 1:1 beibehalten werden und Docker als zusätzliche Technologie einfach nur “übergestülpt” wird. Der Mehrwert bleibt dabei natürlich auf der Strecke. Um dies zu verdeutlichen, möchte ich Dir hier mal einen möglichen klassischen Build Prozess darstellen.

Beispiel für einen klassischen Software Workflow

Es mag sein, dass sich der Workflow von Fall zu Fall unterscheidet, aber im Grundsatz ist das eine Architektur die ich immer wieder in den verschiedenen Projekten antreffe. Der Entwickler testet lokale mit einem JAR Artefakt, dieses wird auf einem DEV Server dem Team zur Verfügung gestellt. Wenn das Ergebnise zufriedenstellend ist, wird in die nächste Phase den Integrationstest übergeben, der weitere Tests gegen Drittsysteme abfährt. Am Ende des Integrationstests steht die Abnahme durch den PO. Am Ende folgt die Integration in den Master-Branch und das Deployment in die Produktion.

Im folgenden möchte ich auf die wichtigsten Bauchschmerzen eingehen, die ich mit einem solchen Workflow habe.

Bauchschmerz 1:

Die lokale Testumgebung beim Entwickler entspricht nicht der Produktion. Der Entwickler testet mit irgendeinem JAR Artefakt, obwohl später das ganze Konstrukt als Docker Container laufen soll. Im besten Fall, hat der Entwickler Scripte zur Verfügung, die ihm das Docker Image auch noch bauen, damit er dieses lokal starten kann. Aber am Ende des Tages ist es eine Tatsache: Wenn die Entwicklungsumgebung (oder die Art des Builds) sich von der Produktion unterscheidet, werden immer wieder Fehler in andere Branches gemerged.

Bauchschmerz 2:

Abhängigkeit zur Entwicklungsumgebung. Oft sehe ich es dann auch, dass lokale Build-Optimierungs-Script-Dingelings irgendwie hart an die IDE angestrickt sind. Das ist im Prinzip erst mal cool, ich nehme mir aber die Möglichkeit die IDE schnell zu wechseln, Updates werden komplizierter und eine Nutzung unterschiedlicher IDEs innerhalb ein und des selben Projektes kannst du natürlich ganz vergessen. In dieser Artikelreihe haben wir das hier in Ansätzen auch schon gemacht, indem wir docker-compose aus VSCode heraus starten. Das werden wir aber in folgenden Artikeln auch schnell wieder beheben.

Bauchschmerz 3:

Integrationtests laufen nur nachgelagert. Schauen wir uns einmal an, wie viele Schritte notwendig sind, um überhaupt in die Lage zu kommen Integrationstests laufen lassen zu können. Diese müssen ja für jede Änderung am Artefakt alle wiederholt werden müssen. Und die Bezeichnung “Integrationstest” ist an dieser Stelle ja nur ein Platzhalter für viele weitere Aktionen die in den Build Prozess integriert sind (Codenalyse, Security-Tests, Licensechecks, Vulnerability-Scans, …). Dies erzeugt komplett überflüssige Aufwände.

Bauchschmerz 4:

Neubau von Docker Images ohne Änderung. Tatsächlich sehe ich es immer wieder, dass für jede Stage Docker Images neu gebaut werden, obwohl sie immer die gleichen Artefakt Versionen beinhalten. Dies führt zwangsläufig zu Fehlern. Irgendwann kommt mit 100%iger Sicherheit der Tag, an dem am Dockerfile Änderungen zwischen der Erzeugung des DEV und des PRD Releases durchgeführt werden.

Build Workflow mit docker modern

Nun ist die Frage, wie mache ich es besser. Im Rahmen dieses Artikels will ich auf das Thema mit dem größten Hebel eingehen, nämlich möglichst viele Aktionen des Build-Workflows auf die lokale Maschine des Entwicklers zu verschieben, um die Container-Technologie wirklich zu nutzen. Ein Beispiel für ein solches Vorgehen möchte ich wie folgt geben:

Build Workflow überarbeitet

Dies ist sicher noch nciht die perfekte endgültige Lösung, aber man sieht schon sehr deutlich, viele Dinge fallen einfach komplett weg, viele lassen sich deutlich vereinfachen.

Lokale Builds

Das Docker Image wird auf der Entwicklermaschine gebaut und in der Docker-Registry zur Verfügung gestellt. Die Build-Umgebung wird entlastet, was insbesonderen bei geteilten Umgebungen (Mehrere Projekte bauen auf einer zentralen Build-Umgebung) ein ganz wichtiger Faktor ist.

Lokale Tests

Sind alle Umsystem ebenfalls containerisiert (und ggf. mit Mock-Daten versehen) können alle Integrationstest auf der Entwicklermaschine stattfinden. Natürlich hängt die Machbarkeit dies sehr stark von den Umsystemen ab. Aber unbestritten ist, das die lokalen Tests deutlich ausgeweitet werden können.

Wegfall Entwicklungsumgebung

Ist die gesamte Umgebung containerisiert, besteht keine Notwendigkeit mehr, eine explizite Entwicklungsumgebung vorzuhalten. Über bel. Kompositionstools (k8s, docker-compose) lassen sich die Anwendungen zusammenstellen und die zu testende Applikation kann auf jedem beliebiegen System hochgefahren werden.

Ggf. wegfall der Integrationstestumgebung

Dieser Punkt ist mit Einschränkungen versehen. Besteht die Anwendungen nur aus Komponenten die ihrerseits selbstständig lauffähig sind, können die Integrationstest auf einem Server durchgeführt werden der zu diesem Zweck provisioniert und danach wieder gelöscht wird.

Wichtig: Man mag sich fragen, warum nicht gleich die Integrationstest als solche wegfallen können, wenn diese schon auf dem Entwicklersystem gelaufen sind. Hier sehe ich aber schon den Bedarf, Integrationstest nochmal gegen Echtsysteme/Echtdaten auszuführen. Oft sind Systeme auch nicht auf dem Entwicklersystem abzubilden (speziell Hardwarenahe Softwarekomponenten). Auch dürfen wir nicht vergessen, das viele Umsysteme die in Integrationstest einbezogen werden, nicht auf dem lokalen Entwicklersystem abbildbar sind.

Buildlaufzeiten werden verkürzt

Das erzeugen eines “Images” für die Integrationstest und Produktion beschränkt sich auf das zur Verfügung stellen eines Tags. Statt das ganze Image neu zu bauen, reicht es das vom Entwickler erzeugte Image zu nutzen und mit den entsprechenden Tags zu versehen, die für den Einsatz in der Integrations- und Produktionsumgebung notwendig sind.

Weniger merges durch weniger Branches

Auch dieser Punkt hängt natürlich stark von der arbeitsweise des Teams und diversen anderen Einflüssen ab. Aber im Prinzip reicht es aus, einen Features Branch abzuzweigen wenn etwas neues gebaut werden soll und diesen direkt in den master branch zu mergen wenn das Feature abgeschlossen ist. Git und die darauf basierenden Plattformen bieten hier auch noch weitere Mechanismen zur Steuerung (Tags, Releases).

Einheitliche Build-Steuerung und -reduzierung der Abhängigkeit vom Build-System

Ein ganz wichtiges Paradigma ist: Der Build-Server soll den Build auslösen, aber nicht “durchführen”. Oft höre ich die Anforderung: Wir brauchen auf dem Jenkins-Server noch das Plugin XYZ. Das ist ein sicheres Indiz dafür, dass in dem Projekt was falsch läuft. Richtig wäre:
“Führe dieses Kommando aus, der Build läuft dann alleine komplett durch”. Plugins (oder zusätzliche Funktionalität) auf dem Build Server sind OK, wenn sie genutzt werden um den Komfort zu erhöhen. Zum Beispiel wenn damit eine Nachricht in den Teams Channel des Projektes gesendet wird, oder die Ergebnisse der Codeanalyse besondern schön aufbereitet und verteilt werden. also technisch nichts mit dem Build-Vorgang selber zu tun haben. Wenn ich das gemacht habe, dann steht auch nichts der Idee im Wege, auf dem lokalen Entwicklersystem die Software auf dem gleichen Weg zu erzeugen wie auf dem zentralen System.

Fazit zum Build Workflow mit docker

Ganz klar, dies ist nur einer von vielen verschiedenen Möglichkeiten, die Build Umgebung zu gestalten. Aber ich hoffe dieser Artikel zeigt auf, worauf es mir ankommt: Es reicht nicht zu sagen “Wir nutzen jetzt Container”. Mit diesem Ansatz wird mein Projekt nur aufwändiger ohne das ich messbaren Mehrwert erziele. Mit einer richtigen gut überlegten Integration, spart das Projekt sowohl Zeit als auch Resourcen, deren ROI sich schnell bemerkbar macht.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.