Exécuter un agent autohébergé dans Docker

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

Cet article fournit des instructions pour l’exécution de votre agent Azure Pipelines dans Docker. Vous pouvez configurer un agent autohébergé dans Azure Pipelines pour qu’il s’exécute dans un conteneur Windows Server Core (pour les hôtes Windows) ou Ubuntu Container (pour les hôtes Linux) avec Docker. Cela est utile lorsque vous souhaitez exécuter des agents avec une orchestration externe, comme Azure Container Instances. Dans cet article, vous allez parcourir un exemple de conteneur complet, y compris la gestion de la mise à jour automatique de l’agent.

Windows et Linux sont tous deux pris en charge en tant qu’hôtes de conteneur. Les conteneurs Windows doivent s’exécuter sur un Windows vmImage. Pour exécuter votre agent dans Docker, vous allez passer quelques variables d’environnement à docker run, ce qui configure l’agent pour qu’il se connecte à Azure Pipelines ou Azure DevOps Server. Enfin, vous personnalisez le conteneur en fonction de vos besoins. Les tâches et les scripts peuvent dépendre de la disponibilité d’outils PATHspécifiques sur le conteneur, et il est de votre responsabilité de vous assurer que ces outils sont disponibles.

Cette fonctionnalité nécessite la version de l’agent 2.149 ou ultérieure. Azure DevOps 2019 n’a pas été fourni avec une version d’agent compatible. Toutefois, vous pouvez charger le package d’agent approprié dans votre couche Application si vous souhaitez exécuter des agents Docker.

Windows

Activer Hyper-V

Hyper-V n’est pas activé par défaut sur Windows. Si vous souhaitez fournir une isolation entre les conteneurs, vous devez activer Hyper-V. Sinon, Docker pour Windows ne démarre pas.

Notes

Vous devez activer la virtualisation sur votre machine. Elle est généralement activée par défaut. Toutefois, si l’installation d’Hyper-V échoue, reportez-vous à la documentation de votre système pour savoir comment activer la virtualisation.

Installer Docker pour Windows

Si vous utilisez Windows 10, vous pouvez installer Docker Community Edition. Pour Windows Server 2016, installez l’Êdition Entreprise Docker.

Basculer Docker pour utiliser des conteneurs Windows

Par défaut, Docker pour Windows est configuré pour utiliser des conteneurs Linux. Pour autoriser l’exécution du conteneur Windows, vérifiez que Docker pour Windows exécute le démon Windows.

Créer et générer le fichier Dockerfile

Puis, créer le Dockerfile.

  1. Ouvrez une invite de commandes.

  2. Créez un nouveau répertoire :

    mkdir "C:\azp-agent-in-docker\"
    
  3. Allez dans ce nouveau répertoire :

    cd "C:\azp-agent-in-docker\"
    
  4. Enregistrez le contenu suivant dans un fichier appelé 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. Enregistrez le contenu suivant dans 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. Exécutez la commande suivante dans ce répertoire :

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

    L’image finale est balisée azp-agent:windows.

Démarrer l’image

Maintenant que vous avez créé une image, vous pouvez exécuter un conteneur. Cette opération installe la dernière version de l’agent, la configure et exécute l’agent. Il cible le pool d’agents spécifié (le pool d’agents Default par défaut) d’une instance Azure DevOps ou Azure DevOps Server spécifiée de votre choix :

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

Vous devrez peut-être spécifier le paramètre --network si vous rencontrez des problèmes de réseau.

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

Vous devrez peut-être spécifier les flags --interactive and --tty (ou simplement -it) si vous souhaitez pouvoir arrêter le conteneur et supprimer l'agent avec Ctrl + C.

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

Si vous souhaitez un nouveau conteneur d'agent pour chaque tâche de pipeline, transmettez l'--onceindicateur à la commande run.

docker run < . . . > --once

Avec l'indicateur --once, vous souhaiterez peut-être utiliser un système d'orchestration de conteneur, comme Kubernetes ou Azure Container Instances, pour démarrer une nouvelle copie du conteneur une fois la tâche terminée.

Vous pouvez contrôler le nom de l'agent, le pool d'agents et le répertoire de travail de l'agent à l'aide de variables d'environnement facultatives.

Linux

Installation de Docker

Selon votre distribution Linux, vous pouvez installer Docker Community Edition ou Docker Êdition Entreprise.

Créer et générer le fichier Dockerfile

Puis, créer le Dockerfile.

  1. Ouvrez un terminal.

  2. Créez un répertoire (recommandé) :

    mkdir ~/azp-agent-in-docker/
    
  3. Allez dans ce nouveau répertoire :

    cd ~/azp-agent-in-docker/
    
  4. Enregistrez le contenu suivant dans ~/azp-agent-in-docker/azp-agent-linux.dockerfile :

    • Pour 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" ]
      
    • Pour 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" ]
      

    Décommentez la ligne ENV AGENT_ALLOW_RUNASROOT="true" et supprimez l'ajout de l'utilisateur agent avant cette ligne si vous souhaitez exécuter l'agent en tant que root.

    Remarque

    Les tâches peuvent dépendre des exécutables que votre conteneur est censé fournir. Pour instance, vous devez ajouter les packages zip et unzip à la commande afin d’exécuter les RUN apt install -y tâches ArchiveFiles et ExtractFiles. En outre, comme il s’agit d’une image Linux Ubuntu que l’agent doit utiliser, vous pouvez personnaliser l’image selon vos besoins. Par exemple, si vous avez besoin de créer des applications .NET, vous pouvez suivre le document Installer le SDK .NET ou le runtime .NET sur Ubuntu et l’ajouter à votre image.

  5. Enregistrez le contenu suivant dans ~/azp-agent-in-docker/start.sh, en veillant à utiliser des fins de ligne de style 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 $!
    

    Notes

    Vous devez également utiliser un système d’orchestration de conteneur, comme Kubernetes ou Azure Container Instances, pour démarrer de nouvelles copies du conteneur une fois le travail terminé.

  6. Exécutez la commande suivante dans ce répertoire :

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

    L’image finale est balisée azp-agent:linux.

Démarrer l’image

Maintenant que vous avez créé une image, vous pouvez exécuter un conteneur. Cette opération installe la dernière version de l’agent, la configure et exécute l’agent. Il cible le pool d’agents spécifié (le pool d’agents Default par défaut) d’une instance Azure DevOps ou Azure DevOps Server spécifiée de votre choix :

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

Vous devrez peut-être spécifier les flags --interactive and --tty (ou simplement -it) si vous souhaitez pouvoir arrêter le conteneur et supprimer l'agent avec Ctrl + C.

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

Si vous souhaitez un nouveau conteneur d'agent pour chaque tâche de pipeline, transmettez l'--onceindicateur à la commande run.

docker run < . . . > --once

Avec l'indicateur --once, vous souhaiterez peut-être utiliser un système d'orchestration de conteneur, comme Kubernetes ou Azure Container Instances, pour démarrer une nouvelle copie du conteneur une fois la tâche terminée.

Vous pouvez contrôler le nom de l'agent, le pool d'agents et le répertoire de travail de l'agent à l'aide de variables d'environnement facultatives.

Variables d'environnement

Variable d’environnement Description
AZP_URL URL de l’instance Azure DevOps ou Azure DevOps Server.
AZP_TOKEN Jeton d’accès personnel (PAT) avec étendue Pools d’agents (lecture, gestion), créé par un utilisateur autorisé à configurer des agents, à AZP_URL.
AZP_AGENT_NAME Nom de l’agent (valeur par défaut : nom d’hôte du conteneur).
AZP_POOL Nom du pool d’agents (valeur par défaut : Default).
AZP_WORK Répertoire de travail (valeur par défaut : _work).

Ajouter des outils et personnaliser le conteneur

Vous avez créé un agent de build de base. Vous pouvez étendre le fichier Dockerfile pour inclure des outils supplémentaires et leurs dépendances, ou créer votre propre conteneur en utilisant celui-ci comme couche de base. Assurez-vous simplement que les éléments suivants ne sont pas modifiés :

  • Le start.sh script est appelé par le fichier Dockerfile.
  • Le start.sh script est la dernière commande dans le fichier Dockerfile.
  • Assurez-vous que les conteneurs dérivés ne suppriment aucune des dépendances indiquées par le fichier Dockerfile.

Utiliser Docker dans un conteneur Docker

Pour utiliser Docker à partir d’un conteneur Docker, vous devez lier-monter le socket Docker.

Attention

Cela a de graves implications en matière de sécurité. Le code à l’intérieur du conteneur peut désormais s’exécuter en tant que racine sur votre hôte Docker.

Si vous êtes sûr de vouloir effectuer cette opération, consultez la documentation sur le montage lié sur Docker.com.

Utiliser un cluster Azure Kubernetes Service

Attention

Veuillez considérer que les tâches basées sur Docker ne fonctionneront pas sur AKS 1.19 ou version ultérieure en raison de la restriction Docker dans Docker. Docker a été remplacé par conteneur dans Kubernetes 1.19, et Docker-in-Docker est devenu indisponible.

Déployer et configurer Azure Kubernetes Service

Suivez les étapes décrites dans Démarrage rapide : Déployer un cluster Azure Kubernetes Service (AKS) à l’aide de la Portail Azure. Après cela, votre console PowerShell ou Shell peut utiliser la kubectl ligne de commande.

Déployer et configurer Azure Container Registry

Effectuez les étapes contenues dans Démarrage rapide : Créer un registre de conteneurs Azure avec le portail Azure. Après cela, vous pouvez envoyer (push) et extraire des conteneurs à partir d’Azure Container Registry.

Configurer des secrets et déployer un jeu de réplicas

  1. Créez les secrets sur le cluster AKS.

    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. Exécutez cette commande pour envoyer (push) votre conteneur à Container Registry :

    docker push "<acr-server>/azp-agent:<tag>"
    
  3. Configurez l’intégration de Container Registry pour les clusters AKS existants.

    Notes

    Si vous avez plusieurs abonnements sur le portail Azure, utilisez d’abord cette commande pour sélectionner un abonnement

    az account set --subscription "<subscription id or subscription name>"
    
    az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
    
  4. Enregistrez le contenu suivant dans ~/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
    

    Ce YAML Kubernetes crée un ensemble de réplicas et un déploiement, où replicas: 1 indique le nombre ou les agents qui s’exécutent sur le cluster.

  5. Exécutez la commande suivante :

    kubectl apply -f ReplicationController.yml
    

À présent, vos agents exécutent le cluster AKS.

Définir le paramètre MTU personnalisé

Autoriser la spécification de la valeur MTU pour les réseaux utilisés par les travaux de conteneur (utile pour les scénarios docker-in-docker dans le cluster k8s).

Vous devez définir la variable d'environnement AGENT_DOCKER_MTU_VALUE pour définir la valeur MTU, puis redémarrer l'agent auto-hébergé. Vous trouverez plus d’informations sur le redémarrage de l’agent ici et sur la définition de différentes variables d’environnement pour chaque agent individuel ici.

Cela vous permet de configurer un paramètre réseau pour le conteneur de travaux. L’utilisation de cette commande est similaire à l’utilisation de la commande suivante lors de la configuration du réseau de conteneur :

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

Montage de volumes à l’aide de Docker dans un conteneur Docker

Si un conteneur Docker s’exécute à l’intérieur d’un autre conteneur Docker, ils utilisent tous les deux le démon de l’hôte, de sorte que tous les chemins de montage référencent l’hôte, et non le conteneur.

Par exemple, si nous voulons monter le chemin à partir de l’hôte dans un conteneur Docker externe, nous pouvons utiliser cette commande :

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

Par exemple, si nous voulons monter le chemin à partir de l’hôte dans un conteneur Docker externe, nous pouvons utiliser cette commande :

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

Mais nous ne pouvons pas monter des chemins d’accès à partir d’un conteneur externe vers le conteneur interne ; pour contourner ce problème, nous devons déclarer une variable ENV :

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

Après cela, nous pouvons démarrer le conteneur interne à partir du conteneur externe à l’aide de la commande suivante :

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

Erreurs courantes

Si vous utilisez Windows et que vous obtenez l’erreur suivante :

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

Installez Git Bash en téléchargeant et en installant git-scm.

Exécutez la commande suivante :

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

Réessayez. Vous n’obtenez plus l’erreur.