© Teguh Jati Prasetyo/Shutterstock.com
Teil 3: Docker als Build-Container für Angular-/JavaScript-Applikationen

Voll auf Docker


In den ersten beiden Teilen der Artikelserie wurde der grundsätzliche Einsatz von Jenkins für JavaScript-Projekte am Beispiel Angular gezeigt und beschrieben, wie die Build-Konfiguration mit dem Jenkinsfile in den Entwicklungsprozess integriert wird. In diesem Teil werden wir am Beispiel einer Angular-Anwendung zeigen, welche Vorteile der Einsatz von Docker für Build und Auslieferung bringen kann.

Die Verwaltung von Abhängigkeiten kann als eine der Kernherausforderungen im Technologieumfeld bezeichnet werden. Doch nicht nur die Abhängigkeiten, die eine Anwendung zur Laufzeit oder zum Build-Zeitpunkt mitbringt, müssen durch Werkzeuge wie npm oder Maven abgebildet werden. Auch im Build eingesetzte Werkzeuge (beispielsweise Maven, npm, Compiler, Linker) stellen Abhängigkeiten dar. Meist gibt es für die Werkzeuge wiederum Laufzeitabhängigkeiten, zum Beispiel eine bestimmte Java-Version oder Node.js-Runtime. Für Entwickler, die in unterschiedlichen Umgebungen testen müssen, kann das schnell zur echten Herausforderung werden.

Zumindest für Laufzeitumgebungen wurden Werkzeuge wie Ruby Version Manager (RVM) oder Node Version Manager (nvm) entwickelt. Damit lassen sich die jeweils aktiven Versionen der Laufzeitumgebungen umschalten. In diversen Projekten wurden auch schon Eigenlösungen für die Java Virtual Machine (JVM) oder die Konfiguration von C Libraries beobachtet. Damit lassen sich für Entwickler mehr oder minder komfortable Lösungen erstellen. Doch spätestens auf dem Build-Server kann die Verwaltung vieler unterschiedlicher paralleler Umgebungen und Versionen für die Praxis untauglich werden. Zudem entsteht ein unnötiger Flaschenhals, wenn die Verwaltung der Build-Umgebungen durch Systemadministratoren vorgenommen werden muss – DevOps sieht anders aus.

Wünschenswert ist also eine minimale Build-Umgebung mit weitgehender Kontrolle über Werkzeug(-versionen) und Umgebung durch diejenigen, die am besten wissen, wie diese beschaffen sein müssen: Entwickler. Durch die von Docker gebotene Isolation lassen sich unterschiedliche Versionen von beispielsweise Java und Maven als Container bereitstellen. Sogar parallele Builds mit beiden Umgebungen sind denkbar und sparen damit enorm Zeit. Eine gesonderte Einrichtung auf den Maschinen kann entfallen, außer Docker wird für einen Build über die Kommandozeile nichts benötigt. Derselbe Vorteil wiegt auf dem Build-Server wesentlich höher. Entwickler werden in der Regel nicht um eine lokale Umgebung mit IDE und weiteren Werkzeugen herumkommen. Jedoch ist ein reiner Frontend-Entwickler dann nicht gezwungen, eine ansonsten nicht benötigte Werkzeugsammlung für Backend-Entwicklung zu installieren, wenn er lokale Builds vornehmen möchte.

Auf den ersten Blick ist Build gleich Build, jedoch weicht das Vorgehen während der Entwicklung von einem CI Build oder gar einem Release deutlich ab: Entwickler möchten schnelles Feedback für inkrementelle Änderungen. Daher bauen moderne IDEs in der Regel ohne Unterbrechungen im Hintergrund und zeigen Fehler und Probleme unmittelbar an. Bei Frontend-Anwendungen sollen optische Anpassungen ebenfalls sofort sichtbar werden, damit ggf. erforderliche Nachbesserungen direkt vorgenommen werden können. Um diese Anforderungen umzusetzen, müssen die eingesetzten Werkzeuge ebenfalls kontinuierliche bzw. inkrementelle Builds unterstützen. Bei Angular CLI und dem verwendeten Development-Webserver ist dies der Fall. Dieser unterstützt sogar einen automatischen Reload im Browser, damit die Änderungen auch aktiv werden. Gleiches gilt für Karma, damit können Unit-Tests bei jeder Änderung unmittelbar gestartet und dem Entwickler damit schnell fehlgeschlagene Tests mitgeteilt werden.

Damit diese ganzen Vorteile zum Tragen kommen, benötigt der Build-Container direkten Zugriff auf die Programmquellen. Das kann über einen Bind Mount für ein Docker Volume umgesetzt werden. Dabei wird lediglich vorausgesetzt, dass der Docker Daemon auf demselben System läuft wie das Dateisystem mit den Quelltexten. Auf einem Entwicklersystem sollte dies regelmäßig der Fall sein. Der Parameter für einen Bind Mount lautet -v und erwartet jeweils einen absoluten Pfad für das zu mountende Verzeichnis des Hosts und das Zielverzeichnis innerhalb des Docker-Containers. Die beiden Pfade werden durch einen Doppelpunkt separiert.

Eine Besonderheit gilt es im Kontext von solchen Bind Mounts zu beachten: Oft verwenden Prozesse innerhalb von Docker den Nutzer root. Damit erhalten erzeugte Dateien dann einen falschen Besitzer. Um das zu vermeiden, wird in Docker beim Start durch den Parameter --user ein anderer Nutzer gesetzt, dem dann die erzeugten Dateien gehören. Für einen Build werden in der Regel auch keine root-Privilegien benötigt, ein nicht privilegierter Nutzer stellt damit kein Problem dar.

Am praktischen Beispiel einer Angular-Anwendung können diese Konzepte nun umgesetzt werden. Als Docker Image kommt trion/ng-cli zum Einsatz. Da Angular CLI 1.2.7 zum Zeitpunkt der Artikelerstellung aktuell war, soll dies auch verwendet werden. Damit ergibt sich trion/ng-cli:1.2.7 als vollständige Image-Bezeichnung. Der Vorteil, unterschiedliche Versionen durch entsprechende Docker Images zu adressieren, wird damit direkt offensichtlich (siehe auch Kasten: „Angular CLI Docker Images“).

Um eine Angular-Anwendung zu erstellen, bietet Angular CLI das new-Kommando, gefolgt vom Namen der Anwendung, in diesem Fall DemoApp. Das trion/ng-cli Image ist bereits dafür konfiguriert, nicht den root-User zu verwenden, sondern den User 1000. Aufgrund der Docker-Architektur, die auf Remote-Kommunikation zwischen Kommandozeile und Server basiert, kann Docker den aktuellen Nutzer nicht direkt ermitteln. Auf einem typischen Linux-System wird der erste Nutzer oft als 1000 angelegt. Mit dem Shell-Kommando id -u lässt sich die numerische Nutzer-ID des aktuellen Nutzers jedoch in jedem Fall ermitteln. Durch --user bzw. -u kann die zu verwendende Nutzer-ID Docker mitgeteilt werden. Unter Windows und macOS findet ein separates Mapping der Dateien auf den Nutzer statt, der den Docker Daemon gestartet hat.

Analog zur Nutzer-ID ist auch das aktuelle Verzeichnis nicht verfügbar. Um die von Docker benötigte absolute Pfadangabe für das aktuelle Arbeitsverzeichnis zu ermitteln, kann unter macOS und Linux auf die Umgebungsvariable PWD zurückgegriffen werden. Als letzter Parameter wird noch --rm verwendet. Ohne den Parameter werden die Containerinstanzen nicht gelöscht, wenn die Ausführung beendet ist. Für einen Werkzeugcontainer, wie in diesem Fall, gibt es keinen Grund, die Instanzen aufzuheben: Sie würden lediglich Ressourcen belegen. Der vollständige Aufruf, um die Beispielanwendung zu erzeugen, sieht damit unter Linux auf der Kommandozeile wie folgt aus:

docker run -u $(id -u) --rm -v "$PWD":/app trion/ng-cli:1.2.7 ng new

DemoApp

Der allererste Aufruf führt ggf. dazu, dass Docker das zu verwendende Image noch herunterladen muss. Darin enthalten sind unter anderem Node und Angular CLI, sodass der Download etwas dauern kann (Abb. 1).

kruse_angulardocker_1.tif_fmt1.jpgAbb. 1: Download des Docker Image für Angular CLI bei der ersten Verwendung

Nachdem die Anwendung erstellt wurde, befindet sich im aktuellen Arbeitsverzeichnis ein neuer Ordner mit dem Namen der Angular-Anwendung: DemoApp. Angular CLI hat die erforderlichen Konfigurationsdateien sowie ein rudimentäres Gerüst für die Angular-Anwendung inkl. Tests erzeugt (Abb. 2). Für alle folgenden Schritte sollte man somit in den neu angelegten Ordner wechseln.

kruse_angulardocker_2.tif_fmt1.jpgAbb. 2: Angular CLI im Docker-Container hat die „DemoApp“ erfolgreich erstellt

Durch den Node Package Manager (npm) werden in der Regel die Abhängigkeiten direkt mitinstalliert. Manuell kann man aber auch zu einem späteren Zeitpunkt die Abhängigkeiten nachinstallieren, ebenfalls aus dem Container heraus:

docker run -u $(id -u) --rm -v "$PWD":/app trion/ng-cli:1.2.7 npm install

Je nach Internetanbindung kann dies einen Moment dauern, es werden rund 200 MB heruntergeladen. Anders als vielleicht gewohnt, erfolgt keine kontinuierliche Ausgabe von npm. Das vermeidet, dass die Logdateien im späteren Build-Server unnötig lang und schlecht lesbar werden.

Der typische Entwickler möchte nun die Anwendung starten und Veränderungen live im Browser verfolgen können. Dazu verwendet Angular CLI einen leichtgewichtigen Webserver und Live-Reload. Letzteres erspart einen manuellen Reload im Browser. Die viel gelobte Isolation von Docker muss nun nicht nur bezüglich des Dateisystems, sondern auch noch für den Netzwerkzugriff durch den Browser angepasst werden. Versäumt man das, gibt es außer einer Fehlermeldung nichts zu sehen. Der Angular CLI-Webserver läuft standardmäßig auf Port 4200, verwendet jedoch nur das l...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang