基于Docker托管Azure DevOps代理

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Docker托管Azure DevOps代理相关的知识,希望对你有一定的参考价值。

Azure DevOps非常好用,但是为代理准备单独的服务器经常会显得性价比不高:配置低了,前端构建时会教会你做人,配置太高又有点浪费资源,代理数量少了各团队构建要打架。对于既想享受DevOps的美妙之处但是资源捉襟见肘的小公司,真是一件比较头痛的事情。那么有没有更具备性价比的方案呢?那自然是有的,那就是基于Docker进行自托管。

这里不建议大家丢k8s集群里面去,一是会抢资源,二是k8s容器运行时不推荐大家使用docker,三是不安全。那么现在我们就可以准备一台好点的服务器,来基于Docker来托管自有的Azure DevOps代理。

编写Dockerfile

废话不多,建议大家直接抄代码吧:

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 \\
    gss-ntlmssp

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 .
COPY ./vsts-agent-linux-x64-2.181.2.tar.gz .
RUN chmod +x start.sh

ENTRYPOINT [ "./start.sh" ]

如果对Dockerfile不理解,可以参考笔者之前的教程、博客和书籍《Docker+Kubernetes应用开发与快速上云》来解决问题以及查看日志。这里不建议大家使用官方教程,基本上走不通还问题颇多。

值得注意的是,上面涉及到了两个文件:

  1. start.sh

  2. vsts-agent-linux-x64-2.181.2.tar.gz(从Azure DevOps管理界面下载)

start.sh脚本内容参考如下:

#!/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 negotiate --userName build --password $(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..."

print_header "url=$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1"

# 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_PACKAGES=$(curl -LsS \\
    --ntlm \\
    -u build:$(cat "$AZP_TOKEN_FILE") \\
    -H 'Accept:application/json;' \\
    "$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1")

    print_header "$AZP_AGENT_PACKAGES"

AZP_AGENT_PACKAGE_LATEST_URL=$(echo "$AZP_AGENT_PACKAGES" | jq -r '.value[0].downloadUrl')

    # print_header "1.2 AZP_AGENT_PACKAGE_LATEST_URL=$AZP_AGENT_PACKAGE_LATEST_URL..."

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. extracting Azure Pipelines agent..."

tar zxf ./vsts-agent-linux-x64-2.181.2.tar.gz & wait $!
#curl -LsS $AZP_AGENT_PACKAGE_LATEST_URL | tar -xz & wait $!

source ./env.sh

print_header "3. Configuring Azure Pipelines agent..."

./config.sh --unattended \\
  --agent "$AZP_AGENT_NAME:-$(hostname)" \\
  --url "$AZP_URL" \\
  --auth negotiate \\
  --userName build \\
  --password $(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 $!

当然大家也可以直接使用我已经做好了的镜像:ccr.ccs.tencentyun.com/xinlai/tfsagnet:latest

这样,代码都不用抄了,多省事。相关环境变量如下所示:

环境变量说明
AZP_URLAzure DevOps 或Azure DevOps Server实例的 URL。
AZP_TOKEN密码。PAT认证有问题,被我改成了negotiate认证。
AZP_AGENT_NAME代理名称 (默认值:容器主机名) 。
AZP_POOL代理池名称 (默认值: Default) 。
AZP_WORK工作目录 (默认值: _work) 。

在Docker中运行

参考脚本:

docker run -e AZP_URL=<Azure DevOps instance> -e AZP_TOKEN=<密码> -e AZP_AGENT_NAME=mydockeragent ccr.ccs.tencentyun.com/xinlai/tfsagnet:latest

但是现在的构建离不开Docker,那么Docker in Docker我们肯定是需要的,修改后参考脚本如下所示:

docker run --name docker-ag \\
-e AZP_AGENT_NAME=docker-tx-ag \\
-e AZP_URL=\\
-e AZP_TOKEN=<密码> \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-v /usr/bin/docker:/usr/bin/docker \\
--user root \\
--restart=always \\
ccr.ccs.tencentyun.com/xinlai/tfsagnet:latest

使用portainer来管理Docker代理

在一台服务器上跑了几个实例,为了便于管理,我们可以运行一个portainer实例来进行管理,参考脚本如下:

docker run -d -p 80:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --name prtainer portainer/portainer

然后我们就可以登录其Web管理界面来进行监控和管理:

portainer管理UI

我们可以在一台服务器多跑几个:

Docker代理

运行了之后,我们可以在Azure DevOps的默认代理池看到他们:

定时清理

基于容器构建很方便,但是费空间,尤其是高频次构建,多运行时构建时,各种悬浮镜像的存在,我们可以加个定时构建任务来进行清理,参考脚本如下:

docker system prune -a -f

这样,50G的系统盘一拖3个代理,也是够了。

最后

至此,整个教程到此就结束了。服务器推荐配置如下所示:

  • 磁盘大小:30~50G,如代码文件过大可以适当增加空间以及增加自动清理的频率

  • 内存:16~32G,如果只构建后端代码,8G也是够了,但是前端构建是个黑洞,很多代码构建时没个16G内存,会直接原地崩溃

  • CPU:4~8核

以上是关于基于Docker托管Azure DevOps代理的主要内容,如果未能解决你的问题,请参考以下文章

托管代理失败的 Azure DevOps 构建管道

Azure DevOps 构建管道自托管代理“设备上没有剩余空间”

在 Service Fabric 中运行 Azure DevOps 自托管生成代理时出现“无法解析远程名称”

多目标 .NET Core 控制台的 Azure DevOps (VSTS) 托管 macOS 代理失败

在 Docker 中运行的自定义 Azure DevOps 构建代理上具有卷装载的测试容器

Azure DevOps 管道“正在等待来自代理的控制台输出......”