Эта заметка описывает модификацию решения Запуск и остановка Azure серверов по расписанию от апреля 2019 года.
В процессе работы с инфраструктурой Azure обнаружился не очень приятный факт, что команды на включение виртуальных машин могут не исполняться, в результате чего сервер остается в выключенном состоянии. Например, на картинке ниже видно, как несколько раз подавались команды на запуск сервера и от скрипта, и непосредственно от пользователя через портал Azure, но облако выдавало ошибку "Allocation failed. If you are trying to add a new VM to an Availability Set or update/resize an existing VM in an Availability Set, please note that such Availability Set allocation is scoped to a single cluster, and it is possible that the cluster is out of capacity..." А бот из центра поддержки центра отвечал: "The hardware cluster where the VM is currently deployed did not have enough capacity of this size to support your allocation request." Инженер из центра поддержки Microsoft подтвердил проблему в Дата Центре Azure и посоветовал модифицировать сценарий запуска сервера - проверять через некоторое время, запустился ли сервер и присылать отчет администратору.

Это сподвигло меня проверить скрипты, используемые в решении, дополнить их несколькими улучшениями, а так же написать еще один скрипт, который бы проверял состояние серверов и отправлял письма об ошибках запуска виртуальных машин администраторам системы, что им необходимо подключиться к порталу Azure и проконтролировать процесс.
Основные отличия в обновленном сценарии - это
- добавление переменной $Location, чтобы назначать регион в котором отрабатывать скрипт,
- добавление штампа даты и времени к событиям в логе скрипта - команда Get-Date -Format "HH:mm:ss" в каждой строке вывода, что помогает в расследовании инцидентов с запуском виртуальных машин,
- обработка машин только из назначенного региона, то есть серверов с одними и теми же настройками часового пояса (это сделано, чтоб избежать ошибок вычисления времени запуска серверов в разных регионах) - опция -Location $Location в команде выборки виртуальных машин,
В остальном же сценарий остался без изменений.
Ниже представлены сами модифицированные скрипты, а в заметке Проверка запуска Azure серверов и уведомление администраторов рассказывается о контролирующем запуск скрипте.
$connectionName = "AzureRunAsConnection"
$TagsTimeZone = "Central Europe Standard Time"
$TagsTimeZoneAbbreviation = "CET"
$Location = "westeurope"
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "----- The script started -----")
Try {
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Logging in to Azure...")
Login-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint | Out-Null
}
catch {
if (!$servicePrincipalConnection) {
$ErrorMessage = "Connection $connectionName not found"
throw $ErrorMessage
} else {
Write-Error -Message $_.Exception
throw $_.Exception
}
}
# Making time filter
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "System time zone = " + ([TimeZoneInfo]::Local).Id)
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Current system time = " + (Get-Date -Format "dd.MM.yyyy HH:mm, ddd") )
$TagsTime = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString("dd.MM.yyyy HH:mm, ddd")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Tags time zone = $($TagsTimeZone)")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Tags time = $($TagsTime)")
$START_TIME = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString('H:00')
$START_DAY = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString('ddd')
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Adapting time to search for ""$($START_TIME)"" in tags")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Looking for instances where ""Schedule Start"" tag = ""$($START_TIME)"" ...")
[array]$VMs = Get-AzureRMVm -Location $Location | Where-Object {$PSItem.Tags["Schedule Start"] -eq $START_TIME}
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "$($VMs.Count) instances found")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Processing the instances...")
ForEach ($VM in $VMs) {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "$($VM.Name) instance in $($VM.ResourceGroupName) resource group:")
$VMTags = $VM.Tags
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Checking the ""Schedule Days"" tag ...")
If ( -not($VMTags.Keys -contains "Schedule Days") -or $VMTags["Schedule Days"].Split(',').Trim() -contains $START_DAY ) {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is allowed to be processed today" )
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Checking the instance status ...")
$VMStatus = (Get-AzureRMVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status).Statuses[1].DisplayStatus
If ($VMStatus -eq "VM deallocated") {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is in ""$($VMStatus)"" state")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Starting the instance")
Start-AzureRMVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -AsJob | Out-Null
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Updating COMMENT tag for the instance")
$VMTagCommentText = ("Started by Start-Stop Scheduler at " + `
([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString("dd.MM.yyyy HH:mm") + `
" $($TagsTimeZoneAbbreviation)")
If (-not($VMTags.ContainsKey("Comment"))) {
$VMTags.Add("Comment", $VMTagCommentText)
} Else {
$VMTags["Comment"] = $VMTagCommentText
}
Set-AzureRMResource -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -ResourceType "Microsoft.Compute/VirtualMachines" `
-Tag $VMTags -Force -AsJob | Out-Null
} Else {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is in ""$($VMStatus)"" state")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " No action needed")
}
} Else {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is not allowed to be processed today" )
}
}
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "----- The script stopped -----")
Write-Output (Get-Job)
$connectionName = "AzureRunAsConnection"
$TagsTimeZone = "Central Europe Standard Time"
$TagsTimeZoneAbbreviation = "CET"
$Location = "westeurope"
Write-Output ("----- The script started -----")
Try {
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Logging in to Azure...")
Login-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint | Out-Null
}
catch {
if (!$servicePrincipalConnection) {
$ErrorMessage = "Connection $connectionName not found"
throw $ErrorMessage
} else {
Write-Error -Message $_.Exception
throw $_.Exception
}
}
# Making time filter
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "System time zone = " + ([TimeZoneInfo]::Local).Id)
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Current system time = " + (Get-Date -Format "dd.MM.yyyy HH:mm, ddd") )
$TagsTime = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString("dd.MM.yyyy HH:mm, ddd")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Tags time zone = $($TagsTimeZone)")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Tags time = $($TagsTime)")
$STOP_TIME = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString('H:00')
$STOP_DAY = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString('ddd')
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Adapting time to search for ""$($STOP_TIME)"" in tags")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Looking for instances where ""Schedule Stop"" tag = ""$($STOP_TIME)"" ...")
[array]$VMs = Get-AzureRMVm -Location $Location | Where-Object {$PSItem.Tags["Schedule Stop"] -eq $STOP_TIME}
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "$($VMs.Count) instances found")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "Processing the instances...")
ForEach ($VM in $VMs) {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + "$($VM.Name) instance in $($VM.ResourceGroupName) resource group:")
$VMTags = $VM.Tags
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Checking the ""Schedule Days"" tag ...")
IF ( -not($VMTags.Keys -contains "Schedule Days") -or $VMTags["Schedule Days"].Split(',').Trim() -contains $STOP_DAY ) {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is allowed to be processed today" )
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Checking the instance status ...")
$VMStatus = (Get-AzureRMVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status).Statuses[1].DisplayStatus
If ($VMStatus -eq "VM running") {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is in ""$($VMStatus)"" state")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Stopping the instance")
Stop-AzureRMVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Force -AsJob | Out-Null
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " Updating COMMENT tag for the instance")
$VMTagCommentText = ("Stopped by Start-Stop Scheduler at " + `
([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), $TagsTimeZone)).ToString("dd.MM.yyyy HH:mm") + `
" $($TagsTimeZoneAbbreviation)")
If (-not($VMTags.ContainsKey("Comment"))) {
$VMTags.Add("Comment", $VMTagCommentText)
} Else {
$VMTags["Comment"] = $VMTagCommentText
}
Set-AzureRMResource -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -ResourceType "Microsoft.Compute/VirtualMachines" `
-Tag $VMTags -Force -AsJob | Out-Null
} Else {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is in ""$($VMStatus)"" state")
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " No action needed")
}
} Else {
Write-Output ((Get-Date -Format "HH:mm:ss") + ": " + " The instance is not allowed to be processed today")
}
}
Write-Output ("----- The script stopped -----")
