[Azure][Automation]定時自動將 Azure VM 給 Scale Up 或 Scale Down

仿照前一篇文章對 Automation 上的 PowerShell 範本做一點調整,讓你可以按照所想要的時間,自動 Scale 調整 VM 的等級,做最經濟的考量

在前一篇文章中「定時自動將 Azure SQL Database 給 Scale Up 或 Scale Down」,主要是介紹使用別人已經寫好的腳本來做使用,但如果有些時候,您想要的並沒有辦法在 runbooks gallery 裡面找到合適的腳本,其實您也可以跟我用類似的方法,先找一個類似的功能腳本,再來修改成為您所想要的功能。

這個緣由是我本來要找一個可以自動定時調整 VM 規模的腳本,因為我們放在 Azure 的 VM 在工作日的時候,某些時候的尖峰需要比較多的核心去運算,但晚上或者是周末,就不需要那麼高的資源。因此如果按照白天所需要的規格去選擇 VM 的規模,那所需要的成本就會非常高。很可惜的我在 runbooks 上並沒有找到合適的,只有找到前一篇文章中所介紹的腳本,因此我就參考那個腳本去做一些小幅的調整,就變成一個符合我所使用的 Automotion 上的 Runbook 了。

腳本內容如下:

<#   
.SYNOPSIS   
    Vertically scale an Azure VM up or down according to a 
    schedule using Azure Automation.    
   
.DESCRIPTION   
    This Azure Automation runbook enables vertically scaling of 
    an Azure VM according to a schedule. 

.PARAMETER resourceGroupName
    Name of the resource group to which the VM is assigned.

.PARAMETER azureRunAsConnectionName
    Azure Automation Run As account name. Needs to be able to access
    the $serverName.

 .PARAMETER serverName  
    Azure VM name.
       
.PARAMETER scalingSchedule
    VM Scaling Schedule. It is possible to enter multiple 
    comma separated schedules: [{},{}]
    Weekdays start at 0 (sunday) and end at 6 (saturday).
    If the script is executed outside the scaling schedule time slots
    that you defined, the defaut edition/tier (see below) will be 
    configured.

.PARAMETER scalingScheduleTimeZone
    Time Zone of time slots in $scalingSchedule. 
    Available time zones: [System.TimeZoneInfo]::GetSystemTimeZones().

.PARAMETER defaultSize
    Azure VM that wil be used outside the slots 
    specified in the scalingSchedule paramater value.

.EXAMPLE
        -resourceGroupName myResourceGroup
        -azureRunAsConnectionName AzureRunAsConnection
        -serverName myserver
        -scalingSchedule [{WeekDays:[1], StartTime:"06:59:59", StopTime:"17:59:59", Size: "Standard_D3_v2"}, {WeekDays:[2,3,4,5], StartTime:"06:59:59", StopTime:"17:59:59", Size: "Standard_D2_v2"}]
        -scalingScheduleTimeZone Taipei Standard Time
        -defaultSize Standard_D1_v2
   
.NOTES   
    Author: James Fu
    Last Update: Apr 2018   
#>  
param(
[parameter(Mandatory=$true)]
[string] $resourceGroupName,

[parameter(Mandatory=$false)]
[string] $azureRunAsConnectionName = "AzureRunAsConnection",

[parameter(Mandatory=$true)]
[string] $serverName,

[parameter(Mandatory=$true)]
[string] $scalingSchedule,

[parameter(Mandatory=$false)]
[string] $scalingScheduleTimeZone = "Taipei Standard Time",

[parameter(Mandatory=$false)]
[string] $defaultSize = "Standard_D1_v2"
)

filter timestamp {"[$(Get-Date -Format G)]: $_"}

Write-Output "Script started." | timestamp

$VerbosePreference = "Continue"
$ErrorActionPreference = "Stop"

#Authenticate with Azure Automation Run As account (service principal)  
$runAsConnectionProfile = Get-AutomationConnection `
-Name $azureRunAsConnectionName
Add-AzureRmAccount -ServicePrincipal `
-TenantId $runAsConnectionProfile.TenantId `
-ApplicationId $runAsConnectionProfile.ApplicationId `
-CertificateThumbprint ` $runAsConnectionProfile.CertificateThumbprint | Out-Null
Write-Output "Authenticated with Automation Run As Account."  | timestamp

#Get current date/time and convert to $scalingScheduleTimeZone
$stateConfig = $scalingSchedule | ConvertFrom-Json
$startTime = Get-Date
Write-Output "Azure Automation local time: $startTime." | timestamp
$toTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($scalingScheduleTimeZone)
Write-Output "Time zone to convert to: $toTimeZone." | timestamp
$newTime = [System.TimeZoneInfo]::ConvertTime($startTime, $toTimeZone)
Write-Output "Converted time: $newTime." | timestamp
$startTime = $newTime

#Get current day of week, based on converted start time
$currentDayOfWeek = [Int]($startTime).DayOfWeek
Write-Output "Current day of week: $currentDayOfWeek." | timestamp

# Get the scaling schedule for the current day of week
$dayObjects = $stateConfig | Where-Object {$_.WeekDays -contains $currentDayOfWeek } `
|Select-Object Size, `
@{Name="StartTime"; Expression = {[datetime]::ParseExact($_.StartTime,"HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)}}, `
@{Name="StopTime"; Expression = {[datetime]::ParseExact($_.StopTime,"HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)}}

# Get the VM object
$vm = Get-AzureRmVm -ResourceGroupName $resourceGroupName -Name $serverName 
Write-Output "VM name: $($vm.Name)" | timestamp
Write-Output "Current VM size: $($vm.HardwareProfile.vmSize)" | timestamp

if($dayObjects -ne $null) { # Scaling schedule found for this day
    # Get the scaling schedule for the current time. If there is more than one available, pick the first
    $matchingObject = $dayObjects | Where-Object { ($startTime -ge $_.StartTime) -and ($startTime -lt $_.StopTime) } | Select-Object -First 1
    if($matchingObject -ne $null)
    {
        Write-Output "Scaling schedule found. Check if current size is matching..." | timestamp
        if($vm.HardwareProfile.vmSize -ne $matchingObject.Size)
        {
            Write-Output "VM is not in the size of the scaling schedule. Changing!" | timestamp
			$vm.HardwareProfile.VmSize = $matchingObject.Size
		    Update-AzureRmVm -VM $vm -ResourceGroupName $ResourceGroupName | out-null
            Write-Output "Change to size as specified in scaling schedule initiated..." | timestamp
			$vm = Get-AzureRmVm -ResourceGroupName $resourceGroupName -Name $serverName 
            Write-Output "Current VM size: $($vm.HardwareProfile.VmSize)" | timestamp
        } 
        else 
        {
            Write-Output "Current VM size matches the scaling schedule already. Exiting..." | timestamp
        }
    }
    else { # Scaling schedule not found for current time
        Write-Output "No matching scaling schedule time slot for this time found. Check if current edition/tier matches the default..." | timestamp
        if($vm.HardwareProfile.vmSize -ne $defaultSize)
        {
            Write-Output "VM is not in the default size. Changing!" | timestamp
			$vm.HardwareProfile.VmSize = $defaultSize
		    Update-AzureRmVm -VM $vm -ResourceGroupName $ResourceGroupName | out-null
            Write-Output "Change to default size initiated." | timestamp
			$vm = Get-AzureRmVm -ResourceGroupName $resourceGroupName -Name $serverName 
            Write-Output "Current VM size: $($vm.HardwareProfile.VmSize)" | timestamp
        }
        else
        {
            Write-Output "Current VM size matches the default already. Exiting..." | timestamp
        }
    }
}
else # Scaling schedule not found for this day
{
    Write-Output "No matching scaling schedule for this day found. Check if current size matches the default..." | timestamp
    if($vm.HardwareProfile.vmSize -ne $defaultSize)
    {
        Write-Output "VM is not in the default size. Changing!" | timestamp
		$vm.HardwareProfile.VmSize = $defaultSize
	    Update-AzureRmVm -VM $vm -ResourceGroupName $ResourceGroupName | out-null
        Write-Output "Change to default size initiated." | timestamp
		$vm = Get-AzureRmVm -ResourceGroupName $resourceGroupName -Name $serverName 
        Write-Output "Current VM size: $($vm.HardwareProfile.VmSize)" | timestamp
    }
    else
    {
        Write-Output "Current VM size matches the default already. Exiting..." | timestamp
    }
}

Write-Output "Script finished." | timestamp

基本上這個腳本需要六個參數

  1. 資源群組名稱 : 主要是記錄你要改哪個資源群組下的資料庫
  2. 連線資訊:如果是跟 Azure Automation 在同一個訂閱帳號下的資料庫要被管理,那麼就不用設定
  3. Azure VM 名稱:給 VM 的名稱就好,不用填寫全域名稱
  4. 時間區間:設定每週的哪個時間需要將 VM 設定為哪個等級
  5. 時區:用來指定上述時間是哪個時區,預設為 Taipei Standard Time
  6. 預設規格:在非第 4 項時間範圍內的時候,使用哪個規模大小

VM 的規模大小,可以用以下幾個參數,設定的時候要注意一下,也要注意所在的區域和訂閱是能夠支援的

Basic_A0
Basic_A1
Basic_A2
Basic_A3
Basic_A4

Standard_A0
Standard_A1
Standard_A2
Standard_A3
Standard_A4
Standard_A5
Standard_A6
Standard_A7
Standard_A8
Standard_A9
Standard_A10
Standard_A11

Standard_A1_v2
Standard_A2_v2
Standard_A4_v2
Standard_A8_v2
Standard_A2m_v2
Standard_A4m_v2
Standard_A8m_v2

Standard_D1_v2
Standard_D2_v2
Standard_D3_v2
Standard_D4_v2
Standard_D5_v2
Standard_DS1_v2
Standard_DS2_v2
Standard_DS3_v2
Standard_DS4_v2
Standard_DS5_v2
Standard_D11_v2
Standard_D12_v2
Standard_D13_v2
Standard_D14_v2
Standard_D15_v2
Standard_DS11_v2
Standard_DS12_v2
Standard_DS13_v2
Standard_DS14_v2
Standard_DS15_v2
Standard_DS1
Standard_DS2
Standard_DS3
Standard_DS4
Standard_DS11
Standard_DS12
Standard_DS13
Standard_DS14
Standard_D1
Standard_D2
Standard_D3
Standard_D4
Standard_D11
Standard_D12
Standard_D13
Standard_D14

Standard_G1
Standard_G2
Standard_G3
Standard_G4
Standard_G5
Standard_GS1
Standard_GS2
Standard_GS3
Standard_GS4
Standard_GS5
Standard_D2_v3
Standard_D4_v3
Standard_D8_v3
Standard_D16_v3
Standard_D32_v3
Standard_D64_v3
Standard_D2s_v3
Standard_D4s_v3
Standard_D8s_v3
Standard_D16s_v3
Standard_D32s_v3
Standard_D64s_v3

Standard_E2_v3
Standard_E4_v3
Standard_E8_v3
Standard_E16_v3
Standard_E32_v3
Standard_E64_v3
Standard_E2s_v3
Standard_E4s_v3
Standard_E8s_v3
Standard_E16s_v3
Standard_E32s_v3
Standard_E64s_v3

Standard_F1
Standard_F2
Standard_F4
Standard_F8
Standard_F16
Standard_F1s
Standard_F2s
Standard_F4s
Standard_F8s
Standard_F16s
Standard_F2s_v2
Standard_F4s_v2
Standard_F8s_v2
Standard_F16s_v2
Standard_F32s_v2
Standard_F64s_v2
Standard_F72s_v2

Standard_L4s
Standard_L8s
Standard_L16s
Standard_L32s

Standard_NV6
Standard_NV12
Standard_NV24
Standard_NC6
Standard_NC12
Standard_NC24
Standard_H8
Standard_H16
Standard_H8m
Standard_H16m