Containeraufträge in YAML-Pipelines

Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2019

In diesem Artikel werden Containeraufträge in Azure Pipelines beschrieben.

Standardmäßig werden Aufträge in Azure Pipelines direkt auf den Hostcomputern ausgeführt, auf denen der Agent installiert ist. Gehostete Agent-Aufträge sind praktisch. Sie erfordern nur einen geringen anfänglichen Einrichtungsaufwand, es muss wenig Infrastruktur gewartet werden und sie sind gut für einfache Projekte geeignet.

Wenn Sie mehr Kontrolle über den Aufgabenkontext wünschen, können Sie Aufträge in Containern definieren und ausführen. Container stellen eine einfache Abstraktion auf dem Hostbetriebssystem dar, die eine Isolation vom Host bereitstellt. Beim Ausführen von Aufträgen in Containern können Sie die exakten Versionen von Betriebssystemen, Tools und Abhängigkeiten auswählen, die für Ihren Build erforderlich sind.

Linux- und Windows-Agents können Pipelineaufträge direkt auf dem Host oder in Containern ausführen. Containeraufträge sind unter macOS nicht verfügbar.

Bei einem Containerauftrag ruft der Agent zuerst den Container ab und startet dann den Container. Anschließend wird jeder Schritt des Auftrags innerhalb des Containers ausgeführt.

Wenn Sie eine differenzierte Steuerung auf der Ebene der einzelnen Buildschritte benötigen, können Sie mit Schrittzielen für jeden Schritt einen Container oder Host auswählen.

Voraussetzungen

  • Sie müssen eine YAML-Pipeline verwenden. Klassische Pipelines unterstützen keine Containeraufträge.
  • Sie müssen einen gehosteten Windows- oder Ubuntu-Agent verwenden. Die Ausführung von Containern wird nur von windows-*- und ubuntu-*-Agents unterstützt. macos-*-Agents unterstützen die Ausführung von Containern nicht.
  • Ihr Agent ist für Containeraufträge eingerichtet.
    • Auf Windows- und Linux-Agents muss Docker installiert sein. Sie benötigen darüber hinaus eine Berechtigung zum Zugriff auf den Docker-Daemon.
    • Container werden nicht unterstützt, wenn der Agent bereits in einem Container ausgeführt wird. Geschachtelte Container sind nicht möglich.

Weitere Containeranforderungen

Für Linux-basierte Container gelten die folgenden Anforderungen. Informationen zu Problemumgehungen finden Sie unter Nonglibc-basierte Container.

  • Bash installiert
  • GNU C Library (glibc)-basiert
  • Ohne ENTRYPOINT
  • Bereitstellung von USER mit Zugriff auf groupadd und weitere privilegierte Befehlen ohne Verwendung von sudo
  • Ausführung von Node.js wie vom Agent bereitgestellt möglich

    Hinweis

    Node.js muss auf Windows-Hosts für Linux-Container vorinstalliert sein.

Einige der auf Docker Hub verfügbaren funktionsreduzierten Container, besonders Container auf Basis von Alpine Linux, erfüllen diese Anforderungen nicht. Container mit einem ENTRYPOINT funktionieren möglicherweise nicht, da Azure-Pipelines docker create und docker exec erwarten, dass der Container stets ausgeführt wird.

Beispiele für einzelne Aufträge

In den folgenden Beispielen wird ein Windows- oder Linux-Container für einen einzelnen Auftrag definiert.

Im folgenden einfachen Beispiel wird ein Linux-Container definiert:

pool:
  vmImage: 'ubuntu-latest'

container: ubuntu:18.04

steps:
- script: printenv

Im vorhergehenden Beispiel wird das System angewiesen, das ubuntu-Image mit dem Tag 18.04 von Docker Hub abzurufen und dann den Container zu starten. Der Befehl printenv wird innerhalb des ubuntu:18.04-Containers ausgeführt.

Mehrere Aufträge

Sie können Container zur Ausführung desselben Schritts in mehreren Aufträgen verwenden. Im folgenden Beispiel wird derselbe Schritt in mehreren Versionen von Ubuntu Linux ausgeführt. Sie müssen das Schlüsselwort jobs nicht angeben, da nur ein einzelner Auftrag definiert ist.

pool:
  vmImage: 'ubuntu-latest'

strategy:
  matrix:
    ubuntu16:
      containerImage: ubuntu:16.04
    ubuntu18:
      containerImage: ubuntu:18.04
    ubuntu20:
      containerImage: ubuntu:20.04

container: $[ variables['containerImage'] ]

steps:
- script: printenv

Mehrere Aufträge mit Agent-Pools auf einem einzelnen Agent-Host

Ein Containerauftrag verwendet die Docker-Konfigurationsdatei des zugrunde liegenden Host-Agents zur Autorisierung der Imageregistrierung. Diese Datei meldet sich am Ende der Containerinitialisierung in der Docker-Registrierung ab. Abrufe des Registrierungsimage für nachfolgende Containeraufträge werden für unauthorized authentication möglicherweise abgelehnt, da ein anderer, parallel ausgeführter Auftrag die Docker-Konfigurationsdatei bereits abgemeldet hat.

Die Lösung besteht in der Festlegung einer Docker-Umgebungsvariable DOCKER_CONFIG, die für jeden Agent-Pool spezifisch ist, der auf dem Agent-Host ausgeführt wird. Exportieren Sie die DOCKER_CONFIG-Umgebungsvariable im Skript runsvc.sh für jede einzelnen Agent-Pool wie folgt:

export DOCKER_CONFIG=./.docker

Startoptionen

Sie können options angeben, um den Containerstart zu steuern, wie im folgenden Beispiel gezeigt:

container:
  image: ubuntu:18.04
  options: --hostname container-test --ip 192.168.0.1

steps:
- script: echo hello

Wenn Sie docker create --help ausführen, erhalten Sie die Liste der Optionen, die Sie an den Docker-Aufruf übergeben können. Es ist nicht sicher, dass alle diese Optionen mit Azure DevOps funktionieren. Überprüfen Sie zuerst, ob Sie eine container-Eigenschaft verwenden können, um dasselbe Ziel zu erreichen.

Weitere Informationen finden Sie in der Befehlsreferenz für docker create und unter Definition von resources.containers.container in der Azure DevOps-YAML-Schemareferenz.

Definition wiederverwendbarer Container

Im folgenden Beispiel werden die Container im Abschnitt resources definiert. Anschließend wird anhand der ihnen zugewiesenen Aliase auf sie verwiesen. Das Schlüsselwort jobs wird der Klarheit halber explizit aufgelistet.

resources:
  containers:
  - container: u16
    image: ubuntu:16.04

  - container: u18
    image: ubuntu:18.04

  - container: u20
    image: ubuntu:20.04

jobs:
- job: RunInContainer
  pool:
    vmImage: 'ubuntu-latest'

  strategy:
    matrix:
      ubuntu16:
        containerResource: u16
      ubuntu18:
        containerResource: u18
      ubuntu20:
        containerResource: u20

  container: $[ variables['containerResource'] ]

  steps:
  - script: printenv

Dienstendpunkte

Sie können Container auch in anderen Registrierungen als in öffentlichen Docker Hub-Registrierungen hosten. Um ein Image in Azure Container Registry oder einer anderen privaten Containerregistrierung (einschließlich privater Docker Hub-Registrierungen) zu hosten, fügen Sie eine Dienstverbindung hinzu, um auf die Registrierung zuzugreifen. Anschließend können Sie in der Containerdefinition auf den Endpunkt verweisen.

Private Docker Hub-Verbindung:

container:
  image: registry:ubuntu1804
  endpoint: private_dockerhub_connection

Azure Container Registry-Verbindung:

container:
  image: myprivate.azurecr.io/windowsservercore:1803
  endpoint: my_acr_connection

Hinweis

Azure Pipelines kann keine Dienstverbindung für Amazon Elastic Container Registry (ECR) einrichten, da Amazon ECR andere Clienttools benötigt, um AWS-Anmeldeinformationen in Anmeldeinformationen zu konvertieren, die Docker zur Authentifizierung verwenden kann.

Nicht-glibc-basierte Container

Der Azure Pipelines-Agent stellt eine Kopie von Node.js bereit, die zum Ausführen von Aufgaben und Skripts erforderlich ist. Navigieren Sie zu Von Microsoft gehostete Agents, im die Version von Node.js für einen gehosteten Agent zu ermitteln.

Die Version von Node.js wird für die C-Laufzeit kompiliert, die in der gehosteten Cloud verwendet wird, in der Regel glibc. Einige Linux-Varianten verwenden andere C-Laufzeiten. Alpine Linux verwendet beispielsweise musl.

Wenn Sie einen Container verwenden möchten, der nicht auf glibc basiert, müssen Sie folgende Aktionen ausführen:

  • Sie müssen eine eigene Kopie von Node.js bereitstellen.
  • Sie müssen Ihrem Image eine Beschreibung hinzufügen, die dem Agent mitteilt, wo sich die Node.js-Binärdatei befindet.
  • Stellen Sie weitere Abhängigkeiten bereit, von denen Azure Pipelines abhängig ist: bash, sudo, which und groupadd.

Bereitstellen eines eigenen Node.js

Wenn Sie einen Container verwenden, der nicht auf glibc basiert, müssen Sie Ihrem Container eine Node-Binärdatei hinzufügen. Node.js 18 ist eine sichere Wahl. Beginnen Sie mit dem node:18-alpine-Image.

Informieren des Agents über Node.js

Der Agent liest die Containerbeschreibung "com.azure.dev.pipelines.handler.node.path". Wenn diese Bezeichnung vorhanden ist, muss sie der Pfad zur Node.js Binärdatei sein.

In einem Image, das auf node:18-alpine basiert, fügen Sie Ihrer Dockerfile-Datei beispielsweise die folgende Zeile hinzu:

LABEL "com.azure.dev.pipelines.agent.handler.node.path"="/usr/local/bin/node"

Hinzufügen von erforderlichen Paketen

Azure Pipelines setzt ein Bash-basiertes System voraus, auf dem die üblichen Verwaltungspakete installiert sind. Insbesondere Alpine Linux verfügt nicht über mehrere der benötigten Pakete. Durch die Installation von bash, sudo und shadow werden die Basisanforderungen erfüllt.

RUN apk add bash sudo shadow

Wenn Sie von integrierten oder Marketplace-Aufgaben abhängig sind, müssen Sie auch die Binärdateien bereitstellen, die für diese erforderlich sind.

Vollständiges Docker-Beispiel

FROM node:18-alpine

RUN apk add --no-cache --virtual .pipeline-deps readline linux-pam \
  && apk add bash sudo shadow \
  && apk del .pipeline-deps

LABEL "com.azure.dev.pipelines.agent.handler.node.path"="/usr/local/bin/node"

CMD [ "node" ]