parts/windows/windowscsehelper.ps1 (512 lines of code) (raw):
# This script is used to define basic util functions
# It is better to define functions in the scripts under staging/cse/windows.
# Define all exit codes in Windows CSE
# It must match `[A-Z_]+`
$global:WINDOWS_CSE_SUCCESS=0
$global:WINDOWS_CSE_ERROR_UNKNOWN=1 # For unexpected error caught by the catch block in kuberneteswindowssetup.ps1
$global:WINDOWS_CSE_ERROR_DOWNLOAD_FILE_WITH_RETRY=2
$global:WINDOWS_CSE_ERROR_INVOKE_EXECUTABLE=3
$global:WINDOWS_CSE_ERROR_FILE_NOT_EXIST=4
$global:WINDOWS_CSE_ERROR_CHECK_API_SERVER_CONNECTIVITY=5
$global:WINDOWS_CSE_ERROR_PAUSE_IMAGE_NOT_EXIST=6
$global:WINDOWS_CSE_ERROR_GET_SUBNET_PREFIX=7
$global:WINDOWS_CSE_ERROR_GENERATE_TOKEN_FOR_ARM=8
$global:WINDOWS_CSE_ERROR_NETWORK_INTERFACES_NOT_EXIST=9
$global:WINDOWS_CSE_ERROR_NETWORK_ADAPTER_NOT_EXIST=10
$global:WINDOWS_CSE_ERROR_MANAGEMENT_IP_NOT_EXIST=11
$global:WINDOWS_CSE_ERROR_CALICO_SERVICE_ACCOUNT_NOT_EXIST=12
$global:WINDOWS_CSE_ERROR_CONTAINERD_NOT_INSTALLED=13
$global:WINDOWS_CSE_ERROR_CONTAINERD_NOT_RUNNING=14
$global:WINDOWS_CSE_ERROR_OPENSSH_NOT_INSTALLED=15
$global:WINDOWS_CSE_ERROR_OPENSSH_FIREWALL_NOT_CONFIGURED=16
$global:WINDOWS_CSE_ERROR_INVALID_PARAMETER_IN_AZURE_CONFIG=17
$global:WINDOWS_CSE_ERROR_NO_DOCKER_TO_BUILD_PAUSE_CONTAINER=18
$global:WINDOWS_CSE_ERROR_GET_CA_CERTIFICATES=19
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CA_CERTIFICATES=20
$global:WINDOWS_CSE_ERROR_EMPTY_CA_CERTIFICATES=21
$global:WINDOWS_CSE_ERROR_ENABLE_SECURE_TLS=22
$global:WINDOWS_CSE_ERROR_GMSA_EXPAND_ARCHIVE=23
$global:WINDOWS_CSE_ERROR_GMSA_ENABLE_POWERSHELL_PRIVILEGE=24
$global:WINDOWS_CSE_ERROR_GMSA_SET_REGISTRY_PERMISSION=25
$global:WINDOWS_CSE_ERROR_GMSA_SET_REGISTRY_VALUES=26
$global:WINDOWS_CSE_ERROR_GMSA_IMPORT_CCGEVENTS=27
$global:WINDOWS_CSE_ERROR_GMSA_IMPORT_CCGAKVPPLUGINEVENTS=28
$global:WINDOWS_CSE_ERROR_NOT_FOUND_MANAGEMENT_IP=29
$global:WINDOWS_CSE_ERROR_NOT_FOUND_BUILD_NUMBER=30
$global:WINDOWS_CSE_ERROR_NOT_FOUND_PROVISIONING_SCRIPTS=31
$global:WINDOWS_CSE_ERROR_START_NODE_RESET_SCRIPT_TASK=32
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CSE_PACKAGE=33
$global:WINDOWS_CSE_ERROR_DOWNLOAD_KUBERNETES_PACKAGE=34
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE=35
$global:WINDOWS_CSE_ERROR_DOWNLOAD_HNS_MODULE=36
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CALICO_PACKAGE=37
$global:WINDOWS_CSE_ERROR_DOWNLOAD_GMSA_PACKAGE=38
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CSI_PROXY_PACKAGE=39
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CONTAINERD_PACKAGE=40
$global:WINDOWS_CSE_ERROR_SET_TCP_DYNAMIC_PORT_RANGE=41
$global:WINDOWS_CSE_ERROR_BUILD_DOCKER_PAUSE_CONTAINER=42
$global:WINDOWS_CSE_ERROR_PULL_PAUSE_IMAGE=43
$global:WINDOWS_CSE_ERROR_BUILD_TAG_PAUSE_IMAGE=44
$global:WINDOWS_CSE_ERROR_CONTAINERD_BINARY_EXIST=45
$global:WINDOWS_CSE_ERROR_SET_TCP_EXCLUDE_PORT_RANGE=46
$global:WINDOWS_CSE_ERROR_SET_UDP_DYNAMIC_PORT_RANGE=47
$global:WINDOWS_CSE_ERROR_SET_UDP_EXCLUDE_PORT_RANGE=48
$global:WINDOWS_CSE_ERROR_NO_CUSTOM_DATA_BIN=49 # Return this error code in csecmd.ps1 when C:\AzureData\CustomData.bin does not exist
$global:WINDOWS_CSE_ERROR_NO_CSE_RESULT_LOG=50 # Return this error code in csecmd.ps1 when C:\AzureData\CSEResult.log does not exist
$global:WINDOWS_CSE_ERROR_COPY_LOG_COLLECTION_SCRIPTS=51
$global:WINDOWS_CSE_ERROR_RESIZE_OS_DRIVE=52
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_FAILED=53
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_TIMEOUT=54
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_VM_SIZE_NOT_SUPPORTED=55
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_URL_NOT_SET=56
$global:WINDOWS_CSE_ERROR_GPU_SKU_INFO_NOT_FOUND=57
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_DOWNLOAD_FAILURE=58
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INVALID_SIGNATURE=59
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_EXCEPTION=60
$global:WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_URL_NOT_EXE=61
$global:WINDOWS_CSE_ERROR_UPDATING_KUBE_CLUSTER_CONFIG=62
$global:WINDOWS_CSE_ERROR_GET_NODE_IPV6_IP=63
$global:WINDOWS_CSE_ERROR_GET_CONTAINERD_VERSION=64
$global:WINDOWS_CSE_ERROR_INSTALL_CREDENTIAL_PROVIDER = 65 # exit code for installing credential provider
$global:WINDOWS_CSE_ERROR_DOWNLOAD_CREDEDNTIAL_PROVIDER=66 # exit code for downloading credential provider failure
$global:WINDOWS_CSE_ERROR_CREDENTIAL_PROVIDER_CONFIG=67 # exit code for checking credential provider config failure
$global:WINDOWS_CSE_ERROR_ADJUST_PAGEFILE_SIZE=68
$global:WINDOWS_CSE_ERROR_LOOKUP_INSTANCE_DATA_TAG=69 # exit code for looking up nodepool/VM tags via IMDS
# WINDOWS_CSE_ERROR_MAX_CODE is only used in unit tests to verify whether new error code name is added in $global:ErrorCodeNames
# Please use the current value of WINDOWS_CSE_ERROR_MAX_CODE as the value of the new error code and increment it by 1
$global:WINDOWS_CSE_ERROR_MAX_CODE=70
# Please add new error code for downloading new packages in RP code too
$global:ErrorCodeNames = @(
"WINDOWS_CSE_SUCCESS",
"WINDOWS_CSE_ERROR_UNKNOWN",
"WINDOWS_CSE_ERROR_DOWNLOAD_FILE_WITH_RETRY",
"WINDOWS_CSE_ERROR_INVOKE_EXECUTABLE",
"WINDOWS_CSE_ERROR_FILE_NOT_EXIST",
"WINDOWS_CSE_ERROR_CHECK_API_SERVER_CONNECTIVITY",
"WINDOWS_CSE_ERROR_PAUSE_IMAGE_NOT_EXIST",
"WINDOWS_CSE_ERROR_GET_SUBNET_PREFIX",
"WINDOWS_CSE_ERROR_GENERATE_TOKEN_FOR_ARM",
"WINDOWS_CSE_ERROR_NETWORK_INTERFACES_NOT_EXIST",
"WINDOWS_CSE_ERROR_NETWORK_ADAPTER_NOT_EXIST",
"WINDOWS_CSE_ERROR_MANAGEMENT_IP_NOT_EXIST",
"WINDOWS_CSE_ERROR_CALICO_SERVICE_ACCOUNT_NOT_EXIST",
"WINDOWS_CSE_ERROR_CONTAINERD_NOT_INSTALLED",
"WINDOWS_CSE_ERROR_CONTAINERD_NOT_RUNNING",
"WINDOWS_CSE_ERROR_OPENSSH_NOT_INSTALLED",
"WINDOWS_CSE_ERROR_OPENSSH_FIREWALL_NOT_CONFIGURED",
"WINDOWS_CSE_ERROR_INVALID_PARAMETER_IN_AZURE_CONFIG",
"WINDOWS_CSE_ERROR_NO_DOCKER_TO_BUILD_PAUSE_CONTAINER",
"WINDOWS_CSE_ERROR_GET_CA_CERTIFICATES",
"WINDOWS_CSE_ERROR_DOWNLOAD_CA_CERTIFICATES",
"WINDOWS_CSE_ERROR_EMPTY_CA_CERTIFICATES",
"WINDOWS_CSE_ERROR_ENABLE_SECURE_TLS",
"WINDOWS_CSE_ERROR_GMSA_EXPAND_ARCHIVE",
"WINDOWS_CSE_ERROR_GMSA_ENABLE_POWERSHELL_PRIVILEGE",
"WINDOWS_CSE_ERROR_GMSA_SET_REGISTRY_PERMISSION",
"WINDOWS_CSE_ERROR_GMSA_SET_REGISTRY_VALUES",
"WINDOWS_CSE_ERROR_GMSA_IMPORT_CCGEVENTS",
"WINDOWS_CSE_ERROR_GMSA_IMPORT_CCGAKVPPLUGINEVENTS",
"WINDOWS_CSE_ERROR_NOT_FOUND_MANAGEMENT_IP",
"WINDOWS_CSE_ERROR_NOT_FOUND_BUILD_NUMBER",
"WINDOWS_CSE_ERROR_NOT_FOUND_PROVISIONING_SCRIPTS",
"WINDOWS_CSE_ERROR_START_NODE_RESET_SCRIPT_TASK",
"WINDOWS_CSE_ERROR_DOWNLOAD_CSE_PACKAGE",
"WINDOWS_CSE_ERROR_DOWNLOAD_KUBERNETES_PACKAGE",
"WINDOWS_CSE_ERROR_DOWNLOAD_CNI_PACKAGE",
"WINDOWS_CSE_ERROR_DOWNLOAD_HNS_MODULE",
"WINDOWS_CSE_ERROR_DOWNLOAD_CALICO_PACKAGE",
"WINDOWS_CSE_ERROR_DOWNLOAD_GMSA_PACKAGE",
"WINDOWS_CSE_ERROR_DOWNLOAD_CSI_PROXY_PACKAGE",
"WINDOWS_CSE_ERROR_DOWNLOAD_CONTAINERD_PACKAGE",
"WINDOWS_CSE_ERROR_SET_TCP_DYNAMIC_PORT_RANGE",
"WINDOWS_CSE_ERROR_BUILD_DOCKER_PAUSE_CONTAINER",
"WINDOWS_CSE_ERROR_PULL_PAUSE_IMAGE",
"WINDOWS_CSE_ERROR_BUILD_TAG_PAUSE_IMAGE",
"WINDOWS_CSE_ERROR_CONTAINERD_BINARY_EXIST",
"WINDOWS_CSE_ERROR_SET_TCP_EXCLUDE_PORT_RANGE",
"WINDOWS_CSE_ERROR_SET_UDP_DYNAMIC_PORT_RANGE",
"WINDOWS_CSE_ERROR_SET_UDP_EXCLUDE_PORT_RANGE",
"WINDOWS_CSE_ERROR_NO_CUSTOM_DATA_BIN",
"WINDOWS_CSE_ERROR_NO_CSE_RESULT_LOG",
"WINDOWS_CSE_ERROR_COPY_LOG_COLLECTION_SCRIPTS",
"WINDOWS_CSE_ERROR_RESIZE_OS_DRIVE",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_FAILED",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_TIMEOUT",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_VM_SIZE_NOT_SUPPORTED",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_URL_NOT_SET",
"WINDOWS_CSE_ERROR_GPU_SKU_INFO_NOT_FOUND",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_DOWNLOAD_FAILURE",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INVALID_SIGNATURE",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_EXCEPTION",
"WINDOWS_CSE_ERROR_GPU_DRIVER_INSTALLATION_URL_NOT_EXE",
"WINDOWS_CSE_ERROR_UPDATING_KUBE_CLUSTER_CONFIG",
"WINDOWS_CSE_ERROR_GET_NODE_IPV6_IP",
"WINDOWS_CSE_ERROR_GET_CONTAINERD_VERSION",
"WINDOWS_CSE_ERROR_INSTALL_CREDENTIAL_PROVIDER",
"WINDOWS_CSE_ERROR_DOWNLOAD_CREDEDNTIAL_PROVIDER",
"WINDOWS_CSE_ERROR_CREDENTIAL_PROVIDER_CONFIG",
"WINDOWS_CSE_ERROR_ADJUST_PAGEFILE_SIZE",
"WINDOWS_CSE_ERROR_LOOKUP_INSTANCE_DATA_TAG"
)
# The package domain to be used
$global:PackageDownloadFqdn = $null
# The preferred package FQDN
$global:PreferredPackageDownloadFqdn = "packages.aks.azure.com"
# Fallback FQDN if preferred cannot be contacted
$global:FallbackPackageDownloadFqdn = "acs-mirror.azureedge.net"
# NOTE: KubernetesVersion does not contain "v"
$global:MinimalKubernetesVersionWithLatestContainerd = "1.28.0" # Will change it to the correct version when we support new Windows containerd version
# Although the contianerd package url is set in AKS RP code now, we still need to update the following variables for AgentBaker Windows E2E tests.
$global:StableContainerdPackage = "v1.6.35-azure.1/binaries/containerd-v1.6.35-azure.1-windows-amd64.tar.gz"
# The latest containerd version
$global:LatestContainerdPackage = "v1.7.20-azure.1/binaries/containerd-v1.7.20-azure.1-windows-amd64.tar.gz"
$global:EventsLoggingDir = "C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension\Events\"
$global:TaskName = ""
$global:TaskTimeStamp = ""
# This filter removes null characters (\0) which are captured in nssm.exe output when logged through powershell
filter RemoveNulls { $_ -replace '\0', '' }
filter Timestamp { "$(Get-Date -Format o): $_" }
function Write-Log($message) {
$msg = $message | Timestamp
Write-Output $msg
}
function DownloadFileOverHttp {
Param(
[Parameter(Mandatory = $true)][string]
$Url,
[Parameter(Mandatory = $true)][string]
$DestinationPath,
[Parameter(Mandatory = $true)][int]
$ExitCode
)
# First check to see if a file with the same name is already cached on the VHD
$cleanUrl = $Url.Split('?')[0]
$fileName = [IO.Path]::GetFileName($cleanUrl)
$search = @()
if ($global:CacheDir -and (Test-Path $global:CacheDir)) {
$search = [IO.Directory]::GetFiles($global:CacheDir, $fileName, [IO.SearchOption]::AllDirectories)
}
if ($search.Count -ne 0) {
Write-Log "Using cached version of $fileName - Copying file from $($search[0]) to $DestinationPath"
Copy-Item -Path $search[0] -Destination $DestinationPath -Force
}
else {
$secureProtocols = @()
$insecureProtocols = @([System.Net.SecurityProtocolType]::SystemDefault, [System.Net.SecurityProtocolType]::Ssl3)
foreach ($protocol in [System.Enum]::GetValues([System.Net.SecurityProtocolType])) {
if ($insecureProtocols -notcontains $protocol) {
$secureProtocols += $protocol
}
}
[System.Net.ServicePointManager]::SecurityProtocol = $secureProtocols
$MappedUrl = Update-BaseUrl -InitialUrl $Url
Write-Log "Updated URL $Url -> $MappedUrl to download $fileName to $DestinationPath"
$oldProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
$downloadTimer = [System.Diagnostics.Stopwatch]::StartNew()
try {
$args = @{Uri=$MappedUrl; Method="Get"; OutFile=$DestinationPath}
Retry-Command -Command "Invoke-RestMethod" -Args $args -Retries 5 -RetryDelaySeconds 10
} catch {
Set-ExitCode -ExitCode $ExitCode -ErrorMessage "Failed in downloading $MappedUrl. Error: $_"
}
$downloadTimer.Stop()
if ($global:AppInsightsClient -ne $null) {
$event = New-Object "Microsoft.ApplicationInsights.DataContracts.EventTelemetry"
$event.Name = "FileDownload"
$event.Properties["FileName"] = $fileName
$event.Metrics["DurationMs"] = $downloadTimer.ElapsedMilliseconds
$global:AppInsightsClient.TrackEvent($event)
}
$ProgressPreference = $oldProgressPreference
Write-Log "Downloaded file $MappedUrl to $DestinationPath"
}
}
function Set-ExitCode
{
Param(
[Parameter(Mandatory=$true)][int]
$ExitCode,
[Parameter(Mandatory=$true)][string]
$ErrorMessage
)
Write-Log "Set ExitCode to $ExitCode and exit. Error: $ErrorMessage"
$global:ExitCode=$ExitCode
# we use | as the separator as a workaround since " or ' do not work as expected per the testings
$global:ErrorMessage=($ErrorMessage -replace '\|', '%7C')
exit $ExitCode
}
function Postpone-RestartComputer
{
Logs-To-Event -TaskName "AKS.WindowsCSE.PostponeRestartComputer" -TaskMessage "Start to create an one-time task to restart the VM"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument " -Command `"Restart-Computer -Force`""
$principal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest
# trigger this task once
$trigger = New-JobTrigger -At (Get-Date).AddSeconds(15).DateTime -Once
$definition = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Description "Restart computer after provisioning the VM"
Register-ScheduledTask -TaskName "restart-computer" -InputObject $definition
Write-Log "Created an one-time task to restart the VM"
}
function Create-Directory
{
Param(
[Parameter(Mandatory=$true)][string]
$FullPath,
[Parameter(Mandatory=$false)][string]
$DirectoryUsage = "general purpose"
)
if (-Not (Test-Path $FullPath)) {
Write-Log "Create directory $FullPath for $DirectoryUsage"
New-Item -ItemType Directory -Path $FullPath > $null
} else {
Write-Log "Directory $FullPath for $DirectoryUsage exists"
}
}
# https://stackoverflow.com/a/34559554/697126
function New-TemporaryDirectory {
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
New-Item -ItemType Directory -Path (Join-Path $parent $name)
}
function Retry-Command {
Param(
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]
$Command,
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][hashtable]
$Args,
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][int]
$Retries,
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][int]
$RetryDelaySeconds
)
for ($i = 0; ; ) {
try {
# Do not log Args since Args may contain sensitive data
Write-Log "Retry $i : $command"
return & $Command @Args
}
catch {
$i++
if ($i -ge $Retries) {
throw $_
}
Start-Sleep $RetryDelaySeconds
}
}
}
function Invoke-Executable {
Param(
[Parameter(Mandatory=$true)][string]
$Executable,
[Parameter(Mandatory=$true)][string[]]
$ArgList,
[Parameter(Mandatory=$true)][int]
$ExitCode,
[int[]]
$AllowedExitCodes = @(0),
[int]
$Retries = 0,
[int]
$RetryDelaySeconds = 1
)
for ($i = 0; $i -le $Retries; $i++) {
Write-Log "$i - Running $Executable $ArgList ..."
& $Executable $ArgList
if ($LASTEXITCODE -notin $AllowedExitCodes) {
Write-Log "$Executable returned unsuccessfully with exit code $LASTEXITCODE"
Start-Sleep -Seconds $RetryDelaySeconds
continue
}
else {
Write-Log "$Executable returned successfully"
return
}
}
Set-ExitCode -ExitCode $ExitCode -ErrorMessage "Exhausted retries for $Executable $ArgList"
}
function Assert-FileExists {
Param(
[Parameter(Mandatory = $true)][string]
$Filename,
[Parameter(Mandatory = $true)][int]
$ExitCode
)
if (-Not (Test-Path $Filename)) {
Set-ExitCode -ExitCode $ExitCode -ErrorMessage "$Filename does not exist"
}
}
function Get-WindowsBuildNumber {
return (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild
}
function Get-WindowsVersion {
$buildNumber = Get-WindowsBuildNumber
switch ($buildNumber) {
"17763" { return "1809" }
"20348" { return "ltsc2022" }
"25398" { return "23H2" }
{$_ -ge "25399" -and $_ -le "30397"} { return "test2025" }
Default {
Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_NOT_FOUND_BUILD_NUMBER -ErrorMessage "Failed to find the windows build number: $buildNumber"
}
}
}
function Get-WindowsPauseVersion {
$buildNumber = Get-WindowsBuildNumber
switch ($buildNumber) {
"17763" { return "1809" }
"20348" { return "ltsc2022" }
"25398" { return "ltsc2022" }
{$_ -ge "25399" -and $_ -le "30397"} { return "ltsc2022" }
Default {
Set-ExitCode -ExitCode $global:WINDOWS_CSE_ERROR_NOT_FOUND_BUILD_NUMBER -ErrorMessage "Failed to find the windows build number: $buildNumber"
}
}
}
function Install-Containerd-Based-On-Kubernetes-Version {
Param(
[Parameter(Mandatory = $true)][string]
$ContainerdUrl,
[Parameter(Mandatory = $true)][string]
$CNIBinDir,
[Parameter(Mandatory = $true)][string]
$CNIConfDir,
[Parameter(Mandatory = $true)][string]
$KubeDir,
[Parameter(Mandatory = $true)][string]
$KubernetesVersion
)
Logs-To-Event -TaskName "AKS.WindowsCSE.InstallContainerdBasedOnKubernetesVersion" -TaskMessage "Start to install ContainerD based on kubernetes version. ContainerdUrl: $global:ContainerdUrl, KubernetesVersion: $global:KubeBinariesVersion"
# In the past, $global:ContainerdUrl is a full URL to download Windows containerd package.
# Example: "https://acs-mirror.azureedge.net/containerd/windows/v0.0.46/binaries/containerd-v0.0.46-windows-amd64.tar.gz"
# To support multiple containerd versions, we only set the endpoint in $global:ContainerdUrl.
# Example: "https://acs-mirror.azureedge.net/containerd/windows/"
# We only set containerd package based on kubernetes version when $global:ContainerdUrl ends with "/" so we support:
# 1. Current behavior to set the full URL
# 2. Setting containerd package in toggle for test purpose or hotfix
if ($ContainerdUrl.EndsWith("/")) {
Write-Log "ContainerdURL is $ContainerdUrl"
$containerdPackage=$global:StableContainerdPackage
if (([version]$KubernetesVersion).CompareTo([version]$global:MinimalKubernetesVersionWithLatestContainerd) -ge 0) {
$containerdPackage=$global:LatestContainerdPackage
Write-Log "Kubernetes version $KubernetesVersion is greater than or equal to $global:MinimalKubernetesVersionWithLatestContainerd so the latest containerd version $containerdPackage is used"
} else {
Write-Log "Kubernetes version $KubernetesVersion is less than $global:MinimalKubernetesVersionWithLatestContainerd so the stable containerd version $containerdPackage is used"
}
$ContainerdUrl = $ContainerdUrl + $containerdPackage
}
Logs-To-Event -TaskName "AKS.WindowsCSE.InstallContainerd" -TaskMessage "Start to install ContainerD. ContainerdUrl: $ContainerdUrl"
Install-Containerd -ContainerdUrl $ContainerdUrl -CNIBinDir $CNIBinDir -CNIConfDir $CNIConfDir -KubeDir $KubeDir
}
function Logs-To-Event {
Param(
[Parameter(Mandatory = $true)][string]
$TaskName,
[Parameter(Mandatory = $true)][string]
$TaskMessage
)
$eventLevel="Informational"
if ($global:ExitCode -ne 0) {
$eventLevel="Error"
}
$eventsFileName=[DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
$currentTime=$(Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff")
$lastTaskName = ""
$lastTaskDuration = 0
if ($global:TaskTimeStamp -ne "") {
$lastTaskName = $global:TaskName
$lastTaskDuration = $(New-Timespan -Start $global:TaskTimeStamp -End $currentTime)
}
$global:TaskName = $TaskName
$global:TaskTimeStamp = $currentTime
Write-Log "$global:TaskName - $TaskMessage"
$TaskMessage = (echo $TaskMessage | ConvertTo-Json)
$messageJson = @"
{
"HostName": "$env:computername",
"LastTaskName": "$lastTaskName",
"LastTaskDuration": "$lastTaskDuration",
"CurrentTaskMessage": $TaskMessage
}
"@
$messageJson = (echo $messageJson | ConvertTo-Json)
$jsonString = @"
{
"Timestamp": "$global:TaskTimeStamp",
"OperationId": "$global:OperationId",
"Version": "1.10",
"TaskName": "$global:TaskName",
"EventLevel": "$eventLevel",
"Message": $messageJson
}
"@
echo $jsonString | Set-Content ${global:EventsLoggingDir}${eventsFileName}.json
}
# AKS will transition to use packages.aks.azure.com as the default package download acs-mirror.azureedge.net
# on June 11th, 2025. Just prior to the transition we want to have fallback logic in place to
# ensure that if packages.aks.azure.com is not reachable we can fallback to the old CDN URL
#
# This function sets the global variable $global:PackageDownloadFqdn to the preferred FQDN
# It will attempt to use the preferred FQDN first and if that fails it will fallback to the old CDN URL
function Resolve-PackagesDownloadFqdn {
Param(
[Parameter(Mandatory = $true)][string]
$PreferredFqdn,
[Parameter(Mandatory = $true)][string]
$FallbackFqdn,
[Parameter(Mandatory = $false)][int]
$Retries = 5,
[Parameter(Mandatory = $false)][int]
$WaitSleepSeconds = 1
)
$packageDownloadBaseUrl = $PreferredFqdn
for ($i = 1; $i -le $Retries; $i++) {
# Confirm that we can establish connectivity to packages.aks.azure.com before node provisioning starts
try {
$response = Invoke-WebRequest -Uri "https://${PreferredFqdn}/acs-mirror/healthz" -UseBasicParsing -TimeoutSec 5 -ErrorAction SilentlyContinue
$responseCode = [int]$response.StatusCode
if ($responseCode -eq 200) {
Write-Log "Established connectivity to $PreferredFqdn." | Out-Null
break
}
} catch {
$responseCode = 0
Write-Log "Exception while trying to establish connectivity to $PreferredFqdn. Exception: $_" | Out-Null
if ($_.Exception.Response) {
$responseCode = [int]$_.Exception.Response.StatusCode
}
}
if ($i -eq $Retries) {
# If we cannot establish connectivity to packages.aks.azure.com, fallback to old CDN URL
$packageDownloadBaseUrl = $FallbackFqdn
break
} else {
Start-Sleep -Seconds $WaitSleepSeconds
}
}
# Use Write-Output explicitly to ensure only this value is returned
$global:PackageDownloadFqdn = $packageDownloadBaseUrl
Logs-To-Event -TaskName "AKS.WindowsCSE.ResolvedPackageDomain" -TaskMessage "Package download FQDN: $global:PackageDownloadFqdn"
}
# This function will swap the domain in the URL based on the verified package download FQDN
function Update-BaseUrl {
Param(
[Parameter(Mandatory = $true)][string]
$InitialUrl
)
$updatedUrl = $InitialUrl
if (!($InitialUrl -match "acs-mirror\.azureedge\.net|packages\.aks\.azure\.com")) {
# We're probably not in Public cloud
return $updatedUrl
}
if ($global:PackageDownloadFqdn -eq $null) {
# We're in public cloud, but we haven't set the package download FQDN yet
$null = Resolve-PackagesDownloadFqdn -PreferredFqdn $global:PreferredPackageDownloadFqdn -FallbackFqdn $global:FallbackPackageDownloadFqdn
}
# Replace domain based on the current package download FQDN
if (($global:PackageDownloadFqdn -eq "packages.aks.azure.com") -and ($InitialUrl -like "https://acs-mirror.azureedge.net/*")) {
$updatedUrl = $InitialUrl -replace "acs-mirror.azureedge.net", $global:PackageDownloadFqdn
} elseif (($global:PackageDownloadFqdn -eq "acs-mirror.azureedge.net") -and ($InitialUrl -like "https://packages.aks.azure.com/*")) {
$updatedUrl = $InitialUrl -replace "packages.aks.azure.com", $global:PackageDownloadFqdn
}
return $updatedUrl
}