Azure DevOps ist ein cloudbasierter Ansatz von Microsoft, mit dem sich der vollständige Lebenszyklus von Softwareentwicklungsprozessen jeder Art nach dem DevOps-Prinzip verwalten und realisieren lässt. Hierzu stellt Azure DevOps die folgenden fünf Services zur Verfügung:
Azure Boards: Ein Service für das Projektmanagement, der u.a. Kanban-Boards, Backlogs, Dashboards und Berichte zur Dokumentation des Entwicklungsfortschritts bereitstellt.
Azure Repos: Hier kann der Quellcode in Git-Repositories oder TFVC-Repositories verwaltet werden.
Azure Pipelines: Mit diesem Service können CI / CD – Pipelines für das Projekt entwickelt werden.
Azure Test Plans: Dieser Service dient der Verwaltung von Softwaretests und lässt sich hierzu auch in Azure Pipelines integrieren.
Azure Artifacts: Dieser Service ermöglicht die Integration von Paketen in Azure Pipelines.
In diesem Blog soll der Fokus auf Azure DevOps als CI / CD – Tool gelegt werden. Hierzu werden in erster Linie die Services Azure Repos und Azure Pipelines benötigt. Möchte man für ein bereits laufendes Projekt Azure DevOps als CI / CD – Tool nutzen, besteht daher die Notwendigkeit einer Migration der alten Services zu Azure Repos bzw. Azure Pipelines. Die notwendigen Schritte einer solchen Migration werden im Folgenden im Detail erläutert.
Migration Azure Repos
Ein Repository aus einer externen Quelle wie Bitbucket oder GitHub lässt sich unter den Repository-Einstellungen ganz leicht über die zugehörige URL und einer ggfls. benötigten Nutzerkennung importieren. Dabei werden sämtliche Branches mit ihrer Commit-Historie nach Azure Repos migriert, jedoch keine Pull Request. Alte Pull Requests sind daher entweder vor der Migration abzuschließen oder müssen in Azure Repos neu erstellt werden.
Nach der Migration eines Repositories lassen sich unter den Repository-Einstellungen alle nötigen Berechtigungen verwalten. So kann man sowohl für Benutzergruppen als auch für einzelne Nutzer Berechtigungen, wie z.B. das Erzeugen neuer Branches oder Pull Requests, vergeben (Allow) oder verweigern (Deny). Ist eine Berechtigung für einen einzelnen Nutzer nicht gesetzt (Not set), so ist wird sie implizit verweigert, sofern der Nutzer kein Mitglied einer Gruppe ist, in dem die Berechtigung explizit vergeben wurde.
Neben den Berechtigungen lassen sich für ein Repository oder für einzelne Branches auch Policies festlegen, die für jeden Nutzer verbindlich sind. So lassen sich beispielsweise Pushes nach bestimmten Regeln blockieren oder Pull Requests in den master Branch werden nur akzeptiert, nachdem die vorgegebene Mindestanzahl an Prüfern die Änderungen bestätigt hat.
Migration Azure Pipelines
Sobald alle nötigen Repositories nach Azure Repos migriert worden sind, kann mit dem Aufbau der CI / CD – Pipelines in Azure Pipelines begonnen werden. Zuerst muss festgelegt werden, ob man von Microsoft gehostete Build Agents oder selbst gehostete Build Agents nutzen möchte. Im ersten Fall wird für jeden Job eine virtuelle Maschine in der Cloud gestartet, um die Pipeline auszuführen. Im zweiten Fall wird die Pipeline auf einer selbst eingerichteten Maschine ausgeführt, bei der es sich sowohl um ein On-Premise System handeln kann als auch um eine Maschine in einer Cloud, z.B. einer EC2-Instanz in AWS. Um mehrere Jobs parallel ausführen zu können, müssen entsprechend mehrere Build Agents gehostet und in einem Agent-Pool zusammengefasst werden. Die maximale Anzahl an möglichen Parallel-Jobs lässt sich dabei in den Projekt-Einstellungen konfigurieren, wobei zusätzliche Parallel-Jobs kostenpflichtig sind.
Nachdem die Build Agents aufgesetzt sind, kann mit der Implementierung der Pipelines begonnen werden. Hierzu muss zuerst das Repository ausgewählt werden, welches beim Ausführen der Pipeline auf dem Build Agent ausgecheckt wird. Die Pipeline selbst wird mithilfe einer YAML-Datei definiert und in diesem Repository abgelegt. Dabei kann man zwischen vordefinierten Templates oder einer leeren YAML-Datei wählen. Eine ausführliche Dokumentation aller verfügbaren YAML-Keywords zur Definition von Azure Pipelines wird von Microsoft unter dem Link
YAML schema – Azure Pipelines | Microsoft Docs
zur Verfügung gestellt. Im Folgenden soll auf die wichtigsten Keywords eingegangen werden, die für praktisch alle Pipelines relevant sind:
- stages, jobs und steps:
Die Arbeitsschritte einer Pipeline sind in Stages, Jobs und Steps organisiert. Stages sind dabei die höchste Stufe dieser Hierarchie und bestehen aus einem oder mehreren Jobs. Standardmäßig werden Stages sequentiell ausgeführt, während Jobs im selben Stage parallel ausgeführt werden. Somit eignen sich Stages zur Gruppierung äquivalenter Jobs wie etwa das Ausführen eines Build-Prozesses in verschiedenen Umgebungen. Ein einzelner Job besteht wiederum aus einem oder mehreren Steps, die sequentiell ausgeführt werden. Hierbei handelt es sich um voneinander isolierte Prozesse, welche die einzelnen Arbeitsschritte der Pipeline darstellen. Beispiele für einen Step sind etwa das Ausführen eines Shell-Skripts oder der Build eines Docker-Images. Azure Pipelines bietet dafür eine Vielzahl an Templates an, die über einen Assistenten in den aktuellen YAML-Code eingebaut werden können. - trigger:
Übernimmt das Keyword none oder eine Liste von Branches. Wird ein Commit in einen dieser Branches gepushed, so wird die Pipeline automatisch gestartet. In der Standardeinstellung lösen alle Pushs ein Triggern der Pipeline auf dem entsprechenden Branch aus. - pr:
Übernimmt das Keyword none oder eine Liste von Branches. Wird ein Pull Request in einen dieser Branches gemerged, so startet die Pipeline automatisch. In der Standardeinstellung triggert jeder Merge die Pipeline auf dem entsprechenden Branch. - pool:
Gibt den Namen des Agent-Pools an, auf dem die Pipeline ausgeführt werden soll. Das pool Keyword kann sowohl global definiert werden als auch auf Stage- oder Job-Ebene, falls ein Verteilen der Stages bzw. Jobs auf unterschiedliche Agent-Pools nötig ist.
Das folgende Codebeispiel zeigt ein Minimalbeispiel einer Pipeline. Die stage und jobs Keywords können entfallen, falls die Pipeline nur einen Stage bzw. Job enthält.
trigger:
- main
- develop
pr: none
pool:
name: Muster - Pool
stages:
- stage: Build
jobs:
- job: BuildContainerA
steps:
# Settings
- task: CmdLine@2
inputs:
script: 'echo Hello world'
Da ein solches Minimalbeispiel für praktische Anwendungen unzureichend ist, soll im Folgenden auf drei weitere Features eingegangen werden, die für die meisten praxistauglichen CI / CD – Pipelines unverzichtbar sind.
Verbindung mit Cloud Diensten
In der Regel benötigt eine Pipeline Zugang zu den Diensten eines Cloudanbieters. Dabei verfolgt Azure DevOps einen Multi-Cloud-Ansatz, d.h. neben der Azure Cloud kann eine Azure Pipeline auch Verbindungen zu anderen Cloudanbietern, wie z.B. Amazon Web Services (AWS), herstellen und auf dessen Services zugreifen. Hierzu muss man in den Projekt-Einstellungen die zugehörige Service Connection auswählen. Für eine Verbindung zur Azure Cloud benötigt die Service Connection eine Subscription sowie einen Benutzernamen, der über eine angehängte Azure Active Directory authentifiziert werden kann. Möchte man sich dagegen mit der AWS Cloud verbinden, benötigt man für die Service Connection eine Access Key ID, einen Secret Access Key und ggfls. eine IAM-Rolle, welche die Pipeline beim Nutzen der AWS-Services annehmen soll.
Verwendung von Parametern und Variablen
Für eine flexible Nutzung der Pipelines bietet Azure DevOps die Möglichkeit, Parameter zu definieren dessen Standardwerte bei einem manuellen Trigger der Pipeline überschrieben werden können. Zudem gibt es die Möglichkeit, vordefinierte Systemvariablen oder benutzerdefinierte Variablen zu nutzen. Mithilfe der Systemvariablen kann man z.B. den aktuellen Branchnamen oder die aktuelle Commit ID in den Code einsetzen. Eine vollständige Liste der Systemvariablen wird von Microsoft unter dem folgenden Link zur Verfügung gestellt:
Predefined variables – Azure Pipelines | Microsoft Docs
Ein Anwendungsfall für benutzerdefinierte Variablen liegt z.B. vor, wenn Build-Prozesse in Entwicklungsumgebungen mit anderen Werten ausgeführt werden sollen als in der Produktivumgebung. Hierzu benötigt man eine YAML-Datei, in der die entsprechenden Werte durch Variablen ersetzt sind. Diese YAML-Datei kann anschließend von verschiedenen Pipelines mit jeweils unterschiedlichen Werten aufgerufen werden. Zur leichteren Gruppierung der Variablen und ihren Werten bietet Azure DevOps das Tool „Variable Groups“ an. So kann man z.B. für Entwicklung und Produktion je eine Variable Group anlegen und innerhalb dieser Variable Groups den einzelnen Variablen den zur Umgebung passenden Wert zuweisen.
Secrets Management
Für den Zugang zu anderen Diensten benötigt die Pipeline oft sicherheitsrelevante Informationen wie z.B. Passwörter. Azure Pipelines bietet mehrere Wege an, solche Informationen an die Pipeline zu übertragen, ohne diese auf unsichere Weise ins Repository hochzuladen. Zum einen können benutzerdefinierte Variablen als Secret definiert werden, sodass Nutzer ohne Administratorrechte keinen Zugriff auf den Wert der Variable erhalten. Eine weitere Möglichkeit, die Azure DevOps anbietet, sind die Secure Files. Dies sind Dateien, die außerhalb der Repositories in Azure DevOps abgelegt werden und nur Nutzern sowie Pipelines mit entsprechender Berechtigung zur Verfügung stehen. Über den „Download Secure File“ Step kann die Secure File dann von der Pipeline eingelesen werden. Zusätzlich gibt es die Möglichkeit, den Azure Service „Azure Key Vault“ zu nutzen, um sensible Daten sicher in der Azure Cloud zu hinterlegen. Mithilfe des „Azure Key Vault“ Steps erhält die Pipeline Zugriff auf die nötigen Key Vaults. Eine detaillierte Beschreibung dieses Service findet man in dem folgenden Blog:
Azure Security Handling with Azure Key Vault Service – saracus consulting
Fazit
Durch eine Migration nach Azure DevOps lassen sich alle Aufgaben eines DevOps Zykels in einem zentralen Tool verbinden und verwalten. Zwar ist die Migration der Pipelines mit einem gewissen Aufwand verbunden, jedoch lässt sich alter Code oft mit geringfügigen Anpassungen in den Steps der Azure Pipelines wiederverwenden. Zudem wird mithilfe von Parametern und Variablen sowie einem geeigneten Secret Management eine flexible und sichere Nutzung der Pipelines für verschiedenste Anwendungsfälle in beliebigen Umgebungen ermöglicht.
Den größten Vorteil einer Migration zu Azure DevOps bietet jedoch der Multi-Cloud-Ansatz. Dieser ermöglicht eine schnellere und leichtere Migration, da die benötigten Ressourcen nicht erst nach Azure migriert werden müssen. Dadurch wird der Aufwand auch bei weit fortgeschrittenen Projekten nicht zu groß, sodass eine Migration zu Azure DevOps jederzeit eine interessante Option zur Verbesserung der CI / CD – Prozesse im Projekt darstellt.