Neuigkeiten von trion.
Immer gut informiert.

Docker File für Angular CLI

Docker

Dieser Artikel erläutert am Beispiel eines Angular CLI Dockerfiles den Aufbau eines Docker Containers zur Ausführung von Builds. Die Verwendung des Containers wurde in Docker Build für Angular CLI erklärt. Der fertige Container ist via DockerHub unter trion/ng-cli verfügbar. Das zugehörige Repository bei GitHub findet sich unter https://github.com/trion-development/docker-ng-cli

Der obere Teil des Dockerfile definiert das verwendete Basis Image und deklariert Parameter, die beim Build des Images spezifiziert werden können.

In diesem Fall soll ein Image für einen Container erstellt werden, der eine Umgebung für Angular CLI bereitstellen sollen. Dazu wird node.js benötigt, was durch das node Basisimage in Version 6 herangezogen wird.

Die zusätzlichen Argumente erlauben das Image zum Erstellungszeitpunkt zu konfigurieren, falls von den Vorgaben abweichende Ausprägungen gewünscht sind.

FROM node:6   #(1)

ARG NG_CLI_VERSION=1.0.0-beta.32.3    #(2)
ARG USER_HOME_DIR="/app"              #(3)
ARG USER_ID=1000                      #(4)
  1. Basisimage: node in Version 6

  2. Zu installierende Angular CLI Version

  3. Arbeitsverzeichnis

  4. Default User-ID

Hier ist zu beachten, dass das einmal erstellte Docker Image sich nie wieder ändert. Somit findet auch kein Update der Angular CLI Version statt - das ist gerade gewünschtes Verhalten für stabile und reproduzierbare Ergebnisse. Die Images werden dann passend zur verwendeten Angular CLI Version getaggt, um eine deterministische Version angeben zu können oder gar den parallelen Betrieb mehrerer Versionen zu ermöglichen.
Ohne ein Tag wird immer latest verwendet, das ist dann die Version, die zum Zeitpunkt des Imagedownloads aktuell war.

Anders als Argumente sind Umgebungsvariablen, diese können zur Containerlaufzeit noch geändert werden. Das Standard node Docker-Image setzt das Loglevel auf info, was in der Kombination mit Angular CLI dazu führt, dass der npm-install Schritt bei der Projekterzeugung fehlschlägt, weil zu viele Ausgaben erzeugt werden. Darum wird für das Angular CLI Image das Default-Level auf "warning" konfiguriert, indem eine entsprechende Umgebungsvariable gesetzt wird.

ENV NPM_CONFIG_LOGLEVEL warn

dumb-init

Docker isoliert innerhalb des Containers laufende Prozesse vom umgebenden System, inkl. der Prozess-IDs für gestartete Programme. Damit erhält der erste im Container gestartete Prozess, d.h. entweder der Entrypoint oder das Command, die Prozess ID 1.

Die Prozess ID 1 ist jedoch etwas besonderes: Es handelt sich dabei in der Regel um das Init-System für das ganze Betriebssystem. Der Kernel behandelt den Prozess daher anders, als jeden anderen Prozess.

Ein regulärer Prozess hat die Möglichkeit Signal-Handler zu registrieren, um z.B. auf SIGTERM zu reagieren. Ohne Signal-Handler wird der Prozess durch den Kernel beendet. Dieses Verhalten gibt es für PID 1 nicht, der Prozess wird nicht beendet, wenn er nicht explizit einen Signal-Handler registiert hat, und dieser auch dafür sorgt, dass der Prozess beendet wird. Das führt beispielsweise dazu, dass ein docker-run, welches ein SIGTERM erhält, beendet wird, der Container jedoch weiter läuft.

Ein weiteres Problem sind Zombie-Prozesse: Ein Prozess wird zum Zombie-Prozess, wenn er beendet wird, und kein wait() durch den Elternprozess erfolgt. Ist ein Elternprozess vor dem Kindprozess beendet, so wird dem Kindprozess der Prozess PID 1 als neuer Elternprozess zugewiesen. Der init Prozess muss daher für solche Waisenprozesse wait() aufrufen, damit keine Zombie-Prozesse übrig bleiben.

Typische Prozesse sind nicht für diese Verantwortlichkeiten ausgelegt, so dass es in Containern zu Resourcen-Lecks kommen kann, oder sich diese nicht auf gewohnte Weise verwalten lassen, da SIGTERM nicht korrekt behandelt wird.

Abhilfe schafft hier dumb-init: Als sehr simples Init-System startet er einen Kindprozess und reicht alle Signale an diesen weiter. Da dieser Prozess nun nicht mehr PID 1 ist, funktioniert auch die gewohnte Signalbehandlung.

#(1)
RUN curl -sL https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 > /usr/bin/dumb-init \
    && chmod +x /usr/bin/dumb-init \    #(2)
    && mkdir -p $USER_HOME_DIR \        #(3)
    && chown $USER_ID $USER_HOME_DIR \  #(4)
    && (cd "$USER_HOME_DIR"; \          #(5)
    npm install -g @angular/cli@$NG_CLI_VERSION; npm cache clean)
  1. Download von Yelp dumb-init als statisches binary

  2. dumb-init als ausführbare Datei kennzeichnen

  3. Arbeitsverzeichnis anlegen

  4. Eigentümer des Arbeitsverzeichnis setzen

  5. Installation von Angular CLI, anschließend aufräumen von nicht benötigten npm Cache-Dateien

Die Einrichtung von Angular CLI und Arbeitsverzeichnis ist nun abgeschlossen. Damit die nahtlose Nutzung des Docker Containers funktioniert, werden nun noch die Netzwerkports und die Integration des Dateisystems vorbereitet.

Entrypoint

Der Entrypoint stellt, wie der Name schon sagt, den Einstiegspunkt in den Docker-Container dar. Vor jedes Kommando wird der Entrypoint vorangestellt. Das Dockerfile verwendet den vorher installierten dumb-init als Entrypoint, der Parameter -- kennzeichnet das Ende der Parameter für den Entrypoint. Alle folgenden Parameter werden dadurch an das auszuführende Kommando weitergegeben.

WORKDIR $USER_HOME_DIR   #(1)

VOLUME "$USER_HOME_DIR/" #(2)
EXPOSE 4200              #(3)

ENTRYPOINT ["/usr/bin/dumb-init", "--"]   #(4)

USER $USER_ID           #(5)
  1. Standardverzeichnis festlegen

  2. Arbeitsverzeichnis als Volume deklarieren

  3. Netzwerkport von Angular CLI deklarieren

  4. Defaultprozess für den Docker Container festlegen

  5. ID des Standardnutzers setzen

Bei der Ausführung wird der Nutzer mit der ID $USER_ID verwendet. Die meisten Linux Systeme, die nur von einem Benutzer genutzt werden, verwenden 1000 als ID. Wird die falsche User-ID verwendet, so gehören angelegte Dateien nicht dem aufrufenden Nutzer. Aufgrund der Architektur von Docker als Client-Server System lässt sich dies prinzipbedingt nicht automatisch korrekt setzen.
Abhilfe schafft hier explizit die zu verwendende Nutzer-ID von außen durch den Parameter -u mitzugeben.

Der Angular CLI Docker Container ist damit fertig. Bei der Verwendung ist ein Verzeichnis von außen in den Docker Container herein zu reichen, damit die erzeugten Ergebnisse des Containers später auch verfügbar sind. Dazu wird ein Volume-Mount mit $USER_HOME_DIR als Ziel verwendet.




Zu den Themen Angular und Docker bieten wir sowohl Unterstützung als auch passende Schulungen an:

Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.