【把玩Azure DevOps】Day21 建立自管的Azure DevOps Agent(Windows Container agent)

前一篇提到了在Windows VM中安装Azure DevOps Agent,步骤非常的简单,不过Azure DevOps Agent也可以在Container内执行,这一篇就来看看如何在Windows Container内执行Azure DevOps Agent。(如果需要Windows Server安装Docker的方式请看这篇文章)

首先,在Windows Container的部份有些基本的知识需要了解,那就是Windows Container区分不同的基底映像类型(Base Image),分为Windows、Windows Server、Windows Server Core、Nano Server四种,越前面的映像档大小所占的容量越大,包含的功能越多,越後面的则越小,所包含的功能也越少。(参考官方文件:中文/英文)

另外就是Windows Container的隔离模式(Isolation Modes),分为Process和Hyper-V隔离这两种,在Windows Server上执行的Windows Container预设以Process隔离方式执行,在Windows 10执行的Windows Container则预设以Hyper-V隔离方式执行。(参考官方文件:中文/英文)

在上面这两个不同的差异之下,选择建立Windows Container Image就会有很多的选择,这牵涉到执行Container的主机(Host)所使用的是哪一个版本的OS,不同版本之间的相容性会有所差异,例如:Windows Server 2019与Windows Server 20H2这两个版本的OS相容性:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961ozHK8ymtRu.png

https://ithelp.ithome.com.tw/upload/images/20211003/20033961KytbzdyDvq.png

从上面两张图可以看到Windows Server 2019的Host可以支援以Windows Server 2019为基底的Container Image以Process隔离模式执行,若是以Hyper-V隔离模式执行,则是可以支援Windows Server 2019以下的版本。同样的,Windows Server 20H2的Host可以支援同为Windows Server 20H2的Image以Process隔离模式执行,Hyper-V的隔离模式则是与Host相同版本以下的都能够支援。

换成是Windows 10作为Host的话,支援性又有不同的差异:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961opcURsFkAn.png

https://ithelp.ithome.com.tw/upload/images/20211003/20033961Last1pT8dr.png

https://ithelp.ithome.com.tw/upload/images/20211003/20033961DAqORdzGKp.png

Windows 10的部份可以从上面三张图看到一点点差异,就是Windows 10的1909版本并不能支援Windows Server任何版本为基底映像档以Process隔离模式执行,Windows 10的2004版本之後才开始支援与Windows Server相对应的版本映像档以Process隔离模式执行,但是以Hyper-V隔离模式执行的话则是支援相同版本以下的OS映像档。

有点复杂,对吧?

简单来说,要能够支援以Process隔离模式执行,必须是Container Base Image和Host为相同版本的OS,若是以Hyper-V隔离模式执行,则必须挑选Host OS版本以下制成的Container Image,也就是说Windows Server 2016的选择性非常少,只能使用同为Windows Server 2016为基底制作的映像档,越新的OS版本则支援以Hyper-V隔离模式执行的Container Image选择越多。

更多Windows Container版本相容性的部份可以参考官方文件的说明(中文/英文)。

回到Azure DevOps Agent,上面说了那麽多,主要是必须考量Agent要执行的任务需要多大的支援与资源,若是只需要能够以dotnet sdk建置专案,那麽选择任何一个OS版本的Image应该都可以,只要能够在Image里面安装dotnet sdk即可。

以dotnet sdk这个例子来说,在官方文件「在 Docker 中执行自我装载的代理程序」所提到的Dockerfile范例是使用Windows Server Core 2019的基底映像档,里面并没有安装dotnet sdk:

FROM mcr.microsoft.com/windows/servercore:ltsc2019

WORKDIR /azp

COPY start.ps1 .

CMD powershell .\start.ps1

所以要让Agent可以执行dotnet build的任务,可以选择使用上面的范例,在Dockerfile中再另外加入安装dotnet sdk的内容,或是从Docker hub上另外找微软官方制作的dotnet sdk映像档,找到对应的Container Repository与Image tag,例如:「mcr.microsoft.com/dotnet/sdk:5.0-windowsservercore-ltsc2019」这个就是同样以Windows Server Core 2019为基底的Image,并且安装了dotnet 5.0 sdk。

从官方文件「在 Docker 中执行自我装载的代理程序」 中的范例可以看得出来,在Container中执行Azure DevOps Agent的关键在於下面这段PowerShell内容,将它复制之後以start.ps1储存在与Dockerfile相同的位置即可:

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 { ${Env:computername} })" `
    --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})"
}

接着就是准备我们要用的Dockerfile,这边我以dotnet 5.0 sdk的Windows Server Core 2022映像档为基底,它里面包含了PowerShell与dotnet 5.0 sdk,并且我还要额外在Image里面安装Docker。若是需要dotnet core 3.1与其它OS基底映像档的部份可以到Docker hub的页面上去寻找相关的Tag:

FROM mcr.microsoft.com/dotnet/sdk:5.0-windowsservercore-ltsc2022

#在这底下加入要额外装在Docker Image内的程序

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

RUN Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
RUN Install-Module -Name DockerMsftProvider -Repository PSGallery -Force -verbose
RUN Install-Package -Name docker -ProviderName DockerMsftProvider -Force -verbose -ErrorAction SilentlyContinue; exit 0

#===========================================

WORKDIR /azp

COPY start.ps1 .

CMD powershell .\start.ps1

准备好Dockerfile和start.ps1这两个档案之後,执行下面的指令建立映像档:

docker build -t devopsagent .

完成Build Image的动作之後,透过下面的指令建立一个名为azure-agent的Container:

docker run --name azure-agent -d -v \.\pipe\docker_engine:\.\pipe\docker_engine -e AZP_URL=[AzureDevOpsURL] -e AZP_TOKEN=[AzureDevOpsPAT] -e AZP_AGENT_NAME=[AzureDevOpsAgentName] IMAGE:TAG

指令中比较特别的地方是透过mount的方式将host的docker转给container内使用,也就是「-v .\pipe\docker_engine:.\pipe\docker_engine」这段,其它的除了IMAGE:TAG是根据上面的docker build所指定的Repository与Tag(预设为latest)之外,环境变数的部份则参考下面这张表:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961o3uwBoPP9Z.png

五个环境变数中的最後两个AZP_POOL、AZP_WORK是选择性的,只有前三个是必须的,执行之後的结果如下:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961Idk9PZaioj.png

从Azure DevOps中的Agent Pools列表中可以看到这个Agent:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961nYQGnsBQSU.png

进入查看Capabilities的部份也可以看到几个比较不一样的资讯,像是ComputerName是Docker产生的随机名称,还有docker的资讯:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961N0DB1T2CBI.png

至於为什麽需要将host的docker提供给Container中的docker使用,这部份则是为了让Container中的docker可以共用host上的docker,从Container中的Agent执行docker build所产生的docker image也会储存在host的docker images里面,并且可以代替host执行docker run建立新的Container。不过这麽做也是有安全性的风险的,官方就有提出警告,是否要这麽做则是可以自行思考决定:

https://ithelp.ithome.com.tw/upload/images/20211003/20033961ZEdH4mBLLj.png

会在这篇文章中这麽做的原因,纯粹是为了提供Windows环境将docker转给Container内使用的指令使用方式,因为这部份在微软的文件页面上似乎没有提到(我忘了之前从哪里看到的)。


<<:  每日挑战,从Javascript面试题目了解一些你可能忽略的概念 - Day18

>>:  Day20_控制项(A15供应者关系)

资料存取的先後顺序:Stack 和 Queue

题组回顾与观念统整 Stack 和 Queue 绝对是资料结构中不可以错过的一种容器,不只用於资料...

2.2 Design System - 设计语言 (9/24 updated)

对於「台大、中兴事件」的看法 身为毕业多年,曾在世俗定义的中庸学校跟好学校都待过的我,看到这事件只...

硬体安全模组 (HSM) 的身份验证最不相关-职责分离(SOD)

如今,“秘密”(secret)是认证的基础。我们通常使用密码(您知道的东西)、令牌中的加密密钥(您拥...

[Day 5] 站在巨人的肩膀上 - 回顾股票市场交易论文

一、前言 矮子能看得更远,只因为他站在巨人的肩膀上。 - Isaac Newton 今天的文章,我将...

Day-20 使用 @apply 制做组件

昨天威尔猪示范了按钮的制作,很多小伙伴应该看完就崩溃了,样式设计很弹性没错,但写一个小小的按钮 +...