Eseguire un agente self-hosted in Docker

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

Questo articolo fornisce istruzioni per l'esecuzione dell'agente di Azure Pipelines in Docker. È possibile configurare un agente self-hosted in Azure Pipelines per l'esecuzione in un contenitore Windows Server Core (per gli host Windows) o Ubuntu (per gli host Linux) con Docker. Ciò è utile quando si vogliono eseguire agenti con orchestrazione esterna, ad esempio Istanze di Azure Container. In questo articolo verrà illustrato un esempio completo di contenitore, inclusa la gestione dell'aggiornamento automatico dell'agente.

Sia Windows che Linux sono supportati come host contenitore. I contenitori di Windows devono essere eseguiti in windows vmImage. Per eseguire l'agente in Docker, si passeranno alcune variabili di ambiente a docker run, che configura l'agente per connettersi ad Azure Pipelines o azure DevOps Server. Infine, è possibile personalizzare il contenitore in base alle proprie esigenze. Le attività e gli script possono dipendere da strumenti specifici disponibili nel contenitore ed è responsabilità dell'utente PATHassicurarsi che questi strumenti siano disponibili.

Questa funzionalità richiede l'agente versione 2.149 o successiva. Azure DevOps 2019 non è stato fornito con una versione compatibile dell'agente. Tuttavia, è possibile caricare il pacchetto agente corretto nel livello applicazione se si vogliono eseguire agenti Docker.

Finestre

Abilita Hyper-V

Hyper-V non è abilitato per impostazione predefinita in Windows. Se si vuole fornire l'isolamento tra contenitori, è necessario abilitare Hyper-V. In caso contrario, Docker per Windows non verrà avviato.

Nota

È necessario abilitare la virtualizzazione nel computer. In genere è abilitato per impostazione predefinita. Tuttavia, se l'installazione di Hyper-V non riesce, vedere la documentazione del sistema per informazioni su come abilitare la virtualizzazione.

Installare Docker per Windows

Se si usa Windows 10, è possibile installare Docker Community Edition. Per Windows Server 2016, installare il edizione Enterprise Docker.

Passare a Docker per usare i contenitori di Windows

Per impostazione predefinita, Docker per Windows è configurato per l'uso di contenitori Linux. Per consentire l'esecuzione del contenitore windows, verificare che Docker per Windows esegua il daemon di Windows.

Creare e compilare il Dockerfile

Creare quindi il Dockerfile.

  1. Aprire un prompt dei comandi.

  2. Creare una nuova directory:

    mkdir "C:\azp-agent-in-docker\"
    
  3. Passare a questa nuova directory:

    cd "C:\azp-agent-in-docker\"
    
  4. Salvare il contenuto seguente in un file denominato 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. Salvare il contenuto seguente in 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. Eseguire il comando seguente all'interno di tale directory:

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

    L'immagine finale è contrassegnata con azp-agent:windows.

Avviare l'immagine

Dopo aver creato un'immagine, è possibile eseguire un contenitore. Viene installata la versione più recente dell'agente, la configura ed esegue l'agente. È destinato al pool di agenti specificato (il pool di agenti per impostazione predefinita) di un'istanza Default di Azure DevOps o azure DevOps Server specificata a scelta:

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

Potrebbe essere necessario specificare il --network parametro se si verificano problemi di rete.

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

Potrebbe essere necessario specificare --interactive e --tty flag (o semplicemente -it) se si vuole essere in grado di arrestare il contenitore e rimuovere l'agente con Ctrl + C.

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

Se si vuole un nuovo contenitore di agenti per ogni processo della pipeline, passare il --once flag al run comando .

docker run < . . . > --once

Con il --once flag potrebbe essere necessario usare un sistema di orchestrazione dei contenitori, ad esempio Kubernetes o Istanze di Azure Container, per avviare una nuova copia del contenitore al termine del processo.

È possibile controllare il nome dell'agente, il pool di agenti e la directory di lavoro dell'agente usando variabili di ambiente facoltative.

Linux

Installare Docker

A seconda della distribuzione Linux, è possibile installare Docker Community Edition o Docker edizione Enterprise.

Creare e compilare il Dockerfile

Creare quindi il Dockerfile.

  1. Aprire un terminale.

  2. Creare una nuova directory (operazione consigliata):

    mkdir ~/azp-agent-in-docker/
    
  3. Passare a questa nuova directory:

    cd ~/azp-agent-in-docker/
    
  4. Salvare il contenuto seguente in ~/azp-agent-in-docker/azp-agent-linux.dockerfile:

    • Per Alpine:

      FROM alpine
      ENV TARGETARCH="linux-musl-x64"
      
      # Another option:
      # FROM arm64v8/alpine
      # ENV TARGETARCH="linux-musl-arm64"
      
      RUN apk update
      RUN apk upgrade
      RUN apk add bash curl git icu-libs jq
      
      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" ]
      
    • Per Ubuntu 22.04:

      FROM ubuntu:22.04
      ENV TARGETARCH="linux-x64"
      # Also can be "linux-arm", "linux-arm64".
      
      RUN apt update
      RUN apt upgrade -y
      RUN apt install -y curl git jq libicu70
      
      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" ]
      

    Rimuovere il commento dalla ENV AGENT_ALLOW_RUNASROOT="true" riga e rimuovere l'aggiunta dell'utente agent prima di questa riga se si vuole eseguire l'agente come radice.

    Nota

    Le attività possono dipendere dai file eseguibili previsti dal contenitore. Ad esempio, è necessario aggiungere i zip pacchetti e unzip al RUN apt install -y comando per eseguire le ArchiveFiles attività e ExtractFiles . Inoltre, poiché si tratta di un'immagine Linux Ubuntu da usare per l'agente, è possibile personalizzare l'immagine in base alle esigenze. Ad esempio, se è necessario compilare applicazioni .NET, è possibile seguire il documento Installare .NET SDK o .NET Runtime in Ubuntu e aggiungerlo all'immagine.

  5. Salvare il contenuto seguente in ~/azp-agent-in-docker/start.sh, assicurandosi di usare terminazioni di riga di tipo Unix (LF):

    #!/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 $!
    

    Nota

    È anche necessario usare un sistema di orchestrazione dei contenitori, ad esempio Kubernetes o Istanze di Azure Container, per avviare nuove copie del contenitore al termine del lavoro.

  6. Eseguire il comando seguente all'interno di tale directory:

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

    L'immagine finale è contrassegnata con azp-agent:linux.

Avviare l'immagine

Dopo aver creato un'immagine, è possibile eseguire un contenitore. Viene installata la versione più recente dell'agente, la configura ed esegue l'agente. È destinato al pool di agenti specificato (il pool di agenti per impostazione predefinita) di un'istanza Default di Azure DevOps o azure DevOps Server specificata a scelta:

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

Potrebbe essere necessario specificare --interactive e --tty flag (o semplicemente -it) se si vuole essere in grado di arrestare il contenitore e rimuovere l'agente con Ctrl + C.

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

Se si vuole un nuovo contenitore di agenti per ogni processo della pipeline, passare il --once flag al run comando .

docker run < . . . > --once

Con il --once flag potrebbe essere necessario usare un sistema di orchestrazione dei contenitori, ad esempio Kubernetes o Istanze di Azure Container, per avviare una nuova copia del contenitore al termine del processo.

È possibile controllare il nome dell'agente, il pool di agenti e la directory di lavoro dell'agente usando variabili di ambiente facoltative.

Variabili di ambiente

Variabile di ambiente Descrizione
AZP_URL URL dell'istanza di Azure DevOps o azure DevOps Server.
AZP_TOKEN Token di accesso personale (PAT) con ambito pool di agenti (lettura, gestione), creato da un utente che dispone dell'autorizzazione per configurare gli agenti all'indirizzo AZP_URL.
AZP_AGENT_NAME Nome agente (valore predefinito: nome host contenitore).
AZP_POOL Nome pool di agenti (valore predefinito: Default).
AZP_WORK Directory di lavoro (valore predefinito: _work).

Aggiungere strumenti e personalizzare il contenitore

È stato creato un agente di compilazione di base. È possibile estendere il Dockerfile per includere strumenti aggiuntivi e le relative dipendenze oppure creare un contenitore personalizzato usando questo file come livello di base. Assicurarsi che i seguenti elementi rimangano invariati:

  • Lo start.sh script viene chiamato dal Dockerfile.
  • Lo start.sh script è l'ultimo comando nel Dockerfile.
  • Assicurarsi che i contenitori derivati non rimuovono alcuna delle dipendenze indicate dal Dockerfile.

Usare Docker all'interno di un contenitore Docker

Per usare Docker da un contenitore Docker, è necessario montare il socket Docker.

Attenzione

Questa operazione ha gravi implicazioni per la sicurezza. Il codice all'interno del contenitore può ora essere eseguito come radice nell'host Docker.

Se si è certi di voler eseguire questa operazione, vedere la documentazione sul montaggio di binding in Docker.com.

Usare servizio Azure Kubernetes cluster

Attenzione

Tenere presente che tutte le attività basate su Docker non funzioneranno nel servizio Azure Kubernetes 1.19 o versione successiva a causa di docker in restrizioni docker. Docker è stato sostituito con il contenitore in Kubernetes 1.19 e Docker-in-Docker non è più disponibile.

Distribuire e configurare servizio Azure Kubernetes

Seguire la procedura descritta in Avvio rapido: Distribuire un cluster del servizio Azure Kubernetes servizio Azure Kubernetes usando il portale di Azure. Successivamente, la console di PowerShell o Shell può usare la kubectl riga di comando.

Distribuire e configurare Registro Azure Container

Seguire la procedura descritta in Avvio rapido: Creare un Registro Azure Container usando il portale di Azure. Successivamente, è possibile eseguire il push e il pull dei contenitori da Registro Azure Container.

Configurare i segreti e distribuire un set di repliche

  1. Creare i segreti nel cluster del servizio Azure Kubernetes.

    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. Eseguire questo comando per eseguire il push del contenitore in Registro Contenitori:

    docker push "<acr-server>/azp-agent:<tag>"
    
  3. Configurare l'integrazione del Registro Container per i cluster del servizio Azure Kubernetes esistenti.

    Nota

    Se nel portale di Azure sono presenti più sottoscrizioni, usare prima di tutto questo comando per selezionare una sottoscrizione

    az account set --subscription "<subscription id or subscription name>"
    
    az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
    
  4. Salvare il contenuto seguente in ~/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
    

    Questo YAML di Kubernetes crea un set di repliche e una distribuzione, dove replicas: 1 indica il numero o gli agenti in esecuzione nel cluster.

  5. Eseguire questo comando:

    kubectl apply -f ReplicationController.yml
    

Ora gli agenti eseguiranno il cluster del servizio Azure Kubernetes.

Impostare il parametro MTU personalizzato

Consentire di specificare il valore MTU per le reti usate dai processi del contenitore (utile per gli scenari docker in docker nel cluster k8s).

È necessario impostare la variabile di ambiente AGENT_DOCKER_MTU_VALUE per impostare il valore MTU e quindi riavviare l'agente self-hosted. Altre informazioni sul riavvio dell'agente sono disponibili qui e sull'impostazione di variabili di ambiente diverse per ogni singolo agente qui.

In questo modo è possibile configurare un parametro di rete per il contenitore di processi, l'uso di questo comando è simile all'uso del comando successivo durante la configurazione di rete del contenitore:

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

Montaggio di volumi tramite Docker all'interno di un contenitore Docker

Se un contenitore Docker viene eseguito all'interno di un altro contenitore Docker, entrambi usano il daemon dell'host, quindi tutti i percorsi di montaggio fanno riferimento all'host, non al contenitore.

Ad esempio, se si vuole montare il percorso dall'host al contenitore Docker esterno, è possibile usare questo comando:

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

Se si vuole montare il percorso dall'host al contenitore Docker interno, è possibile usare questo comando:

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

Ma non è possibile montare i percorsi dal contenitore esterno a quello interno; per risolvere questo problema, è necessario dichiarare una variabile ENV:

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

Successivamente, è possibile avviare il contenitore interno dall'esterno usando questo comando:

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

Errori comuni

Se si usa Windows e viene visualizzato l'errore seguente:

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

Installare Git Bash scaricando e installando git-scm.

Eseguire questo comando:

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

Riprovare. Non viene più visualizzato l'errore.