Для того, чтобы уменьшить расходы на инфраструктуру, можно перевести тестовые серверы в облако Azure и запускать машины только когда они нужны. Чтобы автоматизировать процесс активности серверов, Azure предлагает инструмент Automation Account (учетная запись автоматизации), который доступен по этой ссылке. Проблема представленного метода управления состоянием виртуальных машин в том, что он управляет целой ресурсной группой, а не конкретными машинами, а так же для всех машин будет одно и то же расписание управления.
В этой заметке я предлагаю решение, которое управляет машинами индивидуально, сканируя тэги у всех машин в подписке. Подобный метод используется в этой публикации.
- Запуск сервиса каждый час.
- Вход в подписку от имени сервисной учетной записи.
- Сканирование всех виртуальных машин в подписке и поиск тегов "Schedule Start" и "Schedule Stop" у которых значение равно текущему часу.
- Проверка у найденных объектов тега "Schedule Days", если в нем есть текущий день недели, или тег "Schedule Days" отсутствует вообще (разрешены все дни недели).
- Проверка состояния найденных объектов (запущены или остановлены).
- Запуск или остановка отфильтрованных виртуальных машин.
- Запись выполненного действия в тег "Comment" для каждого обработанного объекта.
Возможные значения (только одно из перечисленных значений) тегов "Schedule Start" и "Schedule Stop": 0:00, 1:00, 2:00, 3:00, 4:00, 5:00, 6:00, 7:00, 8:00, 9:00, 10:00, 11:00, 12:00, 13:00, 14:00, 15:00, 16:00, 17:00, 18:00, 19:00, 20:00, 21:00, 22:00, 23:00 - то есть каждый час без учета минут. Часовой пояс времени, используемых в тегах задается в переменных скрипта-обработчика.
Возможные значения (список значений через запятую) тега "Schedule Days": Mon, Tue, Wed, Thu, Fri, Sat, Sun - то есть список дней недели через запятую, когда можно запускать обработчик включения-выключения виртуальной машины. Если тег у виртуальной машины не установлен (не существует), то разрешены все дни недели. Если тег установлен, но пустой, то запрещены все дни недели - задание выполняться не будет.
Для запуска задания от сервисной учетной записи необходимо создать объект типа Automation Account. В моем примере он будет называться aac-StartStopSchedule.
1. В консоли Azure в поиске объектов необходимо ввести Automation и выбрать из результатов поиска сервис Automation Account.
2. Далее необходимо добавить новый объект.
3. Дать объекту имя, например aac-StartStopSchedule. Указать подписку, ресурсную группу, расположение и тип учетной записи. Надать кнопку Создать.
4. В результе создания служебной учетной записи может появиться ошибка, что Классическая учетная запись не создалась - это можно проигнорировать если у вас в системе нет объектов старого типа Classic.
5. В свойствах созданной учетной записи в разделе Run as accounts можно проверить, что учетная запись для портала создана, а для классического старого портала не создана.
6. В разделе Connections можно увидеть типы соединения созданные к порталу.
7. В разделе Modules располагаются модули, используемые служебной учетной записью. Рекомендуется обновить все модули до последней версии. Для этого нужно нажать на кнопку Update Azure modules над списком модулей.
8. В результате успешного обновления над списком модулей появится соотвествующая запись, а числа версий модулей изменятся на другие.
Служебная учетная запись создана. Теперь можно писать скрипты и назначать этому аккаунту запуск скриптов.
Когда служебная учетная запись создана и настроена, то можно писать скрипты и назначать их запуск с помощью этой учетной записи.
1. В свойствах служебной учетной записи перейти в пункт Runbooks и создать новый объект скрипта.
2. В окне создания скрипта указать имя, например run-StartSchedule, тип Powershell и нажать кнопку Создать.
3. Когда объект скрипта создан, в его свойствах нужно нажать на кнопку Редактирования.
4. Скопировать в окно редактирования скрипт из листинга ниже, нажать Сохранить и Опубликовать.
5. Далее нужно назначить расписание на запуск скрипта на вкладке Schedules в свойствах объекта скрипта. Для этого надо нажать на кнопку Добавить расписание.
6. В свойствах расписания выбрать уже существующее расписание или создать новое, например Run script every hour. В свойствах скипта указать Время начала работы расписания, Часовой пояс, Периодичность - каждый час, Устаревание - нет.
Теперь мы имеем всё, что необходимо для управления состояния серверов по расписанию: сервисный аккаунт, скрипт, расписание.
Чтобы поставить виртуальную машину в расписание на запуск и остановку, нужно создать в её свойствах теги Schedule Start и Schedule Stop, указав в них значения часов запуска и остановки в формате [Час без ведущих нулей]:00, например 7:00 и 18:00 соответсвенно.
Опционально можно установить тег Schedule Days, в котором указать дни недели, когда применять расписание, например Mon, Wed, Fri. Если этого тега не существует, то расписание выполняется ежедневно, если тег существует, но пустой, то расписание не выполняется вообще.
$connectionName = "AzureRunAsConnection" $TagsTimeZone = "Central Europe Standard Time" $TagsTimeZoneAbbreviation = "CET" Write-Output ("----- The script started -----") Try { # Get the connection "AzureRunAsConnection " $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName Write-Output ("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 ("System time zone = " + ([TimeZoneInfo]::Local).Id) Write-Output ("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 ("Tags time zone = $($TagsTimeZone)") Write-Output ("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 ("Adapting time to search for ""$($START_TIME)"" in tags") Write-Output ("Looking for instances where ""Schedule Start"" tag = ""$($START_TIME)"" ...") [array]$VMs = Get-AzureRMVm | Where-Object {$PSItem.Tags["Schedule Start"] -eq $START_TIME} Write-Output ("$($VMs.Count) instances found") Write-Output ("Processing the instances...") ForEach ($VM in $VMs) { Write-Output "$($VM.Name) instance in $($VM.ResourceGroupName) resource group:" $VMTags = $VM.Tags Write-Output " Checking the ""Schedule Days"" tag ..." IF ( -not($VMTags.Keys -contains "Schedule Days") -or $VMTags["Schedule Days"].Split(',').Trim() -contains $START_DAY ) { Write-Output " The instance is allowed to be processed today" Write-Output " Checking the instance status ..." $VMStatus = (Get-AzureRMVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status).Statuses[1].DisplayStatus If ($VMStatus -eq "VM deallocated") { Write-Output " The instance is in ""$($VMStatus)"" state" Write-Output " Starting the instance" Start-AzureRMVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -AsJob | Out-Null Write-Output " 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 " The instance is in ""$($VMStatus)"" state" Write-Output " No action needed" } } Else { Write-Output " The instance is not allowed to be processed today" } } Write-Output ("----- The script stopped -----")
$connectionName = "AzureRunAsConnection" $TagsTimeZone = "Central Europe Standard Time" $TagsTimeZoneAbbreviation = "CET" Write-Output ("----- The script started -----") Try { # Get the connection "AzureRunAsConnection " $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName Write-Output ("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 ("System time zone = " + ([TimeZoneInfo]::Local).Id) Write-Output ("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 ("Tags time zone = $($TagsTimeZone)") Write-Output ("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 ("Adapting time to search for ""$($STOP_TIME)"" in tags") Write-Output ("Looking for instances where ""Schedule Stop"" tag = ""$($STOP_TIME)"" ...") [array]$VMs = Get-AzureRMVm | Where-Object {$PSItem.Tags["Schedule Stop"] -eq $STOP_TIME} Write-Output ("$($VMs.Count) instances found") Write-Output ("Processing the instances...") ForEach ($VM in $VMs) { Write-Output "$($VM.Name) instance in $($VM.ResourceGroupName) resource group:" $VMTags = $VM.Tags Write-Output " Checking the ""Schedule Days"" tag ..." IF ( -not($VMTags.Keys -contains "Schedule Days") -or $VMTags["Schedule Days"].Split(',').Trim() -contains $STOP_DAY ) { Write-Output " The instance is allowed to be processed today" Write-Output " Checking the instance status ..." $VMStatus = (Get-AzureRMVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status).Statuses[1].DisplayStatus If ($VMStatus -eq "VM running") { Write-Output " The instance is in ""$($VMStatus)"" state" Write-Output " Stopping the instance" Stop-AzureRMVM -Name $VM.Name -ResourceGroupName $VM.ResourceGroupName -Force -AsJob | Out-Null Write-Output " 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 " The instance is in ""$($VMStatus)"" state" Write-Output " No action needed" } } Else { Write-Output " The instance is not allowed to be processed today" } } Write-Output ("----- The script stopped -----")