Ausführen eines selbstgehosteten Agents in Docker

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

Dieser Artikel enthält Anweisungen zum Ausführen Ihres Azure Pipelines-Agents in Docker. Sie können in Azure Pipelines einen selbstgehosteten Agent einrichten, der in Windows Server Core (für Windows-Hosts) oder in einem Ubuntu-Container (für Linux-Hosts) mit Docker ausgeführt wird. Dies ist nützlich, wenn Sie Agents mit äußerer Orchestrierung ausführen möchten, z. B. Azure Container Instances. In diesem Artikel werden Sie ein vollständiges Containerbeispiel durcharbeiten, einschließlich der Selbstaktualisierung des Agents.

Sowohl Windows als auch Linux werden als Containerhosts unterstützt. Windows-Container sollten unter einem Windows-vmImage ausgeführt werden. Um Ihren Agent in Docker auszuführen, übergeben Sie einige Umgebungsvariablen an docker run, wodurch der Agent für die Verbindung mit Azure Pipelines oder Azure DevOps Server konfiguriert wird. Schließlich passen Sie den Container an Ihre Anforderungen an. Tasks und Skripts hängen möglicherweise davon ab, dass bestimmte Tools im PATH des Containers verfügbar sind. Es liegt in Ihrer Verantwortung, sicherzustellen, dass diese Tools verfügbar sind.

Für dieses Feature ist die Agent-Version 2.149 oder höher erforderlich. Azure DevOps 2019 wurde nicht mit einer kompatiblen Agent-Version ausgeliefert. Sie können jedoch das richtige Agent-Paket auf Ihre Anwendungsebene hochladen, wenn Sie Docker-Agents ausführen möchten.

Windows

Aktivieren von Hyper-V

Hyper-V ist unter Windows nicht standardmäßig aktiviert. Wenn Sie Isolierung zwischen Containern bereitstellen möchten, müssen Sie Hyper-V aktivieren. Andernfalls wird Docker für Windows nicht gestartet.

Hinweis

Sie müssen Virtualisierung auf Ihrem Computer aktivieren. Diese ist in der Regel standardmäßig aktiviert. Wenn bei der Installation von Hyper-V jedoch ein Fehler auftritt, können Sie in der Dokumentation Ihres Betriebssystems nachlesen, wie Sie die Virtualisierung aktivieren.

Installieren von Docker für Windows

Wenn Sie Windows 10 verwenden, können Sie die Docker Community Edition installieren. Installieren Sie für Windows Server 2016 die Docker Enterprise Edition.

Umstellen von Docker auf die Verwendung von Windows-Containern

Standardmäßig ist Docker für Windows für die Verwendung von Linux-Containern konfiguriert. Um die Ausführung des Windows-Containers zuzulassen, vergewissern Sie sich, dass Docker für Windows den Windows-Daemon ausführt.

Erstellen und Kompilieren der Dockerfile-Datei

Erstellen Sie nun die Dockerfile-Datei.

  1. Öffnen Sie eine Eingabeaufforderung.

  2. Erstellen Sie ein neues Verzeichnis:

    mkdir "C:\azp-agent-in-docker\"
    
  3. Wechseln Sie zu diesem neuen Verzeichnis:

    cd "C:\azp-agent-in-docker\"
    
  4. Speichern Sie den folgenden Inhalt in einer Datei namens C:\azp-agent-in-docker\azp-agent-windows.dockerfile:

    FROM mcr.microsoft.com/windows/servercore:ltsc2022
    
    WORKDIR /azp/
    
    COPY ./start.ps1 ./
    
    CMD powershell .\start.ps1
    
  5. Speichern Sie den folgendem Inhalt unter C:\azp-agent-in-docker\start.ps1:

    function Print-Header ($header) {
      Write-Host "`n${header}`n" -ForegroundColor Cyan
    }
    
    if (-not (Test-Path Env:AZP_URL)) {
      Write-Error "error: missing AZP_URL environment variable"
      exit 1
    }
    
    if (-not (Test-Path Env:AZP_TOKEN_FILE)) {
      if (-not (Test-Path Env:AZP_TOKEN)) {
        Write-Error "error: missing AZP_TOKEN environment variable"
        exit 1
      }
    
      $Env:AZP_TOKEN_FILE = "\azp\.token"
      $Env:AZP_TOKEN | Out-File -FilePath $Env:AZP_TOKEN_FILE
    }
    
    Remove-Item Env:AZP_TOKEN
    
    if ((Test-Path Env:AZP_WORK) -and -not (Test-Path $Env:AZP_WORK)) {
      New-Item $Env:AZP_WORK -ItemType directory | Out-Null
    }
    
    New-Item "\azp\agent" -ItemType directory | Out-Null
    
    # Let the agent ignore the token env variables
    $Env:VSO_AGENT_IGNORE = "AZP_TOKEN,AZP_TOKEN_FILE"
    
    Set-Location agent
    
    Print-Header "1. Determining matching Azure Pipelines agent..."
    
    $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$(Get-Content ${Env:AZP_TOKEN_FILE})"))
    $package = Invoke-RestMethod -Headers @{Authorization=("Basic $base64AuthInfo")} "$(${Env:AZP_URL})/_apis/distributedtask/packages/agent?platform=win-x64&`$top=1"
    $packageUrl = $package[0].Value.downloadUrl
    
    Write-Host $packageUrl
    
    Print-Header "2. Downloading and installing Azure Pipelines agent..."
    
    $wc = New-Object System.Net.WebClient
    $wc.DownloadFile($packageUrl, "$(Get-Location)\agent.zip")
    
    Expand-Archive -Path "agent.zip" -DestinationPath "\azp\agent"
    
    try {
      Print-Header "3. Configuring Azure Pipelines agent..."
    
      .\config.cmd --unattended `
        --agent "$(if (Test-Path Env:AZP_AGENT_NAME) { ${Env:AZP_AGENT_NAME} } else { hostname })" `
        --url "$(${Env:AZP_URL})" `
        --auth PAT `
        --token "$(Get-Content ${Env:AZP_TOKEN_FILE})" `
        --pool "$(if (Test-Path Env:AZP_POOL) { ${Env:AZP_POOL} } else { 'Default' })" `
        --work "$(if (Test-Path Env:AZP_WORK) { ${Env:AZP_WORK} } else { '_work' })" `
        --replace
    
      Print-Header "4. Running Azure Pipelines agent..."
    
      .\run.cmd
    } finally {
      Print-Header "Cleanup. Removing Azure Pipelines agent..."
    
      .\config.cmd remove --unattended `
        --auth PAT `
        --token "$(Get-Content ${Env:AZP_TOKEN_FILE})"
    }
    
  6. Führen Sie den folgenden Befehl innerhalb diesed Verzeichnisses aus:

    docker build --tag "azp-agent:windows" --file "./azp-agent-windows.dockerfile" .
    

    Das endgültige Image wird als azp-agent:windows markiert.

Starten des Images

Nachdem Sie nun ein Image erstellt haben, können Sie einen Container ausführen. Dadurch wird die neueste Version des Agents installiert, der Agent konfiguriert und dann ausgeführt. Das Ziel ist der angegebene Agentpool (standardmäßig Default) einer angegebenen Azure DevOps- oder Azure DevOps Server-Instanz Ihrer Wahl:

docker run -e AZP_URL="<Azure DevOps instance>" -e AZP_TOKEN="<Personal Access Token>" -e AZP_POOL="<Agent Pool Name>" -e AZP_AGENT_NAME="Docker Agent - Windows" --name "azp-agent-windows" azp-agent:windows

Bei Netzwerkproblemen müssen Sie möglicherweise den --network-Parameter angeben.

docker run --network "Default Switch" < . . . >

Wenn Sie den Container beenden und den Agent mit --interactive--tty-it entfernen möchten, müssen Sie möglicherweise die Flags Ctrl und + oder einfach C angeben.

docker run --interactive --tty < . . . >

Wenn Sie einen neuen Agent-Container für jeden Pipelineauftrag benötigen, übergeben Sie das --once-Flag an den run-Befehl.

docker run < . . . > --once

Sie sollten mit dem --once-Flag ein Containerorchestrierungssystem wie Kubernetes oder Azure Container Instances verwenden, um nach Abschluss des Auftrags eine neue Kopie des Containers zu starten.

Sie können den Namen und Pool sowie das Arbeitsverzeichnis des Agents mithilfe optionaler Umgebungsvariablen steuern.

Linux

Installieren von Docker

Abhängig von Ihrer Linux-Distribution können Sie entweder Docker Community Edition oder Docker Enterprise Edition installieren.

Erstellen und Kompilieren der Dockerfile-Datei

Erstellen Sie nun die Dockerfile-Datei.

  1. Öffnen Sie ein Terminal.

  2. Erstellen Sie ein neues Verzeichnis (empfohlen):

    mkdir ~/azp-agent-in-docker/
    
  3. Wechseln Sie zu diesem neuen Verzeichnis:

    cd ~/azp-agent-in-docker/
    
  4. Speichern Sie den folgendem Inhalt unter ~/azp-agent-in-docker/azp-agent-linux.dockerfile:

    • Für Alpine:

      FROM alpine
      
      RUN apk update
      RUN apk upgrade
      RUN apk add bash curl git icu-libs jq
      
      ENV TARGETARCH="linux-musl-x64"
      
      WORKDIR /azp/
      
      COPY ./start.sh ./
      RUN chmod +x ./start.sh
      
      RUN adduser -D agent
      RUN chown agent ./
      USER agent
      # Another option is to run the agent as root.
      # ENV AGENT_ALLOW_RUNASROOT="true"
      
      ENTRYPOINT ./start.sh
      
    • Für Ubuntu 22.04:

      FROM ubuntu:22.04
      
      RUN apt update -y && apt upgrade -y && apt install curl git jq libicu70 -y
      
      # Also can be "linux-arm", "linux-arm64".
      ENV TARGETARCH="linux-x64"
      
      WORKDIR /azp/
      
      COPY ./start.sh ./
      RUN chmod +x ./start.sh
      
      # Create agent user and set up home directory
      RUN useradd -m -d /home/agent agent
      RUN chown -R agent:agent /azp /home/agent
      
      USER agent
      # Another option is to run the agent as root.
      # ENV AGENT_ALLOW_RUNASROOT="true"
      
      ENTRYPOINT ./start.sh
      

    Heben Sie die Auskommentierung der Zeile ENV AGENT_ALLOW_RUNASROOT="true" auf, und fügen Sie den oder die agent-Benutzer*in vor dieser Zeile ein, wenn Sie den Agent mit root-Berechtigungen ausführen möchten.

    Hinweis

    Tasks können von ausführbaren Dateien abhängen, die von Ihrem Container bereitgestellt werden sollen. Beispielsweise müssen Sie dem RUN apt install -y-Befehl die zip- und unzip-Pakete hinzufügen, um die ArchiveFiles- und ExtractFiles-Tasks auszuführen. Da dies ein Linux Ubuntu-Image für den Agent ist, können Sie das Image nach Bedarf anpassen. Wenn Sie z. B. .NET-Anwendungen erstellen müssen, können Sie das Dokument Installieren des .NET SDK oder der .NET-Runtime unter Ubuntu verwenden und Ihrem Image diese Elemente hinzufügen.

  5. Speichern Sie den folgenden Inhalt unter ~/azp-agent-in-docker/start.sh, und stellen Sie sicher, dass Zeilenvorschübe im Unix-Stil (LF) verwendet werden:

    #!/bin/bash
    set -e
    
    if [ -z "${AZP_URL}" ]; then
      echo 1>&2 "error: missing AZP_URL environment variable"
      exit 1
    fi
    
    if [ -z "${AZP_TOKEN_FILE}" ]; then
      if [ -z "${AZP_TOKEN}" ]; then
        echo 1>&2 "error: missing AZP_TOKEN environment variable"
        exit 1
      fi
    
      AZP_TOKEN_FILE="/azp/.token"
      echo -n "${AZP_TOKEN}" > "${AZP_TOKEN_FILE}"
    fi
    
    unset AZP_TOKEN
    
    if [ -n "${AZP_WORK}" ]; then
      mkdir -p "${AZP_WORK}"
    fi
    
    cleanup() {
      trap "" EXIT
    
      if [ -e ./config.sh ]; then
        print_header "Cleanup. Removing Azure Pipelines agent..."
    
        # If the agent has some running jobs, the configuration removal process will fail.
        # So, give it some time to finish the job.
        while true; do
          ./config.sh remove --unattended --auth "PAT" --token $(cat "${AZP_TOKEN_FILE}") && break
    
          echo "Retrying in 30 seconds..."
          sleep 30
        done
      fi
    }
    
    print_header() {
      lightcyan="\033[1;36m"
      nocolor="\033[0m"
      echo -e "\n${lightcyan}$1${nocolor}\n"
    }
    
    # Let the agent ignore the token env variables
    export VSO_AGENT_IGNORE="AZP_TOKEN,AZP_TOKEN_FILE"
    
    print_header "1. Determining matching Azure Pipelines agent..."
    
    AZP_AGENT_PACKAGES=$(curl -LsS \
        -u user:$(cat "${AZP_TOKEN_FILE}") \
        -H "Accept:application/json;" \
        "${AZP_URL}/_apis/distributedtask/packages/agent?platform=${TARGETARCH}&top=1")
    
    AZP_AGENT_PACKAGE_LATEST_URL=$(echo "${AZP_AGENT_PACKAGES}" | jq -r ".value[0].downloadUrl")
    
    if [ -z "${AZP_AGENT_PACKAGE_LATEST_URL}" -o "${AZP_AGENT_PACKAGE_LATEST_URL}" == "null" ]; then
      echo 1>&2 "error: could not determine a matching Azure Pipelines agent"
      echo 1>&2 "check that account "${AZP_URL}" is correct and the token is valid for that account"
      exit 1
    fi
    
    print_header "2. Downloading and extracting Azure Pipelines agent..."
    
    curl -LsS "${AZP_AGENT_PACKAGE_LATEST_URL}" | tar -xz & wait $!
    
    source ./env.sh
    
    trap "cleanup; exit 0" EXIT
    trap "cleanup; exit 130" INT
    trap "cleanup; exit 143" TERM
    
    print_header "3. Configuring Azure Pipelines agent..."
    
    ./config.sh --unattended \
      --agent "${AZP_AGENT_NAME:-$(hostname)}" \
      --url "${AZP_URL}" \
      --auth "PAT" \
      --token $(cat "${AZP_TOKEN_FILE}") \
      --pool "${AZP_POOL:-Default}" \
      --work "${AZP_WORK:-_work}" \
      --replace \
      --acceptTeeEula & wait $!
    
    print_header "4. Running Azure Pipelines agent..."
    
    chmod +x ./run.sh
    
    # To be aware of TERM and INT signals call ./run.sh
    # Running it with the --once flag at the end will shut down the agent after the build is executed
    ./run.sh "$@" & wait $!
    

    Hinweis

    Sie müssen auch ein Containerorchestrierungssystem wie Kubernetes oder Azure Container Instances verwenden, um nach Abschluss der Aufgabe neue Kopien des Containers zu starten.

  6. Führen Sie den folgenden Befehl innerhalb diesed Verzeichnisses aus:

    docker build --tag "azp-agent:linux" --file "./azp-agent-linux.dockerfile" .
    

    Das endgültige Image wird als azp-agent:linux markiert.

Starten des Images

Nachdem Sie nun ein Image erstellt haben, können Sie einen Container ausführen. Dadurch wird die neueste Version des Agents installiert, der Agent konfiguriert und dann ausgeführt. Das Ziel ist der angegebene Agentpool (standardmäßig Default) einer angegebenen Azure DevOps- oder Azure DevOps Server-Instanz Ihrer Wahl:

docker run -e AZP_URL="<Azure DevOps instance>" -e AZP_TOKEN="<Personal Access Token>" -e AZP_POOL="<Agent Pool Name>" -e AZP_AGENT_NAME="Docker Agent - Linux" --name "azp-agent-linux" azp-agent:linux

Wenn Sie den Container beenden und den Agent mit --interactive--tty-it entfernen möchten, müssen Sie möglicherweise die Flags Ctrl und + oder einfach C angeben.

docker run --interactive --tty < . . . >

Wenn Sie einen neuen Agent-Container für jeden Pipelineauftrag benötigen, übergeben Sie das --once-Flag an den run-Befehl.

docker run < . . . > --once

Sie sollten mit dem --once-Flag ein Containerorchestrierungssystem wie Kubernetes oder Azure Container Instances verwenden, um nach Abschluss des Auftrags eine neue Kopie des Containers zu starten.

Sie können den Namen und Pool sowie das Arbeitsverzeichnis des Agents mithilfe optionaler Umgebungsvariablen steuern.

Umgebungsvariablen

Umgebungsvariable BESCHREIBUNG
AZP_URL Die URL der Azure DevOps- oder Azure DevOps Server-Instanz.
AZP_TOKEN Persönliches Zugriffstoken (Personal Access Token, PAT) mit Agent-Poolbereich (Lesen, Verwalten), erstellt von einem Benutzer, der über die Berechtigung zum Konfigurieren von Agents verfügt, unter AZP_URL.
AZP_AGENT_NAME Der Agent-Name (Standardwert: der Containerhostname).
AZP_POOL Der Name des Agent-Pools (Standardwert: Default).
AZP_WORK Das Arbeitsverzeichnis (Standardwert: _work).

Hinzufügen von Tools und Anpassen des Containers

Sie haben einen einfachen Build-Agent erstellt. Sie können die Dockerfile-Datei erweitern, um zusätzliche Tools und deren Abhängigkeiten einzuschließen, oder Ihren eigenen Container erstellen, indem Sie diesen Container als Basisebene verwenden. Stellen Sie nur sicher, dass Folgendes unberührt bleibt:

  • Das start.sh-Skript wird von der Dockerfile-Datei aufgerufen.
  • Das start.sh-Skript ist der letzte Befehl in der Dockerfile-Datei.
  • Stellen Sie sicher, dass abgeleitete Containern keine der Abhängigkeiten entfernen, die in der Dockerfile-Datei angegeben werden.

Verwenden von Docker in einem Docker-Container

Um Docker aus einem Docker-Container zu verwenden, binden Sie den Docker-Socket ein (bind-mount).

Achtung

Dies hat schwerwiegende Auswirkungen auf die Sicherheit. Der Code im Container kann jetzt als Stammverzeichnis auf Ihrem Docker-Host ausgeführt werden.

Wenn Sie sicher sind, dass Sie so vorgehen möchten, lesen Sie die Dokumentation zu bind-mount auf Docker.com.

Verwenden eines Azure Kubernetes Service-Clusters

Achtung

Beachten Sie unbedingt, dass Docker-basierte Aufgaben aufgrund einer Docker-in-Docker-Einschränkung nicht unter AKS 1.19 oder höher funktionieren. Docker wurde in Kubernetes 1.19 durch containerd ersetzt, und Docker-in-Docker war nicht mehr verfügbar.

Bereitstellen und Konfigurieren von Azure Kubernetes Service

Führen Sie die Schritte unter Schnellstart: Bereitstellen eines AKS-Clusters (Azure Kubernetes Service) mithilfe des Azure-Portal aus. Danach kann Ihre PowerShell- oder Shell-Konsole die kubectl-Befehlszeile verwenden.

Bereitstellen und Konfigurieren von Azure Container Registry

Befolgen Sie die unter Schnellstart: Erstellen einer Azure-Containerregistrierung über das Azure-Portal beschriebenen Schritte. Danach können Sie Container in Azure Container Registry pushen und daraus pullen.

Konfigurieren von Geheimnissen und Bereitstellen einer Replikatgruppe

  1. Erstellen Sie die Geheimnisse im AKS-Cluster.

    kubectl create secret generic azdevops \
      --from-literal=AZP_URL=https://dev.azure.com/yourOrg \
      --from-literal=AZP_TOKEN=YourPAT \
      --from-literal=AZP_POOL=NameOfYourPool
    
  2. Führen Sie diesen Befehl aus, um Ihren Container in Container Registry zu pushen:

    docker push "<acr-server>/azp-agent:<tag>"
    
  3. Konfigurieren Sie Container Registry-Integration für vorhandene AKS-Cluster.

    Hinweis

    Wenn Sie über mehrere Abonnements im Azure-Portal verfügen, verwenden Sie zunächst diesen Befehl, um ein Abonnement auszuwählen.

    az account set --subscription "<subscription id or subscription name>"
    
    az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
    
  4. Speichern Sie den folgendem Inhalt unter ~/AKS/ReplicationController.yml:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: azdevops-deployment
      labels:
        app: azdevops-agent
    spec:
      replicas: 1 # here is the configuration for the actual agent always running
      selector:
        matchLabels:
          app: azdevops-agent
      template:
        metadata:
          labels:
            app: azdevops-agent
        spec:
          containers:
          - name: kubepodcreation
            image: <acr-server>/azp-agent:<tag>
            env:
              - name: AZP_URL
                valueFrom:
                  secretKeyRef:
                    name: azdevops
                    key: AZP_URL
              - name: AZP_TOKEN
                valueFrom:
                  secretKeyRef:
                    name: azdevops
                    key: AZP_TOKEN
              - name: AZP_POOL
                valueFrom:
                  secretKeyRef:
                    name: azdevops
                    key: AZP_POOL
            volumeMounts:
            - mountPath: /var/run/docker.sock
              name: docker-volume
          volumes:
          - name: docker-volume
            hostPath:
              path: /var/run/docker.sock
    

    Dieser Kubernetes-YAML-Code erstellt eine Replikatgruppe und eine Bereitstellung, wobei replicas: 1 die Anzahl oder die Agents angibt, die im Cluster ausgeführt werden.

  5. Führen Sie diesen Befehl aus:

    kubectl apply -f ReplicationController.yml
    

Jetzt führen Ihre Agents den AKS-Cluster aus.

Festlegen des benutzerdefinierten MTU-Parameters

Zulassen der Angabe des MTU-Werts für Netzwerke, die von Containeraufträgen verwendet werden (nützlich für Docker-in-Docker-Szenarien im k8s-Cluster).

Sie müssen die Umgebungsvariable AGENT_DOCKER_MTU_VALUE festlegen, um den MTU-Wert festzulegen, und den selbstgehosteten Agent dann neu starten. Weitere Informationen zum Neustart des Agents finden Sie hier und zum Festlegen verschiedener Umgebungsvariablen für jeden einzelnen Agent hier.

Dadurch können Sie einen Netzwerkparameter für den Auftragscontainer einrichten. Die Verwendung dieses Befehls ähnelt der Verwendung des nächsten Befehls während der Konfiguration des Containernetzwerks:

-o com.docker.network.driver.mtu=AGENT_DOCKER_MTU_VALUE

Einbinden von Volumes mithilfe von Docker in einem Docker-Container

Wenn ein Docker-Container in einem anderen Docker-Container ausgeführt wird, verwenden beide den Daemon des Hosts, sodass alle Einbindungspfade auf den Host und nicht auf den Container verweisen.

Wenn Sie beispielsweise den Pfad vom Host in einen äußeren Docker-Container einbinden möchten, können Sie diesen Befehl verwenden:

docker run ... -v "<path-on-host>:<path-on-outer-container>" ...

Wenn Sie den Pfad vom Host in einen inneren Docker-Container einbinden möchten, können Sie diesen Befehl verwenden:

docker run ... -v "<path-on-host>:<path-on-inner-container>" ...

Sie können jedoch keine Pfade aus dem äußeren Container in den inneren Container einbinden. Um dieses Problem zu umgehen, müssen Sie eine ENV-Variable deklarieren:

docker run ... --env DIND_USER_HOME=$HOME ...

Danach können Sie den inneren Container mit dem folgenden Befehl aus dem äußeren Container starten:

docker run ... -v "${DIND_USER_HOME}:<path-on-inner-container>" ...

Häufige Fehler

Wenn Sie Windows verwenden und der folgende Fehler angezeigt wird:

standard_init_linux.go:178: exec user process caused "no such file or directory"

Installieren Sie Git Bash, indem Sie git-scm herunterladen und installieren.

Führen Sie diesen Befehl aus:

dos2unix ~/azp-agent-in-docker/Dockerfile
dos2unix ~/azp-agent-in-docker/start.sh
git add .
git commit -m "Fixed CR"
git push

Versuchen Sie es erneut. Sie erhalten diesen Fehler nicht mehr.