14
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

[Microsoft Azure]勤務時間中だけ仮想マシンを動かす(スケジュールによる自動起動・停止)

概要

AutomationアカウントにPowerShellワークフローを登録し、スケジュールに合わせて仮想マシンを起動/停止します。

仮想マシンの起動/停止は、サービスプリンシパルとして登録したActive Directoryアカウント権限で実行します。

下記情報を参考にしました。


2016年4月7日追記:
下記手順はとても長くて面倒なので、モジュール化してPowerShell Galleryへ公開しました。

こちらへどうぞ。 【再】[Microsoft Azure]勤務時間中だけ仮想マシンを動かす(スケジュールによる自動起動・停止)


準備

必要なPowerShellモジュールをインストールします。

PowerShellからMicrosoft Azureを管理する【準備編】

手順

ステップ :zero:

ログイン

まだログインしていなければログインします。

Login-AzureRmAccount

変数を定義

後から使う変数をあらかじめ定義しておきます。
アプリケーション名とパスワードはお好みで決めてください。
以下の説明では、アプリケーション名とAutomationアカウント名は同一にしています。

$ApplicationName = "schevm" #ADアプリケーション名
$ApplicationPassword = "myPassword!" #ADアプリケーションパスワード
$Context = Get-AzureRmContext
$SubscriptionId = $Context.Subscription.SubscriptionId.ToString()
$TenantId = $Context.Tenant.TenantId.ToString()
$ResourceGroupName = "rg01" #リソースグループ名
$AutomationAccountName = "schevm" #Automationアカウント名
$StartRunbookName = "Start-SingleAzureRmVm" #RunBook名
$StopRunbookName = "Stop-SingleAzureRmVm" #RunBook名
$StartScheduleName = "Start Vm" #VM起動スケジュール名
$StopScheduleName = "Stop Vm" #VM停止スケジュール名
$ScheduleParams = @{ResourceGroupName=$ResourceGroupName;VmName="MyVmName"} #RunBook起動時のパラメータ。仮想マシン名を指定します。

ステップ :one: ADアプリケーションを作成

ホームページとIdentifierUriは適当に決めます。後者は一意であればよいはずです。

$NewApp = New-AzureRmADApplication -DisplayName $ApplicationName -Password $ApplicationPassword -HomePage "https://management.azure.com/subscriptions/${SubscriptionId}/resourcegroups/${ResourceGroupName}/providers/Microsoft.Automation/automationAccounts/${AutomationAccountName}"  -IdentifierUris "https://spn/${SubscriptionId}/${ApplicationName}"

ステップ :two: ADサービスプリンシパルを作成

$NewSp = New-AzureRmADServicePrincipal -ApplicationId $NewApp.ApplicationId

ステップ :three: ADサービスプリンシパルにロールを割り当て

リソースの読み取りができるようにReaderと、仮想マシンを起動/停止できるようにVirtual Machine Contributorを割り当てました。

New-AzureRmRoleAssignment -ServicePrincipalName $NewSp.ServicePrincipalName -RoleDefinitionName "Reader" -ResourceGroupName $ResourceGroupName
New-AzureRmRoleAssignment -ServicePrincipalName $NewSp.ServicePrincipalName -RoleDefinitionName "Virtual Machine Contributor" -ResourceGroupName $ResourceGroupName

ステップ :four: Automationアカウントを作成

New-AzureRmAutomationAccount -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Location "Japan East"

ステップ :five: Automationアカウントへ資産を作成

Credential資産と、Variable資産を作成します。
後で出てくるRunBookの中で使っています。ここで作成する資産名を変更するときは、RunBook実行時のパラメータもあわせて変更してください。

$PsCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $NewApp.ApplicationId,(ConvertTo-SecureString -AsPlainText -Force $ApplicationPassword)
New-AzureRmAutomationCredential -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name AzureCredential -Value $PsCred
New-AzureRmAutomationVariable -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name "TenantId" -Value $TenantId -Encrypted $false

ステップ :six: RunBookをインポート

起動用PowerShellワークフロー

次の内容を適当な名前.ps1という名前で保存します。

Start-SingleAzureRmVm.ps1
Workflow Start-SingleAzureRmVm {
    Param(
        [parameter(mandatory=$false)]
        [String]
        $CredentialAssetName = "AzureCredential",

        [parameter(mandatory=$false)]
        [String]
        $TenantIdAssetName = "TenantId",

        [parameter(mandatory=$true)]
        [String]
        $ResourceGroupName,

        [parameter(mandatory=$true)]
        [String]
        $VmName,

        [parameter(mandatory=$false)]
        [Boolean]
        $DryRun = $false
    )


    <#
    We do not work on Sunday and Saturday
    #>
    function IsBusinessDay {
        $dow = (Get-Date).DayOfWeek

        !($dow -eq [DayOfWeek]::Sunday -or $dow -eq [DayOfWeek]::Saturday)
    }

    <#
    Login Azure RM
    #>
    function LoginAzureRm {
        param([String]$CredentialAssetName, [String]$TenantIdAssetName)

        $Cred = Get-AutomationPSCredential -Name $CredentialAssetName
        $TenantId = Get-AutomationVariable -Name $TenantIdAssetName

        if(!$Cred) {
            Throw "Could not find an Automation Credential Asset named '${CredentialAssetName}'. Make sure you have created one in this Automation Account."
        }

        Login-AzureRmAccount -ServicePrincipal -Credential $Cred -TenantId $TenantId | Out-Null
    }

    <#
    Returns true if Virtual Machine already started.
    #>
    function IsVmAllocated {
        param([String]$ResourceGroupName, [String]$VmName)

        $Vm = Get-AzureRmVM -Name $VmName -ResourceGroupName $ResourceGroupName -Status -ErrorAction Continue

        if (!$Vm) {
            Throw "Virtual machine '${VmName}' not found in Resource Group '${ResourceGroupName}'."
        }

        $Status = $Vm.Statuses | ?{ $_.Code -eq "PowerState/deallocated" }

        $Status -eq $null
    }

    <#
    Start virtual machine.
    Rertrying 3 times until success.
    #>
    function StartVm {
        param([String]$ResourceGroupName, [String]$VmName)        

        $TryCount = 0

        Do {
            $TryCount++

            Write-Output "Trying to start Virtual Machine '${VmName}', Count=${TryCount}"

            $Result = Start-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName -ErrorAction Continue

            if ($Result.IsSuccessStatusCode) {
                Write-Output "Virtual Machine '${VmName}' started."
            }
            else {
                Write-Output "Virtual Machine '${VmName}' cannot start."
                Start-Sleep -Seconds 15
            }
        } while($TryCount -le 3 -and !$Result.IsSuccessStatusCode)
    }

    ############
    # Main
    ############
    if (IsBusinessDay) {
        LoginAzureRm -CredentialAssetName $CredentialAssetName -TenantIdAssetName $TenantIdAssetName

        if (IsVmAllocated -ResourceGroupName $ResourceGroupName -VmName $VmName) {
            Write-Output "Virtual machine '${VmName}' is already allocated."
        }
        else {
            if ($DryRun) {
                Write-Output "DryRun mode, nothing to do."
            } else {
                StartVm -ResourceGroupName $ResourceGroupName -VmName $VmName
            }
        }
    } else {
        Write-Output "Today is not a business day, nothing to do."
    }
}

停止用PowerShellワークフロー

次の内容を適当な名前.ps1という名前で保存します。

Stop-SingleAzureRmVm.ps1
Workflow Stop-SingleAzureRmVm {
    Param(
        [parameter(mandatory=$false)]
        [String]
        $CredentialAssetName = "AzureCredential",

        [parameter(mandatory=$false)]
        [String]
        $TenantIdAssetName = "TenantId",

        [parameter(mandatory=$true)]
        [String]
        $ResourceGroupName,

        [parameter(mandatory=$true)]
        [String]
        $VmName,

        [parameter(mandatory=$false)]
        [Boolean]
        $DryRun = $false
    )

    <#
    Login Azure RM
    #>
    function LoginAzureRm {
        param([String]$CredentialAssetName, [String]$TenantIdAssetName)

        $Cred = Get-AutomationPSCredential -Name $CredentialAssetName
        $TenantId = Get-AutomationVariable -Name $TenantIdAssetName

        if(!$Cred) {
            Throw "Could not find an Automation Credential Asset named '${CredentialAssetName}'. Make sure you have created one in this Automation Account."
        }

        Login-AzureRmAccount -ServicePrincipal -Credential $Cred -TenantId $TenantId | Out-Null
    }

    <#
    Returns true if Virtual Machine already started.
    #>
    function IsVmAllocated {
        param([String]$ResourceGroupName, [String]$VmName)

        $Vm = Get-AzureRmVM -Name $VmName -ResourceGroupName $ResourceGroupName -Status -ErrorAction Continue

        if (!$Vm) {
            Throw "Virtual machine '${VmName}' not found in Resource Group '${ResourceGroupName}'."
        }

        $Status = $Vm.Statuses | ?{ $_.Code -eq "PowerState/deallocated" }

        $Status -eq $null
    }

    <#
    Start virtual machine.
    Rertrying 3 times until success.
    #>
    function StopVm {
        param([String]$ResourceGroupName, [String]$VmName)        

        $TryCount = 0

        Do {
            $TryCount++

            Write-Output "Trying to stop Virtual Machine '${VmName}', Count=${TryCount}"

            $Result = Stop-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName -Force -ErrorAction Continue

            if ($Result.IsSuccessStatusCode) {
                Write-Output "Virtual Machine '${VmName}' stoped."
            }
            else {
                Write-Output "Virtual Machine '${VmName}' cannot stop."
                Start-Sleep -Seconds 15
            }
        } while($TryCount -le 3 -and !$Result.IsSuccessStatusCode)
    }

    ############
    # Main
    ############
    LoginAzureRm -CredentialAssetName $CredentialAssetName -TenantIdAssetName $TenantIdAssetName

    if (IsVmAllocated -ResourceGroupName $ResourceGroupName -VmName $VmName) {
        if ($DryRun) {
            Write-Output "DryRun mode, nothing to do."
        } else {
            StopVm -ResourceGroupName $ResourceGroupName -VmName $VmName
        }
    }
    else {
        Write-Output "Virtual machine '${VmName}' is not running."
    }
}

PowerShellワークフローをインポート

保存したファイルをインポートします。

Import-AzureRmAutomationRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Path .\Start-SingleAzureRmVm.ps1 -Name $StartRunBookName  -Type PowerShellWorkflow
Import-AzureRmAutomationRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Path .\Stop-SingleAzureRmVm.ps1 -Name $StopRunBookName  -Type PowerShellWorkflow

:bulb: PowerShellワークフローのパラメータ

上記でインポートしたPowerShellワークフローのパラメータです。

パラメータ名 説明 必須 既定値
CredentialAssetName 資格情報を定義した資産名です。 AzureCredential
TenantIdAssetName TenantIdを定義した資産名です。 TenantId
ResourceGroupName リソースグループ名です。
VmName 仮想マシン名です。
DryRun $trueにすると、実際に起動/停止はしません。 $false

:bulb: テスト実行

ここまでくれば、Azureポータル上からテスト実行できます。

[すべてのリソース] > AutoMaitionアカウント名 > [RunBook] > RunBook名 > [テストウィンドウ] と辿ると、テスト実行画面が表示されます。

パラメータを設定して実行してみてください。

test-runbook.JPG

:bulb: ワークフローを編集するときは……

インポート後に編集するときは、Azureポータルからではなくファイルを編集して上書きインポート(-Forceオプションをつける)するほうがよさそうです。
Azureポータルで編集しても、反映されないことがあります。

ステップ :seven: RunBookを発行

実使用できるように発行します。

Publish-AzureRmAutomationRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $StartRunBookName
Publish-AzureRmAutomationRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $StopRunBookName

ステップ :eight: スケジュールを設定

VM起動用スケジュールを作成

9:00に起動することにしました。適当に変更してください。

New-AzureRmAutomationSchedule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $StartScheduleName -StartTime ((Get-Date "9:00").AddDays(1)) -DayInterval 1

VM停止用スケジュールを作成

18:00に停止することにしました。適当に変更してください。

New-AzureRmAutomationSchedule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $StopScheduleName -StartTime ((Get-Date "18:00").AddDays(1)) -DayInterval 1

スケジュールとRunBookをリンク

スケジュールとRunBookをリンクします。
パラメータもここで指定します。

Register-AzureRmAutomationScheduledRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Parameters $ScheduleParams -RunbookName $StartRunbookName -ScheduleName $StartScheduleName
Register-AzureRmAutomationScheduledRunbook -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Parameters $ScheduleParams -RunbookName $StopRunbookName -ScheduleName $StopScheduleName

最後に

Windows 仮想マシンの起動/停止を繰り返すと、レジストリに未使用のネットワークインターフェース情報がたまっていき、不安定になるそうです。

こちらに回避策があります。
https://blogs.technet.microsoft.com/jpaztech/2016/02/18/delete-nic/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
14
Help us understand the problem. What are the problem?