parts/k8s/windowsazurecnifunc.ps1 (348 lines of code) (raw):
function Install-VnetPlugins
{
Param(
[Parameter(Mandatory=$true)][string]
$AzureCNIConfDir,
[Parameter(Mandatory=$true)][string]
$AzureCNIBinDir,
[Parameter(Mandatory=$true)][string]
$VNetCNIPluginsURL
)
# Create CNI directories.
mkdir $AzureCNIBinDir
mkdir $AzureCNIConfDir
# Download Azure VNET CNI plugins.
# Mirror from https://github.com/Azure/azure-container-networking/releases
$zipfile = [Io.path]::Combine("$AzureCNIDir", "azure-vnet.zip")
DownloadFileOverHttp -Url $VNetCNIPluginsURL -DestinationPath $zipfile
Expand-Archive -path $zipfile -DestinationPath $AzureCNIBinDir
del $zipfile
# Windows does not need a separate CNI loopback plugin because the Windows
# kernel automatically creates a loopback interface for each network namespace.
# Copy CNI network config file and set bridge mode.
move $AzureCNIBinDir/*.conflist $AzureCNIConfDir
}
function Set-AzureCNIConfig
{
Param(
[Parameter(Mandatory=$true)][string]
$AzureCNIConfDir,
[Parameter(Mandatory=$true)][string]
$KubeDnsSearchPath,
[Parameter(Mandatory=$true)][string]
$KubeClusterCIDR,
[Parameter(Mandatory=$true)][string]
$MasterSubnet,
[Parameter(Mandatory=$true)][string]
$KubeServiceCIDR,
[Parameter(Mandatory=$true)][string]
$VNetCIDR,
[Parameter(Mandatory=$true)][bool]
$IsAzureStack,
[Parameter(Mandatory=$true)][bool]
$IsDualStackEnabled
)
# Fill in DNS information for kubernetes.
if ($IsDualStackEnabled){
$subnetToPass = $KubeClusterCIDR -split ","
$exceptionAddresses = @($subnetToPass[0], $MasterSubnet, $VNetCIDR)
}
else {
$exceptionAddresses = @($KubeClusterCIDR, $MasterSubnet, $VNetCIDR)
}
$fileName = [Io.path]::Combine("$AzureCNIConfDir", "10-azure.conflist")
$configJson = Get-Content $fileName | ConvertFrom-Json
$configJson.plugins.dns.Nameservers[0] = $KubeDnsServiceIp
$configJson.plugins.dns.Search[0] = $KubeDnsSearchPath
$osBuildNumber = (get-wmiobject win32_operatingsystem).BuildNumber
if ($osBuildNumber -le 17763){
# In WS2019 and below rules in the exception list are generated by dropping the prefix lenght and removing duplicate rules.
# If multiple execptions are specified with different ranges we should only include the broadest range for each address.
# This issue has been addressed in 19h1+ builds
$processedExceptions = GetBroadestRangesForEachAddress $exceptionAddresses
Write-Host "Filtering CNI config exception list values to work around WS2019 issue processing rules. Original exception list: $exceptionAddresses, processed exception list: $processedExceptions"
$configJson.plugins.AdditionalArgs[0].Value.ExceptionList = $processedExceptions
}
else {
if ($IsDualStackEnabled) {
$ipv4Cidrs = @()
$ipv6Cidrs = @()
foreach ($cidr in $exceptionAddresses) {
# this is the pwsh way of strings.Count(s, ":") >= 2
if (($cidr -split ":").Count -ge 3) {
$ipv6Cidrs += $cidr
} else {
$ipv4Cidrs += $cidr
}
}
# we just assume the first entry in additional Args is the exception
# list for IPv4 and then append a new EnpointPolicy for IPv6. We
# probably shouldn't hard code the first one like this and just build
# 2 EndpointPolicies and append to the AdditionalArgs.
$configJson.plugins.AdditionalArgs[0].Value.ExceptionList = $ipv4Cidrs
$outboundException = [PSCustomObject]@{
Name = 'EndpointPolicy'
Value = [PSCustomObject]@{
Type = 'OutBoundNAT'
ExceptionList = $ipv6Cidrs
}
}
$configJson.plugins[0].AdditionalArgs += $outboundException
} else {
$configJson.plugins.AdditionalArgs[0].Value.ExceptionList = $exceptionAddresses
}
}
if ($IsDualStackEnabled){
$configJson.plugins[0]|Add-Member -Name "ipv6Mode" -Value "ipv6nat" -MemberType NoteProperty
$serviceCidr = $KubeServiceCIDR -split ","
$configJson.plugins[0].AdditionalArgs[1].Value.DestinationPrefix = $serviceCidr[0]
$valueObj = [PSCustomObject]@{
Type = 'ROUTE'
DestinationPrefix = $serviceCidr[1]
NeedEncap = $True
}
$jsonContent = [PSCustomObject]@{
Name = 'EndpointPolicy'
Value = $valueObj
}
$configJson.plugins[0].AdditionalArgs += $jsonContent
}
else {
$configJson.plugins[0].AdditionalArgs[1].Value.DestinationPrefix = $KubeServiceCIDR
}
if ($IsAzureStack) {
Add-Member -InputObject $configJson.plugins[0].ipam -MemberType NoteProperty -Name "environment" -Value "mas"
}
if ($global:KubeproxyFeatureGates.Contains("WinDSR=true")) {
Write-Log "Setting enableLoopbackDSR in Azure CNI conflist for WinDSR"
$jsonContent = [PSCustomObject]@{
'enableLoopbackDSR' = $True
}
$configJson.plugins[0]|Add-Member -Name "windowsSettings" -Value $jsonContent -MemberType NoteProperty
}
$aclRule1 = [PSCustomObject]@{
Type = 'ACL'
Protocols = '6'
Action = 'Block'
Direction = 'Out'
RemoteAddresses = '168.63.129.16/32'
RemotePorts = '80'
Priority = 200
RuleType = 'Switch'
}
$aclRule2 = [PSCustomObject]@{
Type = 'ACL'
Action = 'Allow'
Direction = 'In'
Priority = 65500
}
$aclRule3 = [PSCustomObject]@{
Type = 'ACL'
Action = 'Allow'
Direction = 'Out'
Priority = 65500
}
$jsonContent = [PSCustomObject]@{
Name = 'EndpointPolicy'
Value = $aclRule1
}
$configJson.plugins[0].AdditionalArgs += $jsonContent
$jsonContent = [PSCustomObject]@{
Name = 'EndpointPolicy'
Value = $aclRule2
}
$configJson.plugins[0].AdditionalArgs += $jsonContent
$jsonContent = [PSCustomObject]@{
Name = 'EndpointPolicy'
Value = $aclRule3
}
$configJson.plugins[0].AdditionalArgs += $jsonContent
$configJson | ConvertTo-Json -depth 20 | Out-File -encoding ASCII -filepath $fileName
}
function GetBroadestRangesForEachAddress{
param([string[]] $values)
# Create a map of range values to IP addresses
$map = @{}
foreach ($value in $Values) {
if ($value -match '([0-9\.]+)\/([0-9]+)') {
if (!$map.contains($matches[1])) {
$map.Add($matches[1], @())
}
$map[$matches[1]] += [int]$matches[2]
}
}
# For each IP address select the range with the lagest scope (smallest value)
$returnValues = @()
foreach ($ip in $map.Keys) {
$range = $map[$ip] | Sort-Object | Select-Object -First 1
$returnValues += $ip + "/" + $range
}
# prefix $returnValues with common to ensure single values get returned as an array otherwise invalid json may be generated
return ,$returnValues
}
function GetSubnetPrefix
{
Param(
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Token,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SubnetId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceManagerEndpoint,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $NetworkAPIVersion
)
$uri = "$($ResourceManagerEndpoint)$($SubnetId)?api-version=$NetworkAPIVersion"
$headers = @{Authorization="Bearer $Token"}
try {
$response = Retry-Command -Command "Invoke-RestMethod" -Args @{Uri=$uri; Method="Get"; ContentType="application/json"; Headers=$headers} -Retries 5 -RetryDelaySeconds 10
} catch {
throw "Error getting subnet prefix. Error: $_"
}
$response.properties.addressPrefix
}
function GenerateAzureStackCNIConfig
{
Param(
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $TenantId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $SubscriptionId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $AADClientId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $AADClientSecret,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $ResourceGroup,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $NetworkAPIVersion,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $AzureEnvironmentFilePath,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $IdentitySystem,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $KubeDir
)
$networkInterfacesFile = "$KubeDir\network-interfaces.json"
$azureCNIConfigFile = "$KubeDir\interfaces.json"
$azureEnvironment = Get-Content $AzureEnvironmentFilePath | ConvertFrom-Json
Write-Log "------------------------------------------------------------------------"
Write-Log "Parameters"
Write-Log "------------------------------------------------------------------------"
Write-Log "TenantId: $TenantId"
Write-Log "SubscriptionId: $SubscriptionId"
Write-Log "AADClientId: ..."
Write-Log "AADClientSecret: ..."
Write-Log "ResourceGroup: $ResourceGroup"
Write-Log "NetworkAPIVersion: $NetworkAPIVersion"
Write-Log "ServiceManagementEndpoint: $($azureEnvironment.serviceManagementEndpoint)"
Write-Log "ActiveDirectoryEndpoint: $($azureEnvironment.activeDirectoryEndpoint)"
Write-Log "ResourceManagerEndpoint: $($azureEnvironment.resourceManagerEndpoint)"
Write-Log "------------------------------------------------------------------------"
Write-Log "Variables"
Write-Log "------------------------------------------------------------------------"
Write-Log "azureCNIConfigFile: $azureCNIConfigFile"
Write-Log "networkInterfacesFile: $networkInterfacesFile"
Write-Log "------------------------------------------------------------------------"
Write-Log "Generating token for Azure Resource Manager"
$tokenURL = ""
if($IdentitySystem -ieq "adfs") {
$tokenURL = "$($azureEnvironment.activeDirectoryEndpoint)adfs/oauth2/token"
} else {
$tokenURL = "$($azureEnvironment.activeDirectoryEndpoint)$TenantId/oauth2/token"
}
Add-Type -AssemblyName System.Web
$encodedSecret = [System.Web.HttpUtility]::UrlEncode($AADClientSecret)
$body = "grant_type=client_credentials&client_id=$AADClientId&client_secret=$encodedSecret&resource=$($azureEnvironment.serviceManagementEndpoint)"
$args = @{Uri=$tokenURL; Method="Post"; Body=$body; ContentType='application/x-www-form-urlencoded'}
try {
$tokenResponse = Retry-Command -Command "Invoke-RestMethod" -Args $args -Retries 5 -RetryDelaySeconds 10
} catch {
throw "Error generating token for Azure Resource Manager. Error: $_"
}
$token = $tokenResponse | Select-Object -ExpandProperty access_token
Write-Log "Fetching network interface configuration for node"
$interfacesUri = "$($azureEnvironment.resourceManagerEndpoint)subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Network/networkInterfaces?api-version=$NetworkAPIVersion"
$headers = @{Authorization="Bearer $token"}
$args = @{Uri=$interfacesUri; Method="Get"; ContentType="application/json"; Headers=$headers; OutFile=$networkInterfacesFile}
try {
Retry-Command -Command "Invoke-RestMethod" -Args $args -Retries 5 -RetryDelaySeconds 10
} catch {
throw "Error fetching network interface configuration for node. Error: $_"
}
Write-Log "Generating Azure CNI interface file"
$localNics = Get-NetAdapter | Select-Object -ExpandProperty MacAddress | ForEach-Object {$_ -replace "-",""}
$sdnNics = Get-Content $networkInterfacesFile `
| ConvertFrom-Json `
| Select-Object -ExpandProperty value `
| Where-Object { $null -ne $_.properties.macAddress -and $localNics.Contains($_.properties.macAddress) } `
| Where-Object { $_.properties.ipConfigurations.Count -gt 0}
if (!$sdnNics) {
throw 'Error extracting the SDN interfaces from the network interfaces file'
}
$interfaces = @{
Interfaces = @( $sdnNics | ForEach-Object { @{
MacAddress = $_.properties.macAddress
IsPrimary = $_.properties.primary
IPSubnets = @(@{
Prefix = GetSubnetPrefix `
-Token $token `
-SubnetId $_.properties.ipConfigurations[0].properties.subnet.id `
-NetworkAPIVersion $NetworkAPIVersion `
-ResourceManagerEndpoint $($azureEnvironment.resourceManagerEndpoint)
IPAddresses = $_.properties.ipConfigurations | ForEach-Object { @{
Address = $_.properties.privateIPAddress
IsPrimary = $_.properties.primary
}}
})
}})
}
ConvertTo-Json $interfaces -Depth 6 | Out-File -FilePath $azureCNIConfigFile -Encoding ascii
Set-ItemProperty -Path $azureCNIConfigFile -Name IsReadOnly -Value $true
}
function New-ExternalHnsNetwork
{
param (
[Parameter(Mandatory=$true)][bool]
$IsDualStackEnabled
)
Write-Log "Creating new HNS network `"ext`""
$externalNetwork = "ext"
$nas = @(Get-NetAdapter -Physical)
if ($nas.Count -eq 0) {
throw "Failed to find any physical network adapters"
}
# If there is more than one adapter, use the first adapter that is assigned an ipaddress.
foreach($na in $nas)
{
$netIP = Get-NetIPAddress -ifIndex $na.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue -ErrorVariable netIPErr
if ($netIP)
{
$managementIP = $netIP.IPAddress
$adapterName = $na.Name
break
}
else {
Write-Log "No IPv4 found on the network adapter $($na.Name); trying the next adapter ..."
if ($netIPErr) {
Write-Log "error when retrieving IPAddress: $netIPErr"
$netIPErr.Clear()
}
}
}
if(-Not $managementIP)
{
throw "None of the physical network adapters has an IP address"
}
Write-Log "Using adapter $adapterName with IP address $managementIP"
$mgmtIPAfterNetworkCreate
$stopWatch = New-Object System.Diagnostics.Stopwatch
$stopWatch.Start()
# Fixme : use a smallest range possible, that will not collide with any pod space
if ($IsDualStackEnabled) {
New-HNSNetwork -Type $global:NetworkMode -AddressPrefix @("192.168.255.0/30","192:168:255::0/127") -Gateway @("192.168.255.1","192:168:255::1") -AdapterName $adapterName -Name $externalNetwork -Verbose
}
else {
New-HNSNetwork -Type $global:NetworkMode -AddressPrefix "192.168.255.0/30" -Gateway "192.168.255.1" -AdapterName $adapterName -Name $externalNetwork -Verbose
}
# Wait for the switch to be created and the ip address to be assigned.
for ($i = 0; $i -lt 60; $i++) {
$mgmtIPAfterNetworkCreate = Get-NetIPAddress $managementIP -ErrorAction SilentlyContinue
if ($mgmtIPAfterNetworkCreate) {
break
}
Start-Sleep -Milliseconds 500
}
$stopWatch.Stop()
if (-not $mgmtIPAfterNetworkCreate) {
throw "Failed to find $managementIP after creating $externalNetwork network"
}
Write-Log "It took $($StopWatch.Elapsed.Seconds) seconds to create the $externalNetwork network."
}