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 -yRUN 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-ntlmsspRUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash# Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'.
ENV TARGETARCH=linux-x64WORKDIR /azp
COPY ./start.sh .
COPY ./vsts-agent-linux-x64-2.181.2.tar.gz .
RUN chmod +x start.shENTRYPOINT [ "./start.sh" ]
如果对Dockerfile不理解,可以参考笔者之前的教程、博客和书籍《Docker+Kubernetes应用开发与快速上云》来解决问题以及查看日志。这里不建议大家使用官方教程,基本上走不通还问题颇多。
值得注意的是,上面涉及到了两个文件:
start.sh
vsts-agent-linux-x64-2.181.2.tar.gz(从Azure DevOps管理界面下载)
start.sh脚本内容参考如下:
#!/bin/bash
set -eif [ -z "$AZP_URL" ]; thenecho 1>&2 "error: missing AZP_URL environment variable"exit 1
fiif [ -z "$AZP_TOKEN_FILE" ]; thenif [ -z "$AZP_TOKEN" ]; thenecho 1>&2 "error: missing AZP_TOKEN environment variable"exit 1fiAZP_TOKEN_FILE=/azp/.tokenecho -n $AZP_TOKEN > "$AZP_TOKEN_FILE"
fiunset AZP_TOKENif [ -n "$AZP_WORK" ]; thenmkdir -p "$AZP_WORK"
fiexport AGENT_ALLOW_RUNASROOT="1"cleanup() {if [ -e config.sh ]; thenprint_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") && breakecho "Retrying in 30 seconds..."sleep 30donefi
}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_FILEprint_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" ]; thenecho 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
fiprint_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.shprint_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' TERMchmod +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_URL | Azure 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核