在 Docker 中运行自托管代理

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

本文提供有关在 Docker 中运行 Azure Pipelines 代理的说明。 可以在 Azure Pipelines 中设置一个自承载代理,以便在 Windows 主机) 的 Windows Server Core (中运行,或使用 Docker) 适用于 Linux 主机的 Ubuntu 容器 (。 如果要通过外部业务流程(例如Azure 容器实例)运行代理,这非常有用。 在本文中,你将演练一个完整的容器示例,包括处理代理自我更新。

WindowsLinux 都支持作为容器主机。 Windows 容器应在 Windows vmImage上运行。 若要在 Docker 中运行代理,请将几个环境变量传递给 docker run,后者将代理配置为连接到 Azure Pipelines 或Azure DevOps Server。 最后, 自定义容器 以满足你的需求。 任务和脚本可能取决于容器 PATH上可用的特定工具,你有责任确保这些工具可用。

此功能需要代理版本 2.149 或更高版本。 Azure DevOps 2019 未随附兼容的代理版本。 但是,如果要运行 Docker 代理,可以将 正确的代理包上传到应用层

Windows

启用 Hyper-V

Windows 上默认不启用 Hyper-V。 如果要在容器之间提供隔离,则必须启用 Hyper-V。 否则,适用于 Windows 的 Docker 将不会启动。

注意

必须在计算机上启用虚拟化。 它通常默认启用。 但是,如果 Hyper-V 安装失败,请参阅系统文档,了解如何启用虚拟化。

安装 Docker for Windows

如果使用 Windows 10,可以安装 Docker Community Edition。 对于Windows Server 2016,请安装 Docker Enterprise Edition

切换 Docker 以使用 Windows 容器

默认情况下,适用于 Windows 的 Docker 配置为使用 Linux 容器。 若要允许运行 Windows 容器,请确认 Docker for Windows 正在运行 Windows 守护程序

创建和生成 Dockerfile

接下来,创建 Dockerfile。

  1. 打开命令提示符。

  2. 创建新目录:

    mkdir C:\dockeragent
    
  3. 将目录更改为此新目录:

    cd C:\dockeragent
    
  4. 将以下内容保存到名为 C:\dockeragent\Dockerfile (无文件扩展名) 的文件:

    FROM mcr.microsoft.com/windows/servercore:ltsc2019
    
    WORKDIR /azp
    
    COPY start.ps1 .
    
    CMD powershell .\start.ps1
    
  5. 将以下内容保存到 C:\dockeragent\start.ps1

    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
    
    Write-Host "1. Determining matching Azure Pipelines agent..." -ForegroundColor Cyan
    
    $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
    
    Write-Host "2. Downloading and installing Azure Pipelines agent..." -ForegroundColor Cyan
    
    $wc = New-Object System.Net.WebClient
    $wc.DownloadFile($packageUrl, "$(Get-Location)\agent.zip")
    
    Expand-Archive -Path "agent.zip" -DestinationPath "\azp\agent"
    
    try
    {
      Write-Host "3. Configuring Azure Pipelines agent..." -ForegroundColor Cyan
    
      .\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
    
      Write-Host "4. Running Azure Pipelines agent..." -ForegroundColor Cyan
    
      .\run.cmd
    }
    finally
    {
      Write-Host "Cleanup. Removing Azure Pipelines agent..." -ForegroundColor Cyan
    
      .\config.cmd remove --unattended `
        --auth PAT `
        --token "$(Get-Content ${Env:AZP_TOKEN_FILE})"
    }
    
  6. 在该目录中运行以下命令:

    docker build -t dockeragent:latest .
    

    此命令在当前目录中生成 Dockerfile。

    最终图像标记为 dockeragent:latest。 可以轻松地将其作为 dockeragent在容器中运行,因为如果未指定标记, latest 则标记是默认标记。

启动映像

创建映像后,可以运行容器。

  1. 打开命令提示符。

  2. 运行容器。 这会安装最新版本的代理,对其进行配置并运行代理。 它面向Default所选的指定 Azure DevOps 或Azure DevOps Server实例的池:

    docker run -e AZP_URL=<Azure DevOps instance> -e AZP_TOKEN=<PAT token> -e AZP_AGENT_NAME=mydockeragent dockeragent:latest
    

(可选)可以使用其他 环境变量来控制池和代理工作目录。

如果需要每次管道运行都有一个新的代理容器,请将 --once 标志 传递给 run 命令。 还必须使用容器业务流程系统(如 Kubernetes 或 Azure 容器实例)在工作完成时启动容器的新副本。

Linux

安装 Docker

根据 Linux 分发版,可以安装 Docker Community EditionDocker Enterprise Edition

创建和生成 Dockerfile

接下来,创建 Dockerfile。

  1. 打开终端。

  2. 创建新目录(推荐):

    mkdir ~/dockeragent
    
  3. 将目录更改为此新目录:

    cd ~/dockeragent
    
  4. 将以下内容保存到 ~/dockeragent/Dockerfile

    • 对于 Ubuntu 20.04:
      FROM ubuntu:20.04
      RUN DEBIAN_FRONTEND=noninteractive apt-get update
      RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
      
      RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
          apt-transport-https \
          apt-utils \
          ca-certificates \
          curl \
          git \
          iputils-ping \
          jq \
          lsb-release \
          software-properties-common
      
      RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
      
      # Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'.
      ENV TARGETARCH=linux-x64
      
      WORKDIR /azp
      
      COPY ./start.sh .
      RUN chmod +x start.sh
      
      ENTRYPOINT [ "./start.sh" ]
      
    • 对于 Ubuntu 18.04:
      FROM ubuntu:18.04
      
      # To make it easier for build and release pipelines to run apt-get,
      # configure apt to not require confirmation (assume the -y argument by default)
      ENV DEBIAN_FRONTEND=noninteractive
      RUN echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes
      
      RUN apt-get update && apt-get install -y --no-install-recommends \
          ca-certificates \
          curl \
          jq \
          git \
          iputils-ping \
          libcurl4 \
          libicu60 \
          libunwind8 \
          netcat \
          libssl1.0 \
        && rm -rf /var/lib/apt/lists/*
      
      RUN curl -LsS https://aka.ms/InstallAzureCLIDeb | bash \
        && rm -rf /var/lib/apt/lists/*
      
      # Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'.
      ENV TARGETARCH=linux-x64
      
      WORKDIR /azp
      
      COPY ./start.sh .
      RUN chmod +x start.sh
      
      ENTRYPOINT ["./start.sh"]
      

    注意

    任务可能依赖于容器应提供的可执行文件。 例如,必须将 和 unzip 包添加到 zipRUN apt-get 命令才能运行 ArchiveFilesExtractFiles 任务。 此外,由于这是供代理使用的 Linux Ubuntu 映像,因此可以根据需要自定义该映像。 例如:如果需要生成 .NET 应用程序,可以按照文档 在 Ubuntu 上安装 .NET SDK 或 .NET 运行时 并将其添加到映像。

  5. 将以下内容保存到 ~/dockeragent/start.sh,确保使用 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
    
    export AGENT_ALLOW_RUNASROOT="1"
    
    cleanup() {
      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 "${lightcyan}$1${nocolor}"
    }
    
    # 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..."
    
    trap 'cleanup; exit 0' EXIT
    trap 'cleanup; exit 130' INT
    trap 'cleanup; exit 143' TERM
    
    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 $!
    

    注意

    还必须使用容器业务流程系统(如 Kubernetes 或 Azure 容器实例)在工作完成时启动容器的新副本。

  6. 在该目录中运行以下命令:

    docker build -t dockeragent:latest .
    

    此命令在当前目录中生成 Dockerfile。

    最终图像标记为 dockeragent:latest。 可以轻松地将其作为 dockeragent在容器中运行,因为如果未指定标记, latest 则标记是默认标记。

启动映像

创建映像后,可以运行容器。

  1. 打开终端。

  2. 运行容器。 这会安装最新版本的代理,对其进行配置并运行代理。 它面向Default所选的指定 Azure DevOps 或Azure DevOps Server实例的池:

    docker run -e AZP_URL=<Azure DevOps instance> -e AZP_TOKEN=<PAT token> -e AZP_AGENT_NAME=mydockeragent dockeragent:latest
    

    如果需要为每个管道作业使用一个新的代理容器,请将 --once 标志 传递给 run 命令。

    docker run -e AZP_URL=<Azure DevOps instance> -e AZP_TOKEN=<PAT token> -e AZP_AGENT_NAME=mydockeragent dockeragent:latest --once
    

(可选)可以使用其他 环境变量来控制池和代理工作目录。

环境变量

环境变量 说明
AZP_URL Azure DevOps 或 Azure DevOps Server 实例的 URL。
AZP_TOKEN 具有代理池的个人访问令牌 (PAT) (读取、管理由有权配置代理的用户创建的) 范围。AZP_URL
AZP_AGENT_NAME 代理名称 (默认值:容器主机名) 。
AZP_POOL 代理池名称 (默认值: Default) 。
AZP_WORK 工作目录 (默认值: _work) 。

添加工具和自定义容器

已创建基本生成代理。 可以扩展 Dockerfile 以包含其他工具及其依赖项,或者将此容器用作基础层来生成自己的容器。 只需确保以下各项保持不变:

  • 脚本 start.sh 由 Dockerfile 调用。
  • 脚本 start.sh 是 Dockerfile 中的最后一个命令。
  • 确保派生容器不会删除 Dockerfile 声明的任何依赖项。

在 Docker 容器中使用 Docker

若要从 Docker 容器中使用 Docker,需要绑定装载 Docker 套接字。

注意

这样做会产生严重的安全隐患。 容器中的代码现在可以在 Docker 主机上以 root 身份运行。

如果确定要执行此操作,请参阅 Docker.com 上的 绑定装载 文档。

使用 Azure Kubernetes 服务 群集

注意

请注意,由于 docker 中的 docker 限制,任何基于 docker 的任务将无法在 AKS 1.19 或更早版本上运行。 Docker 在 Kubernetes 1.19 中替换为容器化,Docker-in-Docker 变得不可用。

部署和配置Azure Kubernetes 服务

按照快速入门:使用 Azure 门户部署 Azure Kubernetes 服务 (AKS) 群集中的步骤操作。 在此之后,PowerShell 或 Shell 控制台可以使用 kubectl 命令行。

部署和配置Azure 容器注册表

按照快速入门:使用 Azure 门户创建 Azure 容器注册表中的步骤操作。 之后,可以从Azure 容器注册表推送和拉取容器。

配置机密并部署副本集

  1. 在 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. 运行以下命令,将容器推送到容器注册表:

    docker push <acr-server>/dockeragent:latest
    
  3. 为现有 AKS 群集配置容器注册表集成。

注意

如果在 Azure 门户中有多个订阅,请先使用此命令选择一个订阅

az account set --subscription <subscription id or >subscription name>
az aks update -n myAKSCluster -g myResourceGroup --attach-acr <acr-name>
  1. 将以下内容保存到 ~/AKS/ReplicationController.yaml

    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>/dockeragent:latest
            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
    

    此 Kubernetes YAML 创建一个副本集和一个部署,其中 replicas: 1 指示群集上运行的代理数或数量。

  2. 运行以下命令:

    kubectl apply -f ReplicationController.yaml
    

现在,代理将运行 AKS 群集。

设置自定义 MTU 参数

允许为容器作业使用的网络指定 MTU 值 (适用于 k8s 群集) 中的 docker-in-docker 方案。

重启自承载代理后,需要设置环境变量AGENT_MTU_VALUE来设置 MTU 值。 可 在此处 找到有关代理重启以及为每个代理设置不同环境变量 的详细信息

这允许你为作业容器设置网络参数,此命令的使用类似于在容器网络配置时使用下一个命令:

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

在 Docker 容器中使用 Docker 装载卷

如果 Docker 容器在另一个 Docker 容器中运行,则它们都使用主机的守护程序,因此所有装载路径都引用主机,而不是容器。

例如,如果要将路径从主机装载到外部 Docker 容器,则可以使用以下命令:

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

如果要将路径从主机装载到内部 Docker 容器,可以使用以下命令:

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

但是,我们无法将路径从外部容器装载到内部容器中;若要解决此问题,必须声明一个 ENV 变量:

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

之后,可以使用以下命令从外部容器启动内部容器:

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

常见错误

如果你使用的是 Windows,并且收到以下错误:

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

通过下载并安装 git-scm 来安装 Git Bash。

运行以下命令:

dos2unix ~/dockeragent/Dockerfile
dos2unix ~/dockeragent/start.sh
git add .
git commit -m 'Fixed CR'
git push

重试。 不再收到错误。