Einführung in Azure DevOps:
Kontrollierte Umgebungen mit Container-Jobs

Beim Build und Release werden Pipeline-Jobs häufig direkt auf einem Computer oder einer VM ausgeführt, wo der Kontext der Umgebung nicht komplett kontrolliert werden kann. Dieser Grad der Kontrolle ist mithilfe von Container-Jobs möglich, da diese die Jobausführung über definierte Windows- oder Linux-Container ermöglichen. Dieser Beitrag bietet eine Einführung in die Funktionsweise und Anwendung von Container-Jobs in Azure DevOps. Anhand eines Beispiels mit Azure-Containern und Docker-Desktop wird Schritt für Schritt dargelegt, wie eine private Azure Registry erstellt und eingebunden wird und die Container-Jobs genutzt werden.
Um Container-Jobs benutzen zu können, braucht es eine Registry. Diese beinhaltet die benötigten Container, auf denen später die Jobs ausgeführt werden. Dafür können öffentliche oder private Registries verwendet werden. Wenn es nicht möglich ist, eine öffentliche Registry, wie z. B. Docker Hub, zu benutzen, kann auch eine private Registry genutzt werden. Im Folgenden erstellen wir eine Azure Container Registry.
Wie erstelle ich eine Azure Container Registry?
Eine Azure Container Registry kann über mehrere Wege erstellt werden, z. B. über das Azure Portal [1]. Wir erstellen eine Azure Container Registry über PowerShell, da wir das Hochladen eines Images ebenfalls über Docker-Konsolenbefehle ausführen. Falls nötig muss das Azure- PowerShell-Az-Modul installiert werden [2].
Zuerst melden wir uns bei Azure an.
Connect-AzAccount
Dann erstellen wir eine neue Ressourcengruppe, wenn wir keine bestehende benutzen möchten (Location Liste [3]).
New-AzResourceGroup -Name resourceGroupForRegistry -Location westeurope
Als nächstes erstellen wir eine Azure Container Registry namens wwcontainerregistry mit der Basic-SKU, die eine kostenoptimierte Option für Entwickler ist [4].
$registry = New-AzContainerRegistry -ResourceGroupName "resourceGroupForRegistry" -Name "wwcontainerregistry" -EnableAdminUser -Sku Basic
Wie verbinde ich mich mit meiner erstellten Azure Container Registry?
Nach der Erstellung melden wir uns bei der Registry an.
Connect-AzContainerRegistry -Name $registry.Name
Beim Anmelden kann folgende Fehlermeldung erscheinen:
Connect-AzContainerRegistry: One or more errors occurred. (Operation returned an invalid status code 'Unauthorized')
Die Ursache der Fehlermeldung ist, dass man selbst nicht genug Berechtigungen hat, auf die Azure Container Registry zuzugreifen. Um das zu lösen, müssen wir uns selbst Berechtigungen vergeben. Da wir ein Image pushen wollen, ist die Berechtigung acrpush notwendig. Zuerst holen wir uns die benötigten IDs vom Benutzer und unserer Azure Container Registry.
$user = Get-AzADUser -Mail "w.w@example.com" $resource = get-AzResource -Name "wwcontainerregistry"
Dann weisen wir eine Rolle zum Pushen zu.
New-AzRoleAssignment -ObjectId $user.Id -RoleDefinitionName “acrpush” -Scope $resource.ResourceId
Wenn die Ressource nicht gefunden wurde, ist die Variable $resource leer und damit kann der Scope nicht gesetzt werden.
New-AzRoleAssignment: Cannot validate argument on parameter 'Scope'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
Unter Angabe des gleichen Befehls und der Ressourcen-Gruppe bekommt die Liste alle Ressourcen.
get-AzResource -ResourceGroupName "resourceGroupForRegistry"
In dieser Ausgabe kopiert man die Ressource ID und fügt diese beim vorherigen New-AzRoleAssignment hinter Scope ein.
Name : wwcontainerregistry ResourceGroupName : resourceGroupForRegistry ResourceType : Microsoft.ContainerRegistry/registries Location : westeurope Resourceld :/subscriptions/ /resourceGroups/resourceGroupForRegistry/provide rs/Microsoft.ContainerRegistry/registries/wwcontainerregistry Tags :
Wenn bei Ausführung des ursprünglichen Befehls Connect-AzContainerRegistry dieser Fehler kommt, heißt dies, dass der Docker-Client unter Windows mit erhöhten Berechtigungen ausgeführt werden muss, um eine Verbindung herzustellen. Dies liegt daran, dass der Docker-Daemon standardmäßig eine benannte Pipe verwendet, um Befehle vom Docker-Client zu empfangen. Nur Benutzer mit Administratorrechten können auf diese Pipe zugreifen.
Connect-AzContainerRegistry: error during connect: in the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect: Post "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/auth": open //./pipe/docker_engine: The system cannot find the file specified.
Das Problem kann häufig auf einem dieser drei Wege gelöst werden:
- Über den Start des Docker-Services
- Über Update vom Docker-Desktop
- Über die Anpassung der Daemon-Konfig, das machen wir direkt in Docker-Desktop bei den Einstellungen. Dort den Eintrag group: docker hinzufügen. Dabei wird eine Gruppe festgelegt, die Zugriff auf die Pipe bekommt, um mit dem Docker-Daemon zu kommunizieren [5].
Wie pushe ich ein Image in meine Azure Container Registry?
In unserem Beispiel holen wir uns das Ubuntu-Image von Docker Hub und werden dieses in unsere Registry pushen. Zuerst müssen wir das Image pullen.
docker pull ubuntu
Als nächstes wird das Image getaggt. Mit dem Taggen bestimmen wir, wohin das Image gepusht werden kann. Der Tag baut sich aus dem Anmeldeserver (URL), unserer Registry und dem Namen des Images zusammen.
docker tag ubuntu wwcontainerregistry.azurecr.io/ubuntu:latest
Nun kann das Image über den Tag zu der erstellten Azure Container Registry gepusht werden.
docker push wwcontainerregistry.azurecr.io/ubuntu:latest
Nach dem Pushen kann auf folgende Weise der erstellte Tag entfernt werden:
docker rmi wwcontainerregistry.azurecr.io/ubuntu:latest
Wie nutze ich Images aus meiner Container-Registry in Azure DevOps Container Jobs?
Damit wir auf die Container-Registry zugreifen können, müssen wir in Azure DevOps eine Service-Connection unter den Team-Projekteinstellungen anlegen [6]. Dabei wählen wir den Typ Docker Registry. Voraussetzung ist, dass wir Berechtigungen haben, um eine Service-Connection anzulegen.
Nun kann der Registry-Type ausgewählt werden. Dabei gibt es mehrere Möglichkeiten, die Service-Connection einzurichten. Eine Übersicht der Authentifizierungen kann dabei helfen [7].
Wir zählen hier zwei Registry-Typen mit insgesamt drei Authentifizierungsarten auf. Den Registry-Typ "Azure Container Registry", bei dem zwei Authentifizierungen (Service Principal und Managed Service Identity) angeboten werden, die man wahrscheinlich zuerst probieren würde. Und den Registry-Typ "Others" mit einer anderen Authentifizierungsart (Admin User) die wir für unser Beispiel gewählt haben.
- Für den Type "Azure Container Registry" gibt es die Auswahl Service Principal oder Managed Service Identity.
– Beim Service Principal kann es sein, dass nicht jeder die Rechte hat, diesen anzulegen, da Berechtigungen zum Registrieren einer App in Azure AD nötig sind. Diese Berechtigungen hat z. B. die Rolle "Cloud Application Administrator" [8].
– Managed Service Identity würde mit Self-hosted Agents in Azure Portal funktionieren, aber nicht mit Microsoft-hosted Agents [9]. - Für den Type "Others" werden hingegen nur der Anmelde-Server der Registry und die Zugangsdaten benötigt.
In unserem Beispiel fahren wir der Einfachheit halber mit dem Registry-Typ "Others" fort. Dazu navigieren wir im Azure-Portal zu der erstellten Azure Container Registry und wählen bei den Einstellungen den Punkt Zugriffsschlüssel aus. Hier finden wir alle Informationen, um die Service-Connection anzulegen.
In der Service-Connection tragen wir unter "Docker Registry" den Anmelde-Server (inkl. https://), unter "Docker ID" den Registrierungsnamen und unter "Docker Password" das Passwort der Azure Container Registry ein. Nun muss noch ein Name für die Service-Connection vergeben werden.
Im Anschluss navigieren wir in Azure DevOps zu den Azure-Pipelines und legen uns eine Pipeline wie im folgenden Yaml-Code an. Mit dem Keyword container geben wir an, dass wir einen Container-Job ausführen möchten. Über den Parameter image definieren wir das zu nutzende Image. Den Parameter endpoint benötigen wir bei einer privaten Container-Registry, hier wird die Service-Connection angegeben. Das folgende Beispiel greift über die zuvor angelegte Service-Connection auf unsere Azure-Registry zu.
container: image: wwcontainerregistry.azurecr.io/ubuntu:latest endpoint: MyFirstAzureContainerRegistry steps: - script: echo "$(Agent.ContainerMapping)" displayName: ContainerMapping
Wenn alles korrekt konfiguriert ist, sollte die Ausführung problemlos funktionieren. In Abb. 5 ist die Mapping ID des Containers zu sehen. Ohne den Container wäre der Zugriff auf die Container Mapping ID nicht möglich.
Wer direkt öffentliche Container in Docker Hub benutzen will, kann das mit der Angabe des Image-Namens erreichen:
container: image: ubuntu:latest steps: - script: echo "$(Agent.ContainerMapping)" displayName: ContainerMapping
Fazit
In diesem Artikel wurde die Verwendung von Container-Jobs in Azure DevOps vorgestellt und erläutert, wie sie Entwicklern helfen können, Aufgaben in einer kontrollierten Umgebung auszuführen. Durch die Schaffung einer sauberen, isolierten Umgebung können Container-Jobs die Zuverlässigkeit und Reproduzierbarkeit von Builds und Tests verbessern.
- Microsoft: Quickstart: Create an Azure container registry using the Azure portal
- Microsoft: How to install Azure PowerShell
- AzureTracks: Current Azure Region Names — Reference
- Microsoft: Azure Container Registry pricing
- Microsoft: Docker Engine on Windows
- Microsoft: Manage service connections
- Microsoft: Authenticate with an Azure container registry
- Microsoft: Application and service principal objects in Microsoft Entra ID
- Microsoft: Build and publish Docker images to Azure Container Registry
Mehr Infos zur Nutzung von Container Jobs:
- Microsoft: Define container jobs (YAML)