Ejecución de un agente autohospedado en Docker

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

En este artículo se proporcionan instrucciones para ejecutar el agente de Azure Pipelines en Docker. Puede configurar un agente autohospedado en Azure Pipelines para que se ejecute dentro de un contenedor de Windows Server Core (para hosts de Windows) o de Ubuntu (para hosts de Linux) con Docker. Resulta útil si desea ejecutar agentes con orquestación externa, como, por ejemplo, Azure Container Instances. En este artículo se le guiará por un ejemplo de contenedor completo, incluido el control de la actualización automática del agente.

Tanto Windows como Linux se admiten como hosts de contenedor. Los contenedores de Windows deben ejecutarse en una vmImage de Windows. Para ejecutar el agente en Docker, deberá pasar algunas variables de entorno a docker run, que configura el agente para conectarse a Azure Pipelines o a Azure DevOps Server. Por último, deberá personalizar el contenedor para satisfacer sus necesidades. Las tareas y los scripts pueden depender de herramientas específicas que estén disponibles en la PATH del contenedor y es responsabilidad suya asegurarse de que estas herramientas estén disponibles.

Esta característica requiere la versión del agente 2.149 o posterior. Azure DevOps 2019 no se ha actualizado con una versión del agente compatible. Sin embargo, puede cargar el paquete de agente correcto en el nivel de aplicación si desea ejecutar agentes de Docker.

Windows

Habilitar Hyper-V

Hyper-V no está habilitado de forma predeterminada en Windows. Si desea proporcionar aislamiento entre contenedores, debe habilitar Hyper-V. De lo contrario, Docker para Windows no se iniciará.

Nota:

Debe habilitar la virtualización en la máquina. Suele estar habilitado de forma predeterminada. Sin embargo, si se produce un error en la instalación de Hyper-V, consulte la documentación del sistema sobre cómo habilitar la virtualización.

Instalación de Docker para Windows

Si usa Windows 10, puede instalar Docker Community Edition. Para Windows Server 2016, debe instalar Docker Enterprise Edition.

Cambio de Docker para usar contenedores de Windows

De forma predeterminada, Docker para Windows está configurado para usar contenedores de Linux. Para permitir la ejecución del contenedor de Windows, confirme que Docker para Windows ejecuta el demonio de Windows.

Crear y compilar el Dockerfile

A continuación, cree el archivo Dockerfile.

  1. Abra un símbolo del sistema.

  2. Cree un directorio:

    mkdir "C:\azp-agent-in-docker\"
    
  3. Vaya a este nuevo directorio:

    cd "C:\azp-agent-in-docker\"
    
  4. Guarde el siguiente contenido en un archivo denominado 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. Guarde el siguiente contenido en 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. Ejecute el comando siguiente desde ese directorio:

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

    La imagen final se etiqueta azp-agent:windows.

Iniciar la imagen

Ahora que ha creado una imagen, puede ejecutar un contenedor. Se instala la versión más reciente del agente, la configura y ejecuta el agente. Tiene como destino el grupo de agentes especificado (el grupo de agentes de Default de forma predeterminada) de una instancia de Azure DevOps o Azure DevOps Server especificada de su elección:

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

Es posible que tenga que especificar el parámetro --network si tiene problemas de red.

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

Es posible que tenga que especificar las marcas --interactive y --tty (o simplemente -it) si desea poder detener el contenedor y quitar el agente con Ctrl + C.

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

Si desea un contenedor de agentes nuevo para cada trabajo de canalización, pase la marca --once al comando run.

docker run < . . . > --once

Con la marca --once, es posible que quiera usar un sistema de orquestación de contenedores, como Kubernetes o Azure Container Instances, para iniciar una nueva copia del contenedor cuando se complete el trabajo.

Puede controlar el nombre del agente, el grupo de agentes y el directorio de trabajo del agente mediante variables de entorno opcionales.

Linux

Instalación de Docker

En función de la distribución de Linux que tenga, puede instalar Docker Community Edition o Docker Enterprise Edition.

Crear y compilar el Dockerfile

A continuación, cree el archivo Dockerfile.

  1. Abra un terminal.

  2. Cree un directorio (recomendado):

    mkdir ~/azp-agent-in-docker/
    
  3. Vaya a este nuevo directorio:

    cd ~/azp-agent-in-docker/
    
  4. Guarde el siguiente contenido en ~/azp-agent-in-docker/azp-agent-linux.dockerfile:

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

    Quite la marca de comentario de la línea ENV AGENT_ALLOW_RUNASROOT="true" y deshaga agregar al usuario agent antes de esta línea si desea ejecutar el agente como raíz.

    Nota:

    Las tareas pueden depender de los ejecutables que se prevé que proporcione el contenedor. Por ejemplo, debe agregar los paquetes zip y unzip al comando RUN apt install -y para poder ejecutar las tareas ArchiveFiles y ExtractFiles. Además, como se trata de una imagen de Linux Ubuntu para que la use el agente, puede personalizar la imagen según sea necesario. Por ejemplo, si necesita compilar aplicaciones .NET, puede seguir el documento Instalación del SDK de .NET o el entorno de ejecución de .NET en Ubuntu y agregarlo a la imagen.

  5. Guarde el siguiente contenido en ~/azp-agent-in-docker/start.sh, asegurándose de usar finales de línea de estilo 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:

    También debe usar un sistema de orquestación de contenedores, como Kubernetes o Azure Container Instances, para iniciar nuevas copias del contenedor cuando finalice el trabajo.

  6. Ejecute el comando siguiente desde ese directorio:

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

    La imagen final se etiqueta azp-agent:linux.

Iniciar la imagen

Ahora que ha creado una imagen, puede ejecutar un contenedor. Se instala la versión más reciente del agente, la configura y ejecuta el agente. Tiene como destino el grupo de agentes especificado (el grupo de agentes de Default de forma predeterminada) de una instancia de Azure DevOps o Azure DevOps Server especificada de su elección:

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

Es posible que tenga que especificar las marcas --interactive y --tty (o simplemente -it) si desea poder detener el contenedor y quitar el agente con Ctrl + C.

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

Si desea un contenedor de agentes nuevo para cada trabajo de canalización, pase la marca --once al comando run.

docker run < . . . > --once

Con la marca --once, es posible que quiera usar un sistema de orquestación de contenedores, como Kubernetes o Azure Container Instances, para iniciar una nueva copia del contenedor cuando se complete el trabajo.

Puede controlar el nombre del agente, el grupo de agentes y el directorio de trabajo del agente mediante variables de entorno opcionales.

Variables de entorno

Variable de entorno Descripción
AZP_URL Dirección URL de la instancia de Azure DevOps o Azure DevOps Server.
AZP_TOKEN Token de acceso personal (PAT) con el ámbito Grupos de agentes (lectura, administración), creado por un usuario que tiene permiso para configurar agentes, en AZP_URL.
AZP_AGENT_NAME Nombre del agente (valor predeterminado: el nombre de host del contenedor).
AZP_POOL Nombre del grupo de agentes (valor predeterminado: Default).
AZP_WORK Directorio de trabajo (valor predeterminado: _work).

Adición de herramientas y personalización del contenedor

Creó un agente de compilación básico. Puede ampliar el archivo Dockerfile para incluir herramientas adicionales y sus dependencias, o bien crear su propio contenedor usando este como capa base. Solo debe asegurarse de que no se toque lo siguiente:

  • El archivo Dockerfile llama al script start.sh.
  • El script start.sh es el último comando del archivo Dockerfile.
  • Asegúrese de que los contenedores derivados no quiten ninguna de las dependencias indicadas por el archivo Dockerfile.

Uso de Docker en un contenedor de Docker

Para usar Docker desde un contenedor de Docker, se debe enlazar el socket de Docker.

Precaución

Esto tiene graves implicaciones de seguridad. El código que hay dentro del contenedor ahora se puede ejecutar como raíz en el host de Docker.

Si sabe seguro que desea hacerlo, consulte la documentación de montaje de enlace en Docker.com.

Uso de un clúster de Azure Kubernetes Service

Precaución

Tenga en cuenta que las tareas basadas en Docker no funcionarán en AKS 1.19 o en versiones anteriores debido a la restricción de Docker. Docker se reemplazó por containerd en Kubernetes 1.19 y Docker-in-Docker dejó de estar disponible.

Implementación y configuración de Azure Kubernetes Service

Siga los pasos descritos en Inicio rápido: implementación de un clúster de Azure Kubernetes Service (AKS) mediante Azure Portal. Después, la consola de PowerShell o Shell puede usar la línea de comandos kubectl.

Implementación y configuración de Azure Container Registry

Siga los pasos de Inicio rápido: Creación de un instancia de Azure Container Registry mediante Azure Portal. Después, puede insertar y extraer contenedores de Azure Container Registry.

Configuración de secretos e implementación de un conjunto de réplicas

  1. Cree los secretos en el clúster de 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. Ejecute este comando para insertar el contenedor en Container Registry:

    docker push "<acr-server>/azp-agent:<tag>"
    
  3. Configure la integración de Container Registry para los clústeres de AKS existentes.

    Nota:

    Si tiene varias suscripciones en Azure Portal, use este comando primero para seleccionar una suscripción.

    az account set --subscription "<subscription id or subscription name>"
    
    az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
    
  4. Guarde el siguiente contenido en ~/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
    

    Este YAML de Kubernetes crea un conjunto de réplicas y una implementación, donde replicas: 1 indica el número o los agentes que se ejecutan en el clúster.

  5. Ejecute este comando:

    kubectl apply -f ReplicationController.yml
    

Ahora los agentes ejecutarán el clúster de AKS.

Establecimiento del parámetro MTU personalizado

Permite especificar el valor de MTU para las redes que usan los trabajos de contenedor (útiles en los escenarios Docker-in-Docker en el clúster k8s).

Debe establecer la variable de entorno AGENT_DOCKER_MTU_VALUE para establecer el valor de MTU y, a continuación, reiniciar el agente autohospedado. Encontrará más información sobre el reinicio del agente aquí y sobre cómo establecer diferentes variables de entorno para cada agente individual aquí.

De este modo, podrá configurar un parámetro de red para el contenedor de trabajos; el uso de este comando es similar al uso del comando siguiente mientras se configura la red del contenedor:

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

Montaje de volúmenes mediante Docker en un contenedor de Docker

Si un contenedor de Docker se ejecuta dentro de otro contenedor de Docker, ambos usan el demonio del host, por lo que todas las rutas de acceso de montaje hacen referencia al host, y no al contenedor.

Por ejemplo, si queremos montar la ruta de acceso del host en un contenedor externo de Docker, podemos usar este comando:

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

Y si queremos montar la ruta de acceso del host en un contenedor interno de Docker, podemos usar este comando:

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

Pero no podemos montar rutas de acceso desde el contenedor exterior en el interno; para solucionarlo, tenemos que declarar una variable ENV:

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

Después, podemos iniciar el contenedor interno desde el exterior usando este comando:

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

Errores comunes

Si usa Windows y recibe el siguiente error:

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

Instale Git Bash descargando e instalando git-scm.

Ejecute este comando:

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

Inténtelo de nuevo. Ya no recibirá el error.