tools/scripts/AksEdgeQuickStart/AksEdgeQuickStartForAio.ps1 (518 lines of code) (raw):

<# QuickStart script for setting up Azure for AKS Edge Essentials and deploying the same on the Windows device #> param( [Parameter(Mandatory)] [String] $aideUserConfigfile, [Parameter(Mandatory)] [String] $aksedgeConfigFile, [string] $Tag ) #Requires -RunAsAdministrator New-Variable -Name gAksEdgeQuickStartForAioVersion -Value "1.0.250313.1500" -Option Constant -ErrorAction SilentlyContinue # Specify only AIO supported regions New-Variable -Option Constant -ErrorAction SilentlyContinue -Name arcLocations -Value @( "eastus", "eastus2", "northeurope", "westeurope", "westus", "westus2", "westus3" ) function Wait-ApiServerReady { $retries = 120 for (; $retries -gt 0; $retries--) { $ret = & kubectl get --raw='/readyz' if ($ret -eq "ok") { Write-Host "ApiServerReady!" break } Write-Host "WaitForApiServer - Retry..." Start-Sleep -Seconds 1 } if ($retries -eq 0) { throw "waiting for API server timed out!" } } function Restart-ApiServer { param( [Parameter(Mandatory=$true)] [string] $serviceAccountIssuer, [Switch] $useK8s=$false ) Write-Host "serviceAccountIssuer = $serviceAccountIssuer" if ($useK8s) { Invoke-AksEdgeNodeCommand -command "sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml | tee /home/aksedge-user/kube-apiserver.yaml | tee /home/aksedge-user/kube-apiserver.yaml.working > /dev/null" Invoke-AksEdgeNodeCommand -command "sudo sed -i 's|service-account-issuer.*|service-account-issuer=$serviceAccountIssuer|' /home/aksedge-user/kube-apiserver.yaml" Invoke-AksEdgeNodeCommand -command "sudo cp /home/aksedge-user/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml" & kubectl delete pod -n kube-system -l component=kube-apiserver } else { Invoke-AksEdgeNodeCommand -command "sudo cat /var/.eflow/config/k3s/k3s-config.yml | tee /home/aksedge-user/k3s-config.yml | tee /home/aksedge-user/k3s-config.yml.working > /dev/null" Invoke-AksEdgeNodeCommand -command "sudo sed -i 's|service-account-issuer.*|service-account-issuer=$serviceAccountIssuer|' /home/aksedge-user/k3s-config.yml" Invoke-AksEdgeNodeCommand -command "sudo cp /home/aksedge-user/k3s-config.yml /var/.eflow/config/k3s/k3s-config.yml" Invoke-AksEdgeNodeCommand -command "sudo systemctl restart k3s.service" } Wait-ApiServerReady } function Verify-ConnectedStatus { param( [Parameter(Mandatory=$true)] [string] $resourceGroup, [Parameter(Mandatory=$true)] [string] $clusterName, [Parameter(Mandatory=$true)] [string] $subscriptionId, [Switch] $enableWorkloadIdentity=$false ) $retries = 90 for (; $retries -gt 0; $retries--) { $connectedCluster = az connectedk8s show -g $resourceGroup -n $clusterName --subscription $subscriptionId | ConvertFrom-Json if ($enableWorkloadIdentity) { $agentState = $connectedCluster.arcAgentProfile.agentState Write-Host "$retries, AgentState = $agentState" } $connectivityStatus = $connectedCluster.ConnectivityStatus Write-Host "$retries, connectivityStatus = $connectivityStatus" if ($connectedCluster.ConnectivityStatus -eq "Connected") { if ((-Not $enableWorkloadIdentity) -Or ($connectedCluster.arcAgentProfile.agentState -eq "Succeeded")) { Write-Host "Cluster reached connected status" break } } Write-Host "Arc connection status is $($connectedCluster.ConnectivityStatus). Waiting for status to be connected..." Start-Sleep -Seconds 10 } if ($retries -eq 0) { throw "waiting for cluster connected status timed out!" } } function New-ConnectedCluster { param( [Parameter(Mandatory=$true)] [object] $arcArgs, [Parameter(Mandatory=$true)] [string] $clusterName, [object] $proxyArgs, [Switch] $useK8s=$false ) Write-Host "New-ConnectedCluster" $tags = @("SKU=AKSEdgeEssentials") $aksEdgeVersion = (Get-Module -Name AksEdge).Version.ToString() if ($aksEdgeVersion) { $tags += @("AKSEE Version=$aksEdgeVersion") } $infra = Get-AideInfra if ($infra) { $tags += @("Host Infra=$infra") } $clusterid = $(kubectl get configmap -n aksedge aksedge -o jsonpath="{.data.clustername}") if ($clusterid) { $tags += @("ClusterId=$clusterid") } $k8sConnectArgs = @("-g", $arcArgs.ResourceGroupName) $k8sConnectArgs += @("-n", $clusterName) $k8sConnectArgs += @("-l", $arcArgs.Location) $k8sConnectArgs += @("--subscription", $arcArgs.SubscriptionId) $k8sConnectArgs += @("--tags", $tags) $k8sConnectArgs += @("--disable-auto-upgrade") if ($null -ne $proxyArgs) { if (-Not [string]::IsNullOrEmpty($proxyArgs.Http)) { $k8sConnectArgs += @("--proxy-http", $proxyArgs.Http) } if (-Not [string]::IsNullOrEmpty($proxyArgs.Https)) { $k8sConnectArgs += @("--proxy-https", $proxyArgs.Https) } if (-Not [string]::IsNullOrEmpty($proxyArgs.No)) { $k8sConnectArgs += @("--proxy-skip-range", $proxyArgs.No) } } if ($arcArgs.EnableWorkloadIdentity) { $k8sConnectArgs += @("--enable-oidc-issuer", "--enable-workload-identity") } if (-Not [string]::IsNullOrEmpty($arcArgs.GatewayResourceId)) { $k8sConnectArgs += @("--gateway-resource-id", $arcArgs.GatewayResourceId) } # Use kubectl.exe from AKSEE deployment $env:KUBECTL_CLIENT_PATH = "$env:ProgramFiles\AksEdge\kubectl\kubectl.exe" Write-Host "Connect cmd args - $k8sConnectArgs" $errOut = $($retVal = & {az connectedk8s connect $k8sConnectArgs}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Arc Connection failed with error : $errOut" } # For debugging Write-Host "az connectedk8s out : $retVal" Verify-ConnectedStatus -clusterName $ClusterName -resourcegroup $arcArgs.ResourceGroupName -subscriptionId $arcArgs.SubscriptionId -enableWorkloadIdentity:$arcArgs.EnableWorkloadIdentity if ($arcArgs.EnableWorkloadIdentity) { $errOut = $($obj = & {az connectedk8s show -g $arcArgs.ResourceGroupName -n $clusterName | ConvertFrom-Json}) 2>&1 if ($null -eq $obj) { throw "Invalid, empty IssuerUrl!" } $serviceAccountIssuer = $obj.oidcIssuerProfile.issuerUrl if ([string]::IsNullOrEmpty($serviceAccountIssuer)) { throw "Invalid, empty IssuerUrl!" } Write-Host "serviceAccountIssuer = $serviceAccountIssuer" Restart-ApiServer -serviceAccountIssuer $serviceAccountIssuer -useK8s:$useK8s Write-Host "Restart ARC agents." & kubectl -n azure-arc rollout restart deployment } } #Validate inputs ## function ValidateConfigFile { param( [ValidateNotNullOrEmpty()] [string] $filePath ) if (!(Test-Path -Path $filePath -PathType Leaf)) { throw "Config file '$filePath' not found!" } try { $configJson = Get-Content "$filePath" } catch { $err = $_.Exception.Message.ToString() throw "Failed to read $filePath content with error $err" } try { $configObj = ($configJson | ConvertFrom-Json) } catch { $err = $_.Exception.Message.ToString() throw "Failed to parse $filePath with error $err" } return $configObj } function ValidateConfig { param( [object] $aideUserConfig, [object] $aksedgeConfig ) #Validate inputs $supportedProductTypes = @("AKS Edge Essentials - K3s") if ([string]::IsNullOrEmpty($aideuserConfig.AksEdgeProduct) -or $supportedProductTypes -notcontains $aideuserConfig.AksEdgeProduct) { throw "AideUserConfig.AksEdgeProduct $($aideuserConfig.AksEdgeProduct) is invalid! Supported values: $supportedProductTypes." } if ([string]::IsNullOrEmpty($aideuserConfig.Azure.SubscriptionId)) { throw "Require SubscriptionId for Azure Arc" } if ([string]::IsNullOrEmpty($aideuserConfig.Azure.TenantId)) { throw "Require TenantId for Azure Arc" } if ([string]::IsNullOrEmpty($aideuserConfig.Azure.Location)) { throw "Require Location for Azure Arc" } elseif ($arcLocations -inotcontains $aideuserConfig.Azure.Location) { Write-Host "Supported Locations : $arcLocations" throw "Location $($aideuserConfig.Azure.Location) is not supported for Azure Arc" } if ([string]::IsNullOrEmpty($aksedgeConfig.Arc.ClusterName)) { throw "Require ClusterName for Azure Arc" } } function Get-AkseeInstalledProductName { return (Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' | Get-ItemProperty | Where-Object {$_.DisplayName -like "*Aks Edge Essentials*"}).DisplayName } function EnsurePreRequisites { if (! [Environment]::Is64BitProcess) { throw "Error: Run this in 64bit Powershell session" } # Validate az cli version. $azVersion = (az version)[1].Split(":")[1].Split('"')[1] $azMinRequiredVersion = "2.64.0" if ($azVersion -lt $azMinRequiredVersion){ throw "Installed Azure CLI version $azVersion is older than $azMinRequiredVersion. Please upgrade Azure CLI and retry." } $installedAkseeProductName = Get-AkseeInstalledProductName if (-Not [string]::IsNullOrEmpty($installedAkseeProductName)) { if ($installedAkseeProductName -like "*K8s*") { throw "Detected AKSEE k8s installation. Please uninstall and run the script again!" } } # Ensure logged into Azure $azureLogin = az account show if ( $null -eq $azureLogin){ throw "Please login to azure via `az login` and retry." } $errOut = $($retVal = & {az extension add --upgrade --name connectedk8s -y}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Error upgrading extension connecktedk8s : $errOut" } } function EnsureDeploymentPrerequisites { param( [object] $aideUserConfig, [object] $aksedgeConfig, [string ] $workdir ) ValidateConfig -aideUserConfig $aideuserConfig -aksedgeConfig $aksedgeConfig $aksedgeShell = (Get-ChildItem -Path "$workdir" -Filter AksEdgeShell.ps1 -Recurse).FullName . $aksedgeShell } function SetupAksEdgeRepo { param( [string] $installDir, [string] $fork ="Azure", [string] $branch="main", [string] $Tag ) $url = "https://github.com/$fork/AKS-Edge/archive/$branch.zip" $zipFile = "AKS-Edge-$branch.zip" $workdir = "$installDir\AKS-Edge-$branch" if (-Not [string]::IsNullOrEmpty($Tag)) { $url = "https://github.com/$fork/AKS-Edge/archive/refs/tags/$Tag.zip" $zipFile = "$Tag.zip" $workdir = "$installDir\AKS-Edge-$tag" } if (!(Test-Path -Path "$installDir\$zipFile")) { try { Invoke-WebRequest -Uri $url -OutFile $installDir\$zipFile -UseBasicParsing } catch { throw "Error: Downloading Aide Powershell Modules failed" } } if (!(Test-Path -Path "$workdir")) { Expand-Archive -Path $installDir\$zipFile -DestinationPath "$installDir" -Force } return $workdir } function DeployAksEdge { param ( [String] $aideUserConfigFile ) # invoke the workflow, the json file already updated above. $retval = Start-AideWorkflow -jsonFile $aideUserConfigFile if ($retval) { Write-Host "Deployment Successful. " } else { throw "Deployment failed" } } function ConnectAksEdgeArc { param ( [object] $aideUserConfig, [object] $aksedgeConfig ) $SubscriptionId = $aideuserConfig.Azure.SubscriptionId $ResourceGroupName = $aideuserConfig.Azure.ResourceGroupName $ClusterName = $aksedgeConfig.Arc.ClusterName # Set the azure subscription $errOut = $($retVal = & {az account set -s $SubscriptionId}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Error setting Subscription ($SubscriptionId): $errOut" } # Create resource group $errOut = $($rgExists = & {az group show --resource-group $ResourceGroupName}) 2>&1 if ($null -eq $rgExists) { Write-Host "Creating resource group: $ResourceGroupName" -ForegroundColor Cyan $errOut = $($retVal = & {az group create --location $Location --resource-group $ResourceGroupName --subscription $SubscriptionId}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Error creating ResourceGroup ($ResourceGroupName): $errOut" } } # Register the required resource providers Write-Host "Registering the required resource providers for AIO" -ForegroundColor Cyan $resourceProviders = @( "Microsoft.ExtendedLocation", "Microsoft.Kubernetes", "Microsoft.KubernetesConfiguration" ) foreach($rp in $resourceProviders) { $errOut = $($obj = & {az provider show -n $rp | ConvertFrom-Json}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Error querying provider $rp : $errOut" } if ($obj.registrationState -eq "Registered") { continue } $errOut = $($retVal = & {az provider register -n $rp}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Error registering provider $rp : $errOut" } } # Arc-enable the Kubernetes cluster Write-Host "Arc enable the kubernetes cluster $ClusterName" -ForegroundColor Cyan New-ConnectedCluster -clusterName $ClusterName -arcArgs $aideuserConfig.Azure -proxyArgs $aksedgeConfig.Network.Proxy # Enable custom location support on your cluster using az connectedk8s enable-features command $objectId = $aideuserConfig.Azure.CustomLocationOID if ([string]::IsNullOrEmpty($objectId)) { Write-Host "Associate Custom location with $ClusterName cluster" $customLocationsAppId = "bc313c14-388c-4e7d-a58e-70017303ee3b" $errOut = $($objectId = & {az ad sp show --id $customLocationsAppId --query id -o tsv}) 2>&1 if ($null -eq $objectId) { throw "Error querying ObjectId for CustomLocationsAppId : $errOut" } } $errOut = $($retVal = & {az connectedk8s enable-features -n $ClusterName -g $ResourceGroupName --custom-locations-oid $objectId --features cluster-connect custom-locations}) 2>&1 if ($LASTEXITCODE -ne 0) { throw "Error enabling feature CustomLocations : $errOut" } } function PrepareForAioWorkloadDeployment { param( [string] $workdir ) Write-Host "Deploy local path provisioner" try { $localPathProvisionerYaml= (Get-ChildItem -Path "$workdir" -Filter local-path-storage.yaml -Recurse).FullName & kubectl apply -f $localPathProvisionerYaml Write-Host "Successfully deployment the local path provisioner" } catch { throw "Error: local path provisioner deployment failed" } Write-Host "Configuring firewall specific to AIO" try { $fireWallRuleExists = Get-NetFirewallRule -DisplayName "AIO MQTT Broker" -ErrorAction SilentlyContinue if ( $null -eq $fireWallRuleExists ) { Write-Host "Add firewall rule for AIO MQTT Broker" New-NetFirewallRule -DisplayName "AIO MQTT Broker" -Direction Inbound -Action Allow | Out-Null } else { Write-Host "firewall rule for AIO MQTT Broker exists, skip configuring firewall rule..." } } catch { throw "Error: Firewall rule addition for AIO MQTT broker failed" } Write-Host "Configuring port proxy for AIO" try { $deploymentInfo = Get-AksEdgeDeploymentInfo # Get the service ip address start to determine the connect address $connectAddress = $deploymentInfo.LinuxNodeConfig.ServiceIpRange.split("-")[0] $portProxyRulExists = netsh interface portproxy show v4tov4 | findstr /C:"1883" | findstr /C:"$connectAddress" if ( $null -eq $portProxyRulExists ) { Write-Host "Configure port proxy for AIO" netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=1883 connectaddress=$connectAddress | Out-Null } else { Write-Host "Port proxy rule for AIO exists, skip configuring port proxy..." } } catch { throw "Error: port proxy update for AIO failed" } Write-Host "Update the iptables rules" try { $iptableRulesExist = Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo iptables-save | grep -- '-m tcp --dport 9110 -j ACCEPT'" -ignoreError if ( $null -eq $iptableRulesExist ) { Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 9110 -j ACCEPT" Write-Host "Updated runtime iptable rules for node exporter" Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo sed -i '/-A OUTPUT -j ACCEPT/i-A INPUT -p tcp -m tcp --dport 9110 -j ACCEPT' /etc/systemd/scripts/ip4save" Write-Host "Persisted iptable rules for node exporter" } else { Write-Host "iptable rule exists, skip configuring iptable rules..." } # Add additional firewall rules $dports = @(10124, 8420, 2379, 50051) foreach($port in $dports) { $iptableRulesExist = Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo iptables-save | grep -- '-m tcp --dport $port -j ACCEPT'" -ignoreError if ( $null -eq $iptableRulesExist ) { Invoke-AksEdgeNodeCommand -NodeType "Linux" -command "sudo iptables -A INPUT -p tcp --dport $port -j ACCEPT" Write-Host "Updated runtime iptable rules for port $port" } else { Write-Host "iptable rule exists, skip configuring iptable rule for port $port..." } } } catch { throw "iptable rule update failed" } } ### # Main ### try { EnsurePrerequisites $installDir = $((Get-Location).Path) $starttime = Get-Date $starttimeString = $($starttime.ToString("yyMMdd-HHmm")) $transcriptFile = "$installDir\aksedgedlog-$starttimeString.txt" Start-Transcript -Path $transcriptFile Set-ExecutionPolicy Bypass -Scope Process -Force Write-Host "Step 1 : Azure/AKS-Edge repo setup" -ForegroundColor Cyan $workdir = SetupAksEdgeRepo -installDir $installDir -Tag $Tag Write-Host "Step 2 : Ensure Deployment prerequisites" if ([string]::IsNullOrEmpty($aksedgeConfigFile)) { $aksedgeConfigFile = "$workdir\tools\aio-aksedge-config.json" } $aksedgeConfig = ValidateConfigFile -filePath $aksedgeConfigFile $aksedgeConfigRepoFile = (Get-ChildItem -Path "$workdir" -Filter aksedge-config.json -Recurse).FullName Set-Content -Path $aksedgeConfigRepoFile -Value ($aksedgeConfig | ConvertTo-Json -Depth 6) -Force if ([string]::IsNullOrEmpty($aideUserConfigFile)) { $aideUserConfigFile = "$workdir\tools\aio-aide-userconfig.json" } $aideuserConfig = ValidateConfigFile -filePath $aideUserConfigFile $aideuserConfig.AksEdgeConfigFile = "aksedge-config.json" $aideuserConfig.AksEdgeProductUrl = "https://download.microsoft.com/download/67fee208-b68d-47a3-81a5-454382df99a6/AksEdge-K3s-1.30.6.msi" $aideuserConfigRepoFile = (Get-ChildItem -Path "$workdir" -Filter aide-userconfig.json -Recurse).FullName Set-Content -Path $aideuserConfigRepoFile -Value ($aideuserConfig | ConvertTo-Json -Depth 6) -Force EnsureDeploymentPrerequisites -aideUserConfig $aideUserConfig -aksedgeConfig $aksedgeConfig -workdir $workdir Write-Host "Step 3: Download, install and deploy AKS Edge Essentials" -ForegroundColor Cyan DeployAksEdge -aideUserConfigFile $aideuserConfigRepoFile Write-Host "Step 4: Connect the cluster to Azure" -ForegroundColor Cyan ConnectAksEdgeArc -aideUserConfig $aideUserConfig -aksedgeConfig $aksedgeConfig Write-Host "Step 5: Prep for AIO workload deployment" -ForegroundColor Cyan PrepareForAioWorkloadDeployment -workdir $workdir } catch { $fileName = Split-Path -Path ($_.InvocationInfo.ScriptName) -Leaf $lineNumber = $_.InvocationInfo.ScriptLineNumber Write-Host "AIO-QuickStart failed with error: $_" -ForegroundColor Red Write-Host "at file: $fileName, line: $lineNumber" -ForegroundColor Red } finally { $endtime = Get-Date $duration = ($endtime - $starttime) Write-Host "Duration: $($duration.Hours) hrs $($duration.Minutes) mins $($duration.Seconds) seconds" Stop-Transcript | Out-Null Pop-Location exit -1 } exit 0