Neuigkeiten von trion.
Immer gut informiert.

Angular Anwendungen in Azure DevOps bauen

Angular

Azure DevOps ist der Nachfolger des Microsoft Team Foundation Server (TFS). Inbegriffen sind Buildserver (CI), Version Control (git), Verwaltung manueller Testpläne und Artefaktauslieferung (CD).
Die Features von Azure DevOps können dabei je nach Bedarf aktiviert werden.

Im folgenden soll eine Angular Webanwendung mit Azure DevOps als Buildserver gebaut werden. Damit der Build gemeinsam mit der Software versioniert und entwickelt werden kann, wird die Buildkonfiguration im YAML Format als Azure Pipeline im Repository abgelegt.

Azure DevOps Setup

Wichtig zu wissen ist dabei, dass Azure DevOps das YAML Pipeline-Format aktuell lediglich für Microsoft-eigene git Repositories bei GitHub oder "Azure Repos" Git unterstützt.
Mehr Informationen dazu finden sich im Microsoft Azure DevOps Support Dokument: https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/?view=azure-devops

Für das Beispiel wird daher das Repos Feature im Azure DevOps aktiviert und als Versionsverwaltung genutzt.

Das Setup eines Azure DevOps Projekts für einen Angular Build mit Azure DevOps sieht dann wie folgt aus:
Als erstes wird ein neues Azure DevOps Projekt erzeugt.

Projekt in Azure Devops anlegen
Abbildung 1. Projekt in Azure-Devops anlegen

Falls GitHub verwendet wird, ist das Setup besonders einfacher. Für die von Microsoft in Azure DevOps angebotene Git-Integration wird zunächst ein leeres Projekt angelegt. In den Projekteinstellungen kann dann das Repos Feature aktiviert werden.

Repos-Feature aktivieren
Abbildung 2. Repos-Feature aktivieren

Anschließend kann eine neue Build Pipeline erzeugt werden, Als Quelle für den Sourcecode wird dann "Azure Repos git" (oder eben GitHub) ausgewählt.

Sourcecode-Quelle einrichten
Abbildung 3. Sourcecode-Quelle einrichten

Azure DevOps YAML Pipeline

Azure DevOps verbindet sich dann mit der Versionsverwaltung und sucht nach der Datei azure-pipelines.yaml. In der Pipeline können dann Schritte (steps) definiert werden, die für den Build abgearbeitet werden.

Die Pipeline wird durch sogenannte Agents ausgeführt. Microsoft bietet die Plattformen Linux, Windows und macOS an. Die Konfiguration der Plattform erfolgt durch einen Pool, der für die gesamte Pipeline oder einen Unterabschnitt gilt.

Verwendung von Ubuntu 20.04 als Buildumgebung
pool:
  vmImage: 'Ubuntu-20.04'

Der eigentliche Build wird durch Scripte oder vordefinierte Tasks abgebildet. Verwendet man stets Scripte, hat dies den Vorteil, dass diese sich leichter auf andere Umgebungen übertragen lassen. Damit lassen sich die Buildabschnitte auch lokal auf einem Entwicklerrechner gut testen, und man benötigt nicht stets die Azure Umgebung.

Beispielhafte Buildschritte
- script: npm ci
  displayName: Install NPM dependencies
- script: ng test --watch false
  displayName: Component tests
- script: ng build -c production
  displayName: Build app

Auf den Agents lässt sich für den Build Software installieren, wie zum Beispiel Angular CLI oder andere Werkzeuge. Da Azure DevOps auch die Ausführung von (Docker-) Containern erlaubt, bietet sich dies Verfahren ebenfalls an, um vorgefertigte Werkzeuge oder Service-Container bereitzustellen.

Azure DevOps Docker Container

Docker Container können prinzipiell in Azure DevOps Pipelines ausgeführt werden, es gilt jedoch einige Besonderheiten zu beachten:
Azure Pipelines wollen einen eigenen User im Container anlegen und dazu das Werkzeug useradd verwenden. Daher muss das Image entsprechend sudo Rechte bereitstellen, useradd so ausführbar machen, dass es von normalen Nutzern ausgeführt wird, oder der im Container verwendete User muss entsprechend privilegiert sein. Die einfachste Lösung ist, den Container mit der Option --user 0:0 auszuführen, und so im Container entsprechend root-Rechte zu haben.
Außerdem muss im Container in jedem Fall NodeJS installiert sein, da die Azure Pipelines damit im Container Befehle ausführen.

Zu verwendende Container müssen zudem im Abschnitt resources deklariert werden, damit sie später durch container referenziert werden können.
Für den Build von Angular Anwendungen bieten sich die trion/ng-cli-* Images an. Nicht nur ist damit eine versionierte Node-Version und Angular CLI Version verfügbar. Es befindet sich im trion/ng-cli-karma Image ein vorkonfigurierter Chrome, der auch als nicht-headless gestartet werden kann, um z.B. mit Karma Komponententests auszuführen.

Beispiel zur Deklarierung von Docker Containern in Azure DevOps
resources:
  containers:
  - container: trion-ng-cli
    image: trion/ng-cli:latest
    options: --user 0:0
  - container: trion-ng-cli-karma
    image: trion/ng-cli-karma:latest
    options: --user 0:0
  - container: trion-ng-cli-e2e
    image: trion/ng-cli-e2e:latest
    options: --user 0:0

Die so deklarierten Container können dann an einer späteren Stelle auf Job-Ebene verwendet werden. Alle steps werden dann in dem Kontext des Containers ausgeführt.

Beispiel Verwendung von trion/ng-cli in Azure DevOps Job
- job: install
  container: trion-ng-cli
  displayName: NPM-Install
  steps:
  - script: npm ci

Build Inhalte in mehreren Stages

Eine Azure DevOps Pipeline strukturiert sich in folgende Einheiten:

  1. Stages; können unabhängig und parallel ausgeführt werden

  2. Jobs; gehören zu einer Stage, können unabhängig und parallel ausgeführt werden und teilen sich unter speziellen Umständen ein Verzeichnis

  3. Steps; gehören zu einem Job und werden sequentiell ausgeführt

Das Design hat zum Ziel, eine möglichst hohe Parallelisierung, und damit einen schnellen Build erzielen zu können. Daraus ist schnell zu erkennen, dass keine impliziten Abhängigkeiten zwischen den Jobs oder Stages bestehen dürfen, da dies die Buildstabilität bzw. Korrektheit beeinträchtigen kann.

Explizite Abhängigkeiten können auf Job- und Stage-Ebene durch dependsOn ausgedrückt werden. Werden Daten aus einer vorherigen Stage oder einem vorherigen Job benötigt, so müssen diese separat bereitgestellt werden.
In dem Sonderfall, dass es lediglich einen Buildagent gibt, können Jobs auf die Daten vorheriger Jobs zugreifen. Der Zugriff auf Daten aus separaten Stages ist jedoch nicht ohne weitere Aktivitäten möglich.

Für NodeJS basierte Anwendungen werden typischerweise mit npm oder yarn Abhängigkeiten geladen. Diese sollen natürlich nicht in jedem Job oder jeder Stage komplett neu geladen werden.
Dazu gibt es in Azure DevOps Pipelines den Cache Task:
Daten lassen sich damit zwischenspeichern und in folgenden Jobs oder Stages wieder heranziehen. Das kann sogar übergreifend für mehrere Pipelinedurchläufe genutzt werden. Um festzustellen, ob der Cache noch verwendet werden kann, wird ein Cache-Key verwendet. Dieser kann aus einem oder mehreren Strings oder Inhalten von Dateien bestimmt werden. Bei NPM bietet sich natürlich der Inhalt der package-lock.json an - wenn sich an den Abhängigkeiten nichts ändert, müssen diese auch nicht neu bezogen werden.
Wurde ermittelt, dass der Cache verwendet werden kann, wird der Inhalt wieder hergestellt und es kann eine Variable gesetzt werden, die den Positivfall anzeigt. Die Variable kann dann in weiteren Schritten genutzt werden, um zu entscheiden, ob ein Schritt - hier der erneute Abruf der NPM Libraries - ausgeführt werden soll, oder nicht. Dazu wird eine condition verwendet, die den Wert der Variablen auswertet: Im folgenden Beispiel steht das ne für not equals und prüft somit, ob der NPM Cache bereitgestellt wurde.

Beispiel Verwendung von Cache in Azure DevOps Pipeline
- task: Cache@2
  displayName: Cache NPM modules
  inputs:
    key: 'npm | package-lock.json'
    path: '$(Build.SourcesDirectory)/node_modules'
    cacheHitVar: NPM_CACHE_RESTORED
- script: npm ci
    displayName: NPM CI
    condition: ne(variables.NPM_CACHE_RESTORED, 'true')

Wichtig ist, dass der Cache Schritt bei allen folgenden Stages - ganz korrekt: bei jedem Job - erneut ausgeführt wird.

Leider hat Microsoft die YAML Unterstützung unvollständig implementiert: Es lassen sich keine Referenzen definieren oder verwenden. (YAML 1.0 Spezifikation: https://yaml.org/spec/1.0/#id2489959)
Damit lassen sich Wiederholungen leider nicht so elegant ausdrücken.

Alternativ zum Cache kann eine Pipeline auch Artifacts bereitstellen. Dabei handelt es sich um Ergebnisse oder Zwischenergebnisse aus dem Build, die dann an anderer Stelle genutzt werden können. Die Artefakte können auch außerhalb der Pipeline verwendet werden: Sie können über die Weboberfläche bequem heruntergeladen werden oder als Basis dienen, um daraus Folgeartefakte zu bauen.

Beispiel Bereitstellung eines Build Artefakts
- task: PublishBuildArtifacts@1
  displayName: 'Publish dist'
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)/dist.tgz'
    ArtifactName: 'dist'

Buildartefakte haben einen Namen, der gleichzeitig der Ordnername ist, unter dem das Artefakt bereitgestellt wird. Darauf gilt es zu achten, wenn ein Artefakt in einem Folgejob heruntergeladen wird: Der Ordnername bleibt dabei erhalten und muß bei Zugriffen beachtet werden.

Beispiel Verwendung Buildartefakt Folgejobs
- task: DownloadBuildArtifacts@0
  inputs:
    buildType: 'current'
    downloadType: 'single'
    artifactName: 'dist'
    downloadPath: '$(Build.ArtifactStagingDirectory)'

Azure DevOps Angular Beispielprojekt

Das vollständige Beispielprojekt als Angular Anwendung mit Linting (eslint), Komponententests (Karma), e2e Tests (Cypress) und natürlich Build der Anwendung befindet sich hier:
Angular Azure Devops-Beispielprojekt

In der Beispielpipeline werden werden an zwei Stellen Artefakte erzeugt: Sowohl in der build als auch in der publish Stage.

Überblick über die Stages
Abbildung 4. Überblick über die Stages, Build und Publish mit Artefakten

Im Beispiel entsteht hier kein Nutzen aus diesem Vorgehen. Es geht dabei vielmehr darum, zu zeigen, wie Artefakte in verschiedenen Stages erstellt werden und später weiterverwendet werden können.
In der finalen publish Stage würden typischerweise noch weitere Elemente hinzugefügt, wie beispielsweise Quellcode und Dokumentation, wenn das Ergebnis an einen Kunden ausgeliefert wird.

Die Beispielpipeline könnte in weiteren Ausbaustufen erweitert werden:
So könnte die Angular Anwendung als Docker Image gebaut und in einer Container Registry bereitgestellt werden, oder Reports der Testergebnisse erstellt und ebenfalls in Azure DevOps publiziert werden.




Zu den Themen Kubernetes, Docker und Angular bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. 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.