Community

Einfacheres CI mit Docker Multi Stage Builds und Jenkins Pipelines

: Martin Schäfer erklärt bei der Java User Group Hessen, wie sich Docker Multi Stage Builds und Jenkins Pipelines im Sinne der Continuous Integration kombinieren lassen.

Auf dem Gebiet der Softwareprogrammierung hat sich über die letzten Jahre ein Paradigmenwechsel vollzogen. Unter dem Stichwort der agilen Softwareentwicklung sind Werte wie Verschlankung, Flexibilisierung, Dynamisierung und Automatisierung wesentlich wichtiger geworden als zuvor. Kennzeichnend für diesen Wandel ist ein zunehmend inkrementelles Vorgehen, sowohl bei der Programmierung selbst als auch bei beim Testen, das zunehmend automatisiert werden soll. Softwareentwickler sprechen in diesem Zusammenhang von Continuous Integration. Als Mittel zum Zweck dienen ihnen Technologien, welche die Aufwände hierbei mit praktischen Konzepten teils erheblich reduzieren. Um zwei davon geht es in diesem Blogbeitrag der JUGH.

Docker Multi Stage Builds

Docker

Docker ist eine Technologie zur Bündelung und Virtualisierung von Softwareteilprozessen. Dazu verpackt Docker diese Teilprozesse in virtuellen Containern, autarken Einheiten, die als Selbstversorger in keiner Abhängigkeit zu ihrer Umgebung stehen und so flexibel auf allen Betriebssystemen lauffähig sind. Folgende drei Elemente sind für die Programmierung mit Docker charakteristisch:

  • Dockerfile: Textdatei, aus der per Befehl schichtweise ein Docker Image generiert wird
  • Docker Image: Speicherabbild eines Containers, das schichtweise aus einem Dockerfile generiert wird
  • Docker Container: Autarkes Softwarepaket, das alle Elemente mitführt, die es für den autarken Betrieb benötigt

Docker Multi Stage Builds

Multi Stage Builds ist ein neues Feature von Docker zur Optimierung von Dockerfiles. Diese sind oft umfangreich und haben entsprechend große Images zur Folge, denn jeder Befehl im Dockerfile stellt eine zusätzliche Schicht für das Docker Image dar. Als Folge lassen sich Entwicklungsumgebungen schwerer aufsetzen und das regelmäßige Ausrollen von neuen Softwareteilen belastet den Speicherplatz und die Bandbreite der Produktivumgebung mehr als nötig.

Oftmals hat man unterschiedliche Artefakte, die bei einem Build herauspurzeln sollen: ein Ergebnis für das Frontend zum Beispiel, gebaut und getestet mit Tools wie etwa mit npm. Danach vielleicht ein Build und Unittests für das Backend mit Maven, an dessem Ende ein WAR-File für den Server erstellt wird. Mit Docker Multi Stage Builds kann man innerhalb eines Dockerfiles mehrere unabhängige Zwischencontainer aufbauen, die jeweils ein Ergebnis produzieren, welches an einen nächsten Zwischencontainer weitergegegen wird – dergestalt, dass die neuen Container sich die nötigen Artefakte aus den alten Containern ziehen. Dadurch bekommt jede Umgebung nur die Dinge mit, welche sie für den jeweiligen Zwischenschritt benötigt. Was dazu führt, dass das Dockerfile wesentlich einfacher zu maintainen und zu lesen ist, und die Kapselung der unterschiedlichen Builds klar voneinander getrennt wird. Mehr dazu hier.

Jenkins Pipelines

Jenkins ist ein weit verbreitetes Open-Source-Tool zur Unterstützung der Continuous Integration in der Softwareentwicklung. Jenkins Pipelines sind in diesem Kontext Programmpakete, die den Workflow des Entwicklers unterstützen.

  • Automatisierung von Integration, Testing und Deployment von Programmcode
  • Speicherbarkeit der Pipelines in jedem beliebigen Quellcode-Kontrollsystem (Versionsverwaltung)
  • Abzweigung und Zusammenführung einzelner Teilbaustellen mit Branches und Pull Requests (Git-Prinzip)
  • Intuitive Nutzeroberfläche für nicht-technische Projektmitarbeiter (Blue Ocean UI)
  • Kompatibilität mit Docker

Der große Unterschied zwischen der althergebrachten Nutzung von Jenkins und Jenkins Pipelines ist:
 Vorher hat das Entwicklungsteam Plugins installiert und diese Plugins über die GUI eingebunden und konfiguriert. Das Problem: Mit der Zeit wuchs der Zoo an Plugins zu einem unübersichtlichem Haufen an, der die verschiedensten Versionen und Abhängigkeiten zwischen den Modulen ziehen und zu Seiteneffekten führen und das System unnötig belasten konnte, da es nicht mehr einfach so aufzuräumen war. Diese Problematik wuchs mit jeder Applikation.

Jenkins Pipelines bietet nun den Vorteil, ähnlich wie bei Docker, anhand einer Textdatei bzw, mit Befehlen Jenkins mitteilen zu können, was er tun soll. Das heißt, man hat wie bei Docker auch, eine Datei, die beschreibt, was getan werden soll: ein Jenkinsfile also, äquivalent zur Dockerfile.

Das Ganze hat folgende Vorteile:

  1. Der Entwickler kann das Jenkinsfile lokal anpassen und einchecken.
  2. Änderungen werde aufgenommen und Jenkins baut ab diesem Zeitpunkt nur noch nach dieser Vorschrift.
  3. Der Entwickler muss sich nicht mehr in die GUI einloggen, um Änderungen durchzuführen.
  4. Das Jenkinsfile ist leichtgewichtig und zeigt lesbar, was das System nachher macht, dadurch fällt die Wartung leichter.
  5. Und schließlich ist die Ausgabe beim Build strukturierter als beim alten Vorgehen, da einzelne Stufen des Builds, welche man im Skript definieren kann, in der GUI getrennt voneinander angezeigt werden, samt Logs zum jeweiligen Schritt.

Best Practices zu Jekins Pipelines gibt es z. B. hier.

Docker Multi Stage Builds und Jenkins Pipelines

Ein starkes Team?

Und wie kombiniert man Docker Multi Stage Builds und Jenkins Pipelines in der Praxis? Dazu hat uns Martin Schäfer beim letzten JUGH-Treffen an seinen persönlichen Erfahrungen teilhaben lassen. Hier der Inhalt der Session in seinen eigenen Worten:

„Stört euch das nicht auch? Endlich ist der Build lokal ordentlich durchkonfiguriert und dann kommt doch wieder die ewig gleiche Fummelei bei der Konfiguration der zugehörigen Jenkins Jobs? ‚Auf meinem Rechner läuft der Build!‘ ruf ich noch empört, bevor ich kleinlaut erkläre, welche Java-, Maven-, Node- und ImageMagick-Versionen installiert sein müssen, damit meine pom.xml endlich baut. Schade nur, dass alles mit allem in Konflikt steht und ich jetzt mehr Zeit mit der Pflege der Tools und Skripte auf meinem Jenkins verbringe, als mit der eigentlichen Entwicklung. Sollte CI nicht ‚works at least on my machine‘ zum Teufel jagen und Ruhe in den Integrationsprozess bringen? Anhand von praktischen Beispielen zeige ich in dieser Session, wie man das Problem durch Multi Stage Builds mit Docker und Pipeline Scripts lösen kann, wie man Build- und Live-Container pragmatisch kombiniert, um die Konfiguration des Integrationsbuilds auf ein Minimum zu reduzieren, und wie diese Konfiguration den Build für alle möglichen Sprachen, Frameworks und Zielplattformen aus Sicht des CI und Entwicklers vereinheitlicht.“

Jule Witte

Presse & Kommunikation