Como Executar um agente auto-hospedado no Docker

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

Este artigo fornece instruções para executar o agente do Azure Pipelines no Docker. Você pode configurar um agente auto-hospedado no Azure Pipelines para ser executado dentro de um Windows Server Core (para hosts do Windows) ou contêiner do Ubuntu (para hosts Linux) com o Docker. Isso é útil quando você quer executar agentes com orquestração externa, como Instâncias de Contêiner do Azure. Neste artigo, você verá um exemplo completo de contêiner, incluindo o tratamento de autoatendimento do agente.

O Windows e o Linux têm suporte como hosts de contêiner. Os contêineres do Windows devem ser executados em um Windows vmImage. Para executar seu agente no Docker, você passará algumas variáveis de ambiente para docker run, o que configura o agente para se conectar ao Azure Pipelines ou Azure DevOps Server. Por fim, você personaliza o contêiner para atender às suas necessidades. Tarefas e scripts podem depender de ferramentas específicas estarem disponíveis no PATH do contêiner, e é sua responsabilidade garantir que essas ferramentas estejam disponíveis.

Esse recurso requer a versão 2.149 ou posterior do agente. O Azure DevOps 2019 não foi fornecido com uma versão de agente compatível. No entanto, você pode carregar o pacote de agente correto na camada de aplicativo se quiser executar agentes do Docker.

Windows

Habilitar Hyper-V

O Hyper-V não está habilitado por padrão no Windows. Se você quiser fornecer isolamento entre contêineres, deverá habilitar o Hyper-V. Caso contrário, o Docker for Windows não será iniciado.

Observação

Você deve habilitar a virtualização em seu computador. Normalmente, ela é habilitada por padrão. No entanto, se a instalação do Hyper-V falhar, consulte a documentação do sistema para saber como habilitar a virtualização.

Instalar o Docker para Windows

Se você estiver usando Windows 10, poderá instalar o Docker Community Edition. Para Windows Server 2016, instale a Edição Enterprise do Docker.

Alternar o Docker para usar contêineres do Windows

Por padrão, o Docker for Windows é configurado para usar contêineres do Linux. Para permitir a execução do contêiner do Windows, confirme se o Docker for Windows está executando o daemon do Windows.

Criar e compilar o Dockerfile

Em seguida, crie o Dockerfile.

  1. Abra um prompt de comando.

  2. Criará um diretório:

    mkdir "C:\azp-agent-in-docker\"
    
  3. Vá para este novo diretório:

    cd "C:\azp-agent-in-docker\"
    
  4. Salve o seguinte conteúdo em um arquivo chamado 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. Salve o seguinte conteúdo em 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. Execute o seguinte comando nesse diretório:

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

    A imagem final está marcada como azp-agent:windows.

Inicia a imagem

Agora que você criou uma imagem, você pode executar um contêiner. Isso instala a versão mais recente do agente, a configura e executa o agente. Ele tem como destino o pool de agentes (o pool de agentes Default por padrão) de uma instância especificada do Azure DevOps ou Azure DevOps Server de sua escolha:

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

Talvez seja necessário especificar o parâmetro --network se você tiver problemas de rede.

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

Talvez seja necessário especificar os sinalizadores --interactive e --tty (ou simplesmente-it) se quiser parar o contêiner e remover o agente com Ctrl + C.

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

Se você quiser um novo contêiner de agente para cada trabalho de pipeline, passe o sinalizador --once para o comando run.

docker run < . . . > --once

Com o sinalizador --once, talvez você deseje usar um sistema de orquestração de contêineres, como Kubernetes ou Instâncias de Contêiner do Azure, para iniciar uma nova cópia do contêiner quando o trabalho for concluído.

Opcionalmente, você pode controlar o nome do agente, o pool de agentes e o diretório de trabalho do agente usando variáveis de ambiente opcionais.

Linux

Instalar o Docker

Dependendo da distribuição do Linux, você pode instalar o Docker Community Edition ou o Docker Edição Enterprise.

Criar e compilar o Dockerfile

Em seguida, crie o Dockerfile.

  1. Abra um terminal.

  2. Crie um novo diretório (recomendado):

    mkdir ~/azp-agent-in-docker/
    
  3. Vá para este novo diretório:

    cd ~/azp-agent-in-docker/
    
  4. Salve o seguinte conteúdo em ~/azp-agent-in-docker/azp-agent-linux.dockerfile:

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

    Remova o comentário da linha ENV AGENT_ALLOW_RUNASROOT="true" e remova a adição do usuário agent antes dessa linha se quiser executar o agente como root.

    Observação

    As tarefas podem depender de executáveis que seu contêiner deve fornecer. Por exemplo, você deve adicionar os pacotes zip e unzip ao comando RUN apt install -y para executar as tarefas ArchiveFiles e ExtractFiles. Além disso, como essa é uma imagem do Ubuntu do Linux para o agente usar, você pode personalizar a imagem conforme necessário. Por exemplo: se você precisar criar aplicativos .NET, poderá seguir o documento Instalar o SDK do .NET ou o Runtime do .NET no Ubuntu e adicioná-lo à sua imagem.

  5. Salve o seguinte conteúdo em ~/azp-agent-in-docker/start.sh, certificando-se de usar terminações de linha no 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 $!
    

    Observação

    Você também deve usar um sistema de orquestração de contêineres, como Kubernetes ou Instâncias de Contêiner do Azure, para iniciar novas cópias do contêiner quando o trabalho for concluído.

  6. Execute o seguinte comando nesse diretório:

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

    A imagem final está marcada como azp-agent:linux.

Inicia a imagem

Agora que você criou uma imagem, você pode executar um contêiner. Isso instala a versão mais recente do agente, a configura e executa o agente. Ele tem como destino o pool de agentes (o pool de agentes Default por padrão) de uma instância especificada do Azure DevOps ou Azure DevOps Server de sua escolha:

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

Talvez seja necessário especificar os sinalizadores --interactive e --tty (ou simplesmente-it) se quiser parar o contêiner e remover o agente com Ctrl + C.

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

Se você quiser um novo contêiner de agente para cada trabalho de pipeline, passe o sinalizador --once para o comando run.

docker run < . . . > --once

Com o sinalizador --once, talvez você deseje usar um sistema de orquestração de contêineres, como Kubernetes ou Instâncias de Contêiner do Azure, para iniciar uma nova cópia do contêiner quando o trabalho for concluído.

Opcionalmente, você pode controlar o nome do agente, o pool de agentes e o diretório de trabalho do agente usando variáveis de ambiente opcionais.

Variáveis de ambiente

Variável de ambiente Descrição
AZP_URL A URL da instância do Azure DevOps ou do Azure DevOps Server.
AZP_TOKEN Pat (Token de Acesso Pessoal) com escopo de Pools de Agentes (leitura, gerenciamento), criado por um usuário que tem permissão para configurar agentes, em AZP_URL.
AZP_AGENT_NAME Nome do agente (valor padrão: o nome do host do contêiner).
AZP_POOL Nome do pool de agentes (valor padrão: Default).
AZP_WORK Diretório de trabalho (valor padrão: _work).

Adicionar ferramentas e personalizar o contêiner

Você criou um agente de build básico. Você pode estender o Dockerfile para incluir ferramentas adicionais e suas dependências ou criar seu próprio contêiner usando este como uma camada base. Apenas certifique-se de que os seguintes itens sejam deixados intocados:

  • O script start.sh é chamado pelo Dockerfile.
  • O script start.sh é o último comando no Dockerfile.
  • Verifique se os contêineres derivados não removem nenhuma das dependências declaradas pelo Dockerfile.

Usar o Docker em um contêiner do Docker

Para usar o Docker de dentro de um contêiner do Docker, você pode montar o soquete do Docker.

Cuidado

Fazer isso tem sérias implicações de segurança. O código dentro do contêiner agora pode ser executado como raiz no host do Docker.

Se você tiver certeza de que deseja fazer isso, confira a documentação de montagem de associação no Docker.com.

Usar o cluster do Serviço de Kubernetes do Azure

Cuidado

Considere que todas as tarefas baseadas no Docker não funcionarão no AKS 1.19 ou posterior devido à restrição de Docker em Docker. O Docker foi substituído por contêiner no Kubernetes 1.19 e o Docker em Docker ficou indisponível.

Implantar e configurar o Serviço de Kubernetes do Azure

Siga as etapas em Início Rápido: implantar um cluster do AKS (Serviço de Kubernetes do Azure) usando o portal do Azure. Depois disso, o console do PowerShell ou do Shell poderá usar a linha de comando kubectl.

Implantar e configurar o Registro de Contêiner do Azure

Siga as etapas no Início Rápido: Criar um registro de contêiner do Azure usando o portal do Azure. Depois disso, você pode efetuar push e pull de contêineres de Registro de Contêiner do Azure.

Configurar segredos e implantar um conjunto de réplicas

  1. Crie os segredos no cluster do 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. Execute esse comando para enviar o contêiner por push para o Registro de Contêiner:

    docker push "<acr-server>/azp-agent:<tag>"
    
  3. Configurar a integração do Registro de Contêiner para clusters do AKS existentes.

    Observação

    Se você tiver várias assinaturas no Portal do Azure, use esse comando primeiro para selecionar uma assinatura

    az account set --subscription "<subscription id or subscription name>"
    
    az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
    
  4. Salve o seguinte conteúdo em ~/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
    

    Esse YAML do Kubernetes cria um conjunto de réplica e uma implantação, em replicas: 1 que indica o número ou os agentes que estão em execução no cluster.

  5. Execute este comando:

    kubectl apply -f ReplicationController.yml
    

Agora, seus agentes executarão o cluster do AKS.

Definir parâmetro MTU personalizado

Permitir a especificação do valor de MTU para redes usadas por trabalhos de contêiner (útil para cenários docker em docker no cluster do k8s).

Você precisa definir a variável de ambiente AGENT_DOCKER_MTU_VALUE para definir o valor de MTU e, em seguida, reiniciar o agente auto-hospedado. Você pode encontrar mais informações sobre a reinicialização do agente aqui e sobre como definir variáveis de ambiente diferentes para cada agente individual aqui.

Isso permite que você configure um parâmetro de rede para o contêiner de trabalho; o uso desse comando é semelhante ao uso do próximo comando enquanto a configuração de rede de contêiner:

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

Montar volumes usando o Docker em um contêiner do Docker

Se um contêiner do Docker for executado dentro de outro contêiner do Docker, ambos usarão o daemon do host, portanto, todos os caminhos de montagem referenciam o host, não o contêiner.

Por exemplo, se quisermos montar o caminho do host para o contêiner externo do Docker, podemos usar este comando:

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

E se quisermos montar o caminho do host para o contêiner interno do Docker, poderemos usar este comando:

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

Porém, não podemos montar caminhos do contêiner externo para o interno; para contornar isso, precisamos declarar uma variável ENV:

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

Depois disso, podemos iniciar o contêiner interno do externo usando este comando:

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

Erros comuns

Se você estiver usando o Windows e receber o seguinte erro:

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

Instale o Git Bash baixando e instalando git-scm.

Execute este comando:

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

Tente novamente. Você não recebe mais o erro.