STL/azure-devops/create-vmss.ps1

444 строки
14 KiB
PowerShell

# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
<#
.SYNOPSIS
Creates a Windows virtual machine scale set, set up for the STL's CI.
.DESCRIPTION
create-vmss.ps1 creates an Azure Windows VM scale set, set up for the STL's CI
system. See https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/overview
for more information.
This script assumes you have installed Azure tools into PowerShell by following the instructions
at https://docs.microsoft.com/en-us/powershell/azure/install-az-ps
or are running from Azure Cloud Shell.
#>
$ErrorActionPreference = 'Stop'
# https://aka.ms/azps-changewarnings
$Env:SuppressAzurePowerShellBreakingChangeWarnings = 'true'
$Location = 'westus2'
$Prefix = 'StlBuild-' + (Get-Date -Format 'yyyy-MM-dd-THHmm')
$VMSize = 'Standard_D32ads_v5'
$ProtoVMName = 'PROTOTYPE'
$LiveVMPrefix = 'BUILD'
$ImagePublisher = 'MicrosoftWindowsServer'
$ImageOffer = 'WindowsServer'
$ImageSku = '2022-datacenter-g2'
$ProgressActivity = 'Creating Scale Set'
$TotalProgress = 14
$CurrentProgress = 1
<#
.SYNOPSIS
Attempts to find a name that does not collide with any resources in the resource group.
.DESCRIPTION
Find-ResourceGroupName takes a set of resources from Get-AzResourceGroup, and finds the
first name in {$Prefix, $Prefix-1, $Prefix-2, ...} such that the name doesn't collide with
any of the resources in the resource group.
.PARAMETER Prefix
The prefix of the final name; the returned name will be of the form "$Prefix(-[1-9][0-9]*)?"
#>
function Find-ResourceGroupName {
[CmdletBinding()]
Param([string] $Prefix)
$existingNames = (Get-AzResourceGroup).ResourceGroupName
$result = $Prefix
$suffix = 0
while ($result -in $existingNames) {
$suffix++
$result = "$Prefix-$suffix"
}
return $result
}
<#
.SYNOPSIS
Generates a random password.
.DESCRIPTION
New-Password generates a password, randomly, of length $Length, containing
only alphanumeric characters, underscore, and dash.
.PARAMETER Length
The length of the returned password.
#>
function New-Password {
Param ([int] $Length = 32)
# This 64-character alphabet generates 6 bits of entropy per character.
# The power-of-2 alphabet size allows us to select a character by masking a random Byte with bitwise-AND.
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'
$mask = 63
if ($alphabet.Length -ne 64) {
throw 'Bad alphabet length'
}
[Byte[]]$randomData = [Byte[]]::new($Length)
$rng = $null
try {
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($randomData)
}
finally {
if ($null -ne $rng) {
$rng.Dispose()
}
}
$result = ''
for ($idx = 0; $idx -lt $Length; $idx++) {
$result += $alphabet[$randomData[$idx] -band $mask]
}
return $result
}
<#
.SYNOPSIS
Waits for the shutdown of the specified resource.
.DESCRIPTION
Wait-Shutdown takes a VM, and checks if there's a 'PowerState/stopped'
code; if there is, it returns. If there isn't, it waits 10 seconds and
tries again.
.PARAMETER ResourceGroupName
The name of the resource group to look up the VM in.
.PARAMETER Name
The name of the virtual machine to wait on.
#>
function Wait-Shutdown {
[CmdletBinding()]
Param([string]$ResourceGroupName, [string]$Name)
Write-Host "Waiting for $Name to stop..."
while ($true) {
$Vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $Name -Status
$highestStatus = $Vm.Statuses.Count
for ($idx = 0; $idx -lt $highestStatus; $idx++) {
if ($Vm.Statuses[$idx].Code -eq 'PowerState/stopped') {
return
}
}
Write-Host '... not stopped yet, sleeping for 10 seconds'
Start-Sleep -Seconds 10
}
}
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Setting the subscription context' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Set-AzContext -SubscriptionName CPP_STL_GitHub | Out-Null
az account set --subscription CPP_STL_GitHub
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating resource group' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$ResourceGroupName = Find-ResourceGroupName $Prefix
$AdminPW = New-Password
New-AzResourceGroup -Name $ResourceGroupName -Location $Location | Out-Null
$AdminPWSecure = ConvertTo-SecureString $AdminPW -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ('AdminUser', $AdminPWSecure)
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating virtual network' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$allowHttp = New-AzNetworkSecurityRuleConfig `
-Name AllowHTTP `
-Description 'Allow HTTP(S)' `
-Access Allow `
-Protocol Tcp `
-Direction Outbound `
-Priority 1000 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange @(80, 443)
$allowQuic = New-AzNetworkSecurityRuleConfig `
-Name AllowQUIC `
-Description 'Allow QUIC' `
-Access Allow `
-Protocol Udp `
-Direction Outbound `
-Priority 1010 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange 443
$allowDns = New-AzNetworkSecurityRuleConfig `
-Name AllowDNS `
-Description 'Allow DNS' `
-Access Allow `
-Protocol * `
-Direction Outbound `
-Priority 1020 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange 53
$denyEverythingElse = New-AzNetworkSecurityRuleConfig `
-Name DenyElse `
-Description 'Deny everything else' `
-Access Deny `
-Protocol * `
-Direction Outbound `
-Priority 2000 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange *
$NetworkSecurityGroupName = $ResourceGroupName + '-NetworkSecurity'
$NetworkSecurityGroup = New-AzNetworkSecurityGroup `
-Name $NetworkSecurityGroupName `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-SecurityRules @($allowHttp, $allowQuic, $allowDns, $denyEverythingElse)
$SubnetName = $ResourceGroupName + '-Subnet'
$Subnet = New-AzVirtualNetworkSubnetConfig `
-Name $SubnetName `
-AddressPrefix '10.0.0.0/16' `
-NetworkSecurityGroup $NetworkSecurityGroup
$VirtualNetworkName = $ResourceGroupName + '-Network'
$VirtualNetwork = New-AzVirtualNetwork `
-Name $VirtualNetworkName `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-AddressPrefix '10.0.0.0/16' `
-Subnet $Subnet
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating prototype VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$NicName = $ResourceGroupName + '-NIC'
$Nic = New-AzNetworkInterface `
-Name $NicName `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-Subnet $VirtualNetwork.Subnets[0]
$VM = New-AzVMConfig -Name $ProtoVMName -VMSize $VMSize -Priority 'Spot' -MaxPrice -1
$VM = Set-AzVMOperatingSystem `
-VM $VM `
-Windows `
-ComputerName $ProtoVMName `
-Credential $Credential `
-ProvisionVMAgent
$VM = Add-AzVMNetworkInterface -VM $VM -Id $Nic.Id
$VM = Set-AzVMSourceImage `
-VM $VM `
-PublisherName $ImagePublisher `
-Offer $ImageOffer `
-Skus $ImageSku `
-Version latest
$VM = Set-AzVMBootDiagnostic -VM $VM -Disable
New-AzVm `
-ResourceGroupName $ResourceGroupName `
-Location $Location `
-VM $VM | Out-Null
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Running provisioning script provision-image.ps1 in VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$ProvisionImageResult = Invoke-AzVMRunCommand `
-ResourceGroupName $ResourceGroupName `
-VMName $ProtoVMName `
-CommandId 'RunPowerShellScript' `
-ScriptPath "$PSScriptRoot\provision-image.ps1" `
-Parameter @{AdminUserPassword = $AdminPW }
Write-Host "provision-image.ps1 output: $($ProvisionImageResult.value.Message)"
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Restarting VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Restart-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName | Out-Null
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Sleeping after restart' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
# The VM appears to be busy immediately after restarting.
# This workaround waits for a minute before attempting to run sysprep.ps1.
Start-Sleep -Seconds 60
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Running provisioning script sysprep.ps1 in VM' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Invoke-AzVMRunCommand `
-ResourceGroupName $ResourceGroupName `
-VMName $ProtoVMName `
-CommandId 'RunPowerShellScript' `
-ScriptPath "$PSScriptRoot\sysprep.ps1" | Out-Null
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Waiting for VM to shut down' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Wait-Shutdown -ResourceGroupName $ResourceGroupName -Name $ProtoVMName
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Converting VM to Image' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Stop-AzVM `
-ResourceGroupName $ResourceGroupName `
-Name $ProtoVMName `
-Force | Out-Null
Set-AzVM `
-ResourceGroupName $ResourceGroupName `
-Name $ProtoVMName `
-Generalized | Out-Null
$VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName
$PrototypeOSDiskName = $VM.StorageProfile.OsDisk.Name
$ImageConfig = New-AzImageConfig -Location $Location -SourceVirtualMachineId $VM.ID -HyperVGeneration 'V2'
$Image = New-AzImage -Image $ImageConfig -ImageName $ProtoVMName -ResourceGroupName $ResourceGroupName
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Deleting unused VM and disk' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
Remove-AzVM -Id $VM.ID -Force | Out-Null
Remove-AzDisk `
-ResourceGroupName $ResourceGroupName `
-DiskName $PrototypeOSDiskName `
-Force | Out-Null
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Creating scale set' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$VmssIpConfigName = $ResourceGroupName + '-VmssIpConfig'
$VmssIpConfig = New-AzVmssIpConfig -SubnetId $Nic.IpConfigurations[0].Subnet.Id -Primary -Name $VmssIpConfigName
$VmssName = $ResourceGroupName + '-Vmss'
$Vmss = New-AzVmssConfig `
-Location $Location `
-SkuCapacity 0 `
-SkuName $VMSize `
-SkuTier 'Standard' `
-Overprovision $false `
-UpgradePolicyMode Manual `
-EvictionPolicy Delete `
-Priority Spot `
-MaxPrice -1
$Vmss = Add-AzVmssNetworkInterfaceConfiguration `
-VirtualMachineScaleSet $Vmss `
-Primary $true `
-IpConfiguration $VmssIpConfig `
-NetworkSecurityGroupId $NetworkSecurityGroup.Id `
-Name $NicName
$Vmss = Set-AzVmssOsProfile `
-VirtualMachineScaleSet $Vmss `
-ComputerNamePrefix $LiveVMPrefix `
-AdminUsername 'AdminUser' `
-AdminPassword $AdminPW `
-WindowsConfigurationProvisionVMAgent $true `
-WindowsConfigurationEnableAutomaticUpdate $true
$Vmss = Set-AzVmssStorageProfile `
-VirtualMachineScaleSet $Vmss `
-OsDiskCreateOption 'FromImage' `
-OsDiskCaching ReadWrite `
-ImageReferenceId $Image.Id
$Vmss = New-AzVmss `
-ResourceGroupName $ResourceGroupName `
-Name $VmssName `
-VirtualMachineScaleSet $Vmss
####################################################################################################
Write-Progress `
-Activity $ProgressActivity `
-Status 'Enabling VMSS diagnostic logs' `
-PercentComplete (100 / $TotalProgress * $CurrentProgress++)
$StorageAccountName = 'stlvmssdiaglogssa'
$ExpirationDate = (Get-Date -AsUTC).AddYears(1).ToString('yyyy-MM-ddTHH:mmZ')
$StorageAccountSASToken = $(az storage account generate-sas `
--account-name $StorageAccountName `
--expiry $ExpirationDate `
--permissions acuw `
--resource-types co `
--services bt `
--https-only `
--output tsv `
2> $null)
$DiagnosticsDefaultConfig = $(az vmss diagnostics get-default-config --is-windows-os 2> $null). `
Replace('__DIAGNOSTIC_STORAGE_ACCOUNT__', $StorageAccountName). `
Replace('__VM_OR_VMSS_RESOURCE_ID__', $Vmss.Id)
Out-File -FilePath "$PSScriptRoot\vmss-config.json" -InputObject $DiagnosticsDefaultConfig
$DiagnosticsProtectedSettings = "{'storageAccountName': '$StorageAccountName', "
$DiagnosticsProtectedSettings += "'storageAccountSasToken': '?$StorageAccountSASToken'}"
az vmss diagnostics set `
--resource-group $ResourceGroupName `
--vmss-name $VmssName `
--settings "$PSScriptRoot\vmss-config.json" `
--protected-settings "$DiagnosticsProtectedSettings" `
--output none
####################################################################################################
Write-Progress -Activity $ProgressActivity -Completed
Write-Host "Location: $Location"
Write-Host "Resource group name: $ResourceGroupName"
Write-Host 'Finished!'