在 Docker 中运行自托管代理
Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019
本文介绍如何在 Docker 中运行 Azure Pipelines 代理。 可以将 Azure Pipelines 中的自托管代理设置为通过 Docker 运行于 Windows Server Core(适用于 Windows 主机)或 Ubuntu 容器(适用于 Linux 主机)中。 如果要运行具有外部业务流程的代理(例如 Azure 容器实例),此功能非常有用。 在本文中,你将演练完整的容器示例,包括处理代理自我更新。
支持将 Windows 和 Linux 作为容器主机。 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 容器,请确认用于 Windows 的 Docker 正在运行 Windows 守护程序。
创建和生成 Dockerfile
接下来,创建 Dockerfile。
打开命令提示符。
新建目录:
mkdir "C:\azp-agent-in-docker\"
转到此新目录:
cd "C:\azp-agent-in-docker\"
将以下内容保存到名为
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
将以下内容保存到
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})" }
从该目录处运行以下命令:
docker build --tag "azp-agent:windows" --file "./azp-agent-windows.dockerfile" .
最终映像被标记为
azp-agent:windows
。
启动映像
现在已经创建了一个映像,因此可以运行容器了。 这会安装代理的最新版本、对其进行配置并运行代理。 它以指定的 Azure DevOps 或你选择的 Azure DevOps Server 实例的指定代理池(默认情况下为 Default
代理池)为目标:
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
如果遇到网络问题,可能需要指定 --network
参数。
docker run --network "Default Switch" < . . . >
如果想要使用 Ctrl
+ C
停止容器并删除代理,则可能需要指定 --interactive
和 --tty
标志(或只指定 -it
)。
docker run --interactive --tty < . . . >
如果希望为每个管道作业使用一个新的代理容器,请将 --once
标志传递给 run
命令。
docker run < . . . > --once
使用 --once
标志,可能需要使用容器编排系统(如 Kubernetes 或 Azure 容器实例)在工作完成时启动容器的新副本。
可以使用可选环境变量来控制代理名称,代理池和代理工作目录。
Linux
安装 Docker
根据 Linux 分发版,可以安装 Docker Community Edition 或 Docker Enterprise Edition。
创建和生成 Dockerfile
接下来,创建 Dockerfile。
打开终端。
创建新目录(推荐):
mkdir ~/azp-agent-in-docker/
转到此新目录:
cd ~/azp-agent-in-docker/
将以下内容保存到
~/azp-agent-in-docker/azp-agent-linux.dockerfile
:对于 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" ]
对于 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" ]
如果要作为根运行代理,请取消注释
ENV AGENT_ALLOW_RUNASROOT="true"
该行,并删除在这一行之前添加的agent
用户。注意
任务可能依赖于容器应提供的可执行文件。 例如,必须将
zip
和unzip
包添加到RUN apt install -y
命令才能运行ArchiveFiles
和ExtractFiles
任务。 此外,由于这是代理要使用的 Linux Ubuntu 映像,因此可以根据需要自定义映像。 例如:如果需要生成 .NET 应用程序,可以按照在 Ubuntu 上安装 .NET SDK 或 .NET 运行时文档进行操作并将 .NET 应用程序添加到映像中。将以下内容保存到
~/azp-agent-in-docker/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 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 $!
注意
还必须使用容器编排系统(如 Kubernetes 或 Azure 容器实例)在工作完成时启动容器的新副本。
从该目录处运行以下命令:
docker build --tag "azp-agent:linux" --file "./azp-agent-linux.dockerfile" .
最终映像被标记为
azp-agent:linux
。
启动映像
现在已经创建了一个映像,因此可以运行容器了。 这会安装代理的最新版本、对其进行配置并运行代理。 它以指定的 Azure DevOps 或你选择的 Azure DevOps Server 实例的指定代理池(默认情况下为 Default
代理池)为目标:
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
如果想要使用 Ctrl
+ C
停止容器并删除代理,则可能需要指定 --interactive
和 --tty
标志(或只指定 -it
)。
docker run --interactive --tty < . . . >
如果希望为每个管道作业使用一个新的代理容器,请将 --once
标志传递给 run
命令。
docker run < . . . > --once
使用 --once
标志,可能需要使用容器编排系统(如 Kubernetes 或 Azure 容器实例)在工作完成时启动容器的新副本。
可以使用可选环境变量来控制代理名称,代理池和代理工作目录。
环境变量
环境变量 | 说明 |
---|---|
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 上的 bind mount 文档。
使用 Azure Kubernetes 服务群集
注意
请注意,由于 Docker-in-Docker 的限制,任何基于 Docker 的任务将无法在 AKS 1.19 或更高版本上运行。 在 Kubernetes 1.19 中,Docker 被替换为容器,Docker-in-Docker 变得不可用。
部署和配置 Azure Kubernetes 服务
按照快速入门:使用 Azure 门户部署 Azure Kubernetes 服务 (AKS) 群集中的步骤操作。 在此之后,PowerShell 或 Shell 控制台便可以使用 kubectl
命令行。
部署和配置 Azure 容器注册表
按照快速入门:使用 Azure 门户创建 Azure 容器注册表中的步骤操作。 之后,可以从 Azure 容器注册表推送和拉取容器。
配置机密并部署副本集
在 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
运行以下命令以将容器推送到容器注册表:
docker push "<acr-server>/azp-agent:<tag>"
为现有 AKS 群集配置容器注册表集成。
注意
如果在 Azure 门户中有多个订阅,请先使用此命令选择一个订阅
az account set --subscription "<subscription id or subscription name>"
az aks update -n "<myAKSCluster>" -g "<myResourceGroup>" --attach-acr "<acr-name>"
将以下内容保存到
~/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
此 Kubernetes YAML 将创建副本集和部署,其中
replicas: 1
指示群集上运行的代理数。运行以下命令:
kubectl apply -f ReplicationController.yml
现在,代理将运行 AKS 群集。
设置自定义 MTU 参数
允许为容器作业使用的网络指定 MTU 值(对于 k8s 集群中的 Docker-in-Docker 场景很有用)。
需要通过设置环境变量 AGENT_DOCKER_MTU_VALUE 来设置 MTU 值,然后重启自托管代理。 有关代理重启的详情,请参阅此处,有关为每个代理设置不同环境变量详情,请参阅此处。
这允许你为作业容器设置网络参数,使用此命令类似于在配置容器网络时使用下一个命令:
-o com.docker.network.driver.mtu=AGENT_DOCKER_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 ~/azp-agent-in-docker/Dockerfile
dos2unix ~/azp-agent-in-docker/start.sh
git add .
git commit -m "Fixed CR"
git push
重试。 应该不会再收到错误。
相关文章
- Self-hosted Windows agents(自托管 Windows 代理)
- Self-hosted Linux agents(自托管 Linux 代理)
- Self-hosted macOS agents(自托管 macOS 代理)
- Microsoft 托管代理