Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

Azure runbook 예제 - 6시간 동안 수행 중인 VM을 중지

지난 글의 runbook 스크립트와 그 제어에 이어서,

Azure runbook을 PowerShell 또는 C# 코드로 실행하는 방법
; https://www.sysnet.pe.kr/2/0/11516

보통 runbook에서 어떤 동작을 구현하고 싶다면 Azure Portal의 Cloud Shell을 통해 Azure 명령어를 실행해 보고 나서 옮기는 것이 편합니다. 가령 Azure의 VM 하나를 멈추게 만들고 싶은 경우 아래와 같이 테스트해 볼 수 있습니다.

PS Azure:\> Stop-AzureRmVM -Name "vm1" -ResourceGroupName "myservice2" -Force

명령어 수행이 잘 되는 것을 확인했으면 이제 적절하게 인자를 골라내 다음과 같이 runbook 스크립트를 만듭니다.

# runbook - stop_vm

param (
    [Parameter(Mandatory=$true)][object]$targetVM
)

Stop-AzureRmVM -Name $targetVM.VMName -ResourceGroupName "myservice2" -Force

그런데, 위의 스크립트는 다음과 같은 오류가 발생합니다.

Stop-AzureRmVM : Run Login-AzureRmAccount to login.
At line:7 char:1
+ Stop-AzureRmVM -Name $targetVM.VMName -ResourceGroupName "myservice2" -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Stop-AzureRmVM], PSInvalidOperationException
    + FullyQualifiedErrorId : InvalidOperation,Microsoft.Azure.Commands.Compute.StopAzureVMCommand

왜냐하면 Azure runbook 실행 환경 내에서 Stop-AzureRmVM과 같은 Azure Automation 명령어를 사용하려면 실행 문맥에서 인증을 필요로 하기 때문입니다. 의외로 이 방법을 알아내는 것이 좀 힘들었는데요, Automation Accounts를 생성하는 경우 기본 생성된 runbook 중에 "AzureAutomationTutorial"을 통해 알아낼 수 있었습니다. 그래서 다음과 같이 작성하면 됩니다.

param (
    [Parameter(Mandatory=$true)][object]$targetVM
)

$azureConnection = Get-AutomationConnection -Name AzureRunAsConnection

Add-AzureRmAccount -ServicePrincipal -Tenant $azureConnection.TenantID -ApplicationID $azureConnection.ApplicationID -CertificateThumbprint $azureConnection.CertificateThumbprint
    

Stop-AzureRmVM -Name $targetVM.VMName -ResourceGroupName "myservice2" -Force

특이하게 Get-AutomationConnection 명령어의 경우 검색해 봐도 정식 도움말이 (2018-05-09 기준) 없습니다. 아마도 Azure runbook 실행 환경 내에서만 특별하게 제공되는 듯한데 이 때문에 "AzureAutomationTutorial" 예제 스크립트 외에는 달리 참조할만한 곳이 없었습니다.

이렇게 만들어진 runbook을 PowerShell 클라이언트로 다음과 같이 실행할 수 있습니다.

PS C:\WINDOWS\system32> $json = '{ "VMName": "vm1" }' | ConvertFrom-Json

PS C:\WINDOWS\system32> $arg = @{ 'targetVM' = $json }

PS C:\WINDOWS\system32> $RBParams = @{ AutomationAccountName = 'testrunbook'; ResourceGroupName = 'myservice2'; Name = 'stop_vm'; Parameters = $arg }

PS C:\WINDOWS\system32> Start-AzureRmAutomationRunbook @RBParams

C#으로 실행하는 방법은 지난번 글과 다르지 않으며 첨부 파일은 이 글의 예제에 맞게 수정한 것입니다.




이번엔 약간 다른 시나리오를 생각해 보겠습니다. 예를 들어, VM이 6시간 이상 실행 중일 때 중지하도록 만들고 싶다면 어떻게 해야 할까요? 그러려면 우선 VM의 운영 시간을 구해야 하는데 다음의 글에 따라,

Get-AzureRmVM, Our script, Extra credit : Obtain Azure VM uptime
; https://4sysops.com/archives/check-azure-vm-status-with-powershell/

Get-AzureRmVM 명령어를 이용해 알아낼 수 있습니다.

PS C:\WINDOWS\system32> $vmStatus = Get-AzureRmVM -ResourceGroupName "myservice2" -Name "vm1" -Status
WARNING: Get-AzureRmVM: A property of the output of this cmdlet will change in an upcoming breaking change release. The Storag
eAccountType property for a DataDisk will return Standard_LRS and Premium_LRS

PS C:\WINDOWS\system32> $vmStatus

ResourceGroupName    : myservice2
Name                 : vm1
Disks[0]             : 
  Name               : vm1_OsDisk_1_eab77339733f42a29dd77430fccc8a42
  Statuses[0]        : 
    Code             : ProvisioningState/succeeded
    Level            : Info
    DisplayStatus    : Provisioning succeeded
    Time             : 2018-05-09 오전 6:29:53
PlatformFaultDomain  : 0
PlatformUpdateDomain : 0
Statuses[0]          : 
  Code               : ProvisioningState/succeeded
  Level              : Info
  DisplayStatus      : Provisioning succeeded
  Time               : 2018-05-09 오전 6:29:53
Statuses[1]          : 
  Code               : PowerState/deallocated
  Level              : Info
  DisplayStatus      : VM deallocated


PS C:\WINDOWS\system32> $vmStatus.Statuses[1].Code
PowerState/deallocated

현재 위의 상태는 "중지됨(할당 취소됨)"입니다. 다음은 각각의 경우에 대한 Statuses 값을 보여줍니다.

[시작 중]

ResourceGroupName    : myservice2
Name                 : vm1
Disks[0]             : 
  Name               : vm1_OsDisk_1_eab77339733f42a29dd77430fccc8a42
  Statuses[0]        : 
    Code             : ProvisioningState/succeeded
    Level            : Info
    DisplayStatus    : Provisioning succeeded
    Time             : 2018-05-09 오전 7:34:34
PlatformFaultDomain  : 0
PlatformUpdateDomain : 0
Statuses[0]          : 
  Code               : ProvisioningState/updating
  Level              : Info
  DisplayStatus      : Updating
Statuses[1]          : 
  Code               : PowerState/starting
  Level              : Info
  DisplayStatus      : VM starting

[실행 중]

Name                 : vm1
Disks[0]             : 
  Name               : vm1_OsDisk_1_eab77339733f42a29dd77430fccc8a42
  Statuses[0]        : 
    Code             : ProvisioningState/succeeded
    Level            : Info
    DisplayStatus    : Provisioning succeeded
    Time             : 2018-05-09 오전 7:34:34
PlatformFaultDomain  : 0
PlatformUpdateDomain : 0
VMAgent              : 
  VmAgentVersion     : Unknown
  Statuses[0]        : 
    Code             : ProvisioningState/Unavailable
    Level            : Warning
    DisplayStatus    : Not Ready
    Message          : VM status blob is found but not yet populated.
    Time             : 2018-05-09 오전 7:35:39
Statuses[0]          : 
  Code               : ProvisioningState/succeeded
  Level              : Info
  DisplayStatus      : Provisioning succeeded
  Time               : 2018-05-09 오전 7:35:31
Statuses[1]          : 
  Code               : PowerState/running
  Level              : Info
  DisplayStatus      : VM running

[할당을 취소하는 중]

ResourceGroupName    : myservice2
Name                 : vm1
Disks[0]             : 
  Name               : vm1_OsDisk_1_eab77339733f42a29dd77430fccc8a42
  Statuses[0]        : 
    Code             : ProvisioningState/succeeded
    Level            : Info
    DisplayStatus    : Provisioning succeeded
    Time             : 2018-05-09 오전 7:34:34
PlatformFaultDomain  : 0
PlatformUpdateDomain : 0
VMAgent              : 
  VmAgentVersion     : 2.7.41491.875
  Statuses[0]        : 
    Code             : ProvisioningState/succeeded
    Level            : Info
    DisplayStatus    : Ready
    Message          : GuestAgent is running and accepting new configurations.
    Time             : 2018-05-09 오전 7:41:29
Statuses[0]          : 
  Code               : ProvisioningState/updating
  Level              : Info
  DisplayStatus      : Updating
Statuses[1]          : 
  Code               : PowerState/deallocating
  Level              : Info
  DisplayStatus      : VM deallocating

따라서, Statuses[1].DisplayStatus == "VM running" 상태를 기준으로 Statuses[0].Time을 현재 시간과 비교해 6시간이 지난 경우 VM을 중지하라고 스크립트를 구성하면 됩니다. 다음은 이를 반영해 stop_vm 스크립트를 작성한 것입니다.

param ([Parameter(Mandatory=$true)][object]$targetVM)

$azureConnection = Get-AutomationConnection -Name AzureRunAsConnection

Add-AzureRmAccount -ServicePrincipal -Tenant $azureConnection.TenantID `
    -ApplicationID $azureConnection.ApplicationID -CertificateThumbprint $azureConnection.CertificateThumbprint

$vmStatus = Get-AzureRmVM -ResourceGroupName "myservice2" -Name $targetVM.VMName -Status

if ($vmStatus.Statuses[1].DisplayStatus -eq "VM running")
{
    Write-Output "VM: Running"

    $uptime = New-TimeSpan -Start $vmStatus.Statuses[0].Time -End (Get-Date)
    if ($uptime.TotalHours -gt 6)
    {
        Write-Output "VM update: {$uptime.TotalHours}"
        Stop-AzureRmVM -Name $targetVM.VMName -ResourceGroupName "myservice2" -Force
    }
    else
    {
        Write-Output "VM update: {$uptime.TotalHours}"
    }
}
else
{
    Write-Output "VM: Not running"
}




6시간마다 중지하도록 stop_vm runbook도 완성했는데, 이 스크립트를 사용자가 일일이 시간마다 실행해 줄 수는 없습니다. ^^ 당연히 자동화해야 할 텐데요, 이를 위해 runbook에서는 다음과 같이 "schedule(일정)" 기능을 제공합니다.

stop_vm_runbook_1.png

특정 시간에 한 번만 실행하는 것도 가능하지만, 이 글의 시나리오를 위해선 되풀이하도록 설정하는 것이 현실적입니다. 따라서 다음과 같이 "1시간 간격"으로 되풀이하도록 일정을 만들 수 있습니다.

stop_vm_runbook_2.png

현재 Azure에서 설정 가능한 되풀이 단위는 "시간, 일, 주, 월"이며 따라서 "1 시간"이 최소 간격입니다. 만약 다른 시간 간격을 사용하고 싶다면 Webhook으로 노출시켜 원하는 간격으로 직접 호출하는 스케줄링을 하면 됩니다. 시간 간격 후에는, 이 글에서 실습하는 stop_vm runbook은 매개 변수도 필요하기 때문에 다음과 같이 설정해 주어야 합니다. (참고로 아래와 같이 Azure Portal UI를 통해 json 유형의 값으로 매개 변수를 채우면 schedule로 인한 호출 시 오류가 발생합니다. 이유는 나중에 설명합니다.)

stop_vm_runbook_3.png

이후부터는, 설정된 시간 간격과 매개 변수로 runbook이 자동으로 실행됩니다.

당연히 이런 일정 등록 작업도 PowerShell이나 C#으로 가능합니다. 그런데, 위에서처럼 runbook에 schedule을 할당하는 방법은 없고 "Automation Account" 계정 수준에서 schedule을 만든 다음,

# Automation Account 내에 schedule 생성

$StartTime = Get-Date "10:25:00"
New-AzureRmAutomationSchedule -AutomationAccountName "testrunbook" -Name "Schedule01" -StartTime $StartTime -HourInterval 1  -ResourceGroupName "myservice2"

이것을 개별 runbook에 연결하는 방법으로 등록할 수 있도록 하고 있습니다.

# Automation Account 내에 생성해 두었던 schedule과 runbook을 연결

$vmParams = '{ "VMName": "vm1" }' | ConvertFrom-Json
$runbookParams = @{"targetVM"=$vmParams}
Register-AzureRmAutomationScheduledRunbook -AutomationAccountName "testrunbook" -RunbookName "stop_vm" -ScheduleName "Schedule01" -ResourceGroupName "myservice2" -Parameters $runbookParams

어찌 보면 schedule을 재사용할 수 있도록 해주므로 좋은 방법이긴 합니다. (아마도 runbook 단위로 생성하는 방법도 있겠지만, 제가 방법을 못 찾은 걸 수도 있습니다.)

참고로, Automation Account 단위로 재사용할 수 있는 변수도 추가할 수 있는데 다음과 같이 생성합니다.

$vmParams = '{ "VMName": "vm1" }' | ConvertFrom-Json
New-AzureRmAutomationVariable -AutomationAccountName "testrunbook" -Name "targetVM" -Encrypted $False -Value $vmParams -ResourceGroupName "myservice2"




이 외에도, VM의 성능 메트릭(Metrics)에 따라 WebHook이나 내부의 runbook을 호출하도록 설정하는 것이 가능합니다. 이에 대해서는 다음의 글에서 설명하고 있는데, 지금까지의 내용을 이해하셨다면 한번 훑어보기만 해도 금방 이해가 되실 것입니다.

Using Azure Automation to take action on Azure Alerts
; https://azure.microsoft.com/en-us/blog/using-azure-automation-to-take-actions-on-azure-alerts/




Stop-AzureRmVM 명령어는 기본적으로 명령어 실행 모드가 동기 방식입니다. 따라서, 대상 VM이 Azure에서 중지될 때까지 기다리게 되는데 이 시간이 꽤 깁니다. 따라서 여러 개의 VM을 중지하는 경우에는 비동기로 실행하는 것을 권장하며, 이를 위해 명령어에 -AsJob 옵션을 주면 됩니다.

Stop-AzureRmVM -Name "vm1" -ResourceGroupName "myservice2" -Force -AsJob

그런데 문제는, Azure runbook 실행 환경에서는 Stop-AzureRmVM에 대해 AsJob 옵션을 허용하지 않아 다음과 같은 오류가 발생합니다.

Stop-AzureRmVM : A parameter cannot be found that matches parameter name 'AsJob'.
At line:12 char:16
+ Stop-AzureRmVM -AsJob -Name "vm1" -ResourceGroupName ...
+                ~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Stop-AzureRmVM], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.Azure.Commands.Compute.StopAzureVMCommand

그래서, 만약 여러 개의 VM을 제어하고 싶다면 runbook 스크립트 내에서 for-each를 통해 VM을 제어해서는 안 되고 runbook을 실행하는 측에서 해당 runbook을 제어해야 할 VM의 이름을 넘겨, 이를 여러 번 호출하는 방식으로 구현해야 합니다.




일정 등록을 할 때, 다음과 같은 오류가 발생할 수 있습니다.

PS C:\WINDOWS\system32> New-AzureRmAutomationSchedule -AutomationAccountName "testrunbook" -Name "Schedule01" -StartTime $StartTime -HourInterval 1  -ResourceGroupName "myservice2"
New-AzureRmAutomationSchedule : BadRequest: Argument requestScheduleData with value Orchestrator.Schedules.DataAccess.Models.ScheduleAllData is not valid. Error message: The start time of the schedule must be at least 5 minutes after the time you create the schedule. 
At line:1 char:1
+ New-AzureRmAutomationSchedule -AutomationAccountName "testrunbook" -N ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (:) [New-AzureRmAutomationSchedule], CloudException
    + FullyQualifiedErrorId : Microsoft.Azure.Commands.Automation.Cmdlet.NewAzureAutomationSchedule

말 그대로, -StartTime의 인자로 전달한 시간이 현재 시간으로부터 5분 이내로 설정된 경우이기 때문입니다. 따라서, 다음과 같은 식으로 생성하면 안전하게 등록할 수 있습니다. (참고로, region에 따라 시간이 다릅니다. 제가 테스트 한 바로는 15분인 경우도 있었습니다.)

$StartTime = Get-Date
$StartTime = $StartTime.AddMinutes(6)
New-AzureRmAutomationSchedule -AutomationAccountName "testrunbook" -Name "Schedule01" -StartTime $StartTime -HourInterval 1  -ResourceGroupName "myservice2"




이 글을 실습하는 중에 다음과 같은 runbook 실행 오류 로그가 남을 수 있습니다.

Get-AzureRmVM : Cannot validate argument on parameter 'Name'. The argument is null or empty. Provide an argument that 
is not null or empty, and then try the command again.
At line:12 char:65
+ ... reRmVM -ResourceGroupName "myservice2" -Name $targetVM.VMName -Status
+                                                  ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Get-AzureRmVM], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Azure.Commands.Compute.GetAzureVMCommand

말 그대로, runbook의 $targetVM에 전달하는 인자가 정상적으로 역직렬화가 안 된 것입니다. 이 부분이 좀 이상한데요. runbook에 등록하는 일정의 매개 변수를 다음과 같이 설정하면,

stop_vm_runbook_3.png

{ "VMName": "vm1" }

생성 후 다시 매개 변수를 확인했을 때 다음과 같이 출력됩니다.

{ \"VMName\": \"vm1\" }

재미있는 것은, 위와 같이 Azure Portal의 UI를 이용하지 않고 직접 프로그래밍을 통해 설정하는 경우에는 매개 변숫값이 겹따옴표에 대한 escape 문자 표현을 포함하고 있지 않다는 점입니다. 따라서 complex type에 대해 schedule의 매개 변수를 설정하려면 다음과 같이 코드로 설정해야 합니다.

# Automation Account 내에 생성해 두었던 schedule과 runbook을 연결

$vmParams = '{ "VMName": "vm1" }' | ConvertFrom-Json
$runbookParams = @{"targetVM"=$vmParams}
Register-AzureRmAutomationScheduledRunbook -AutomationAccountName "testrunbook" -RunbookName "stop_vm" -ScheduleName "Schedule01" -ResourceGroupName "myservice2" -Parameters $runbookParams

위와 같이 해서 scheduler를 생성하면 매개 변수를 확인했을 때 정상적으로 { "VMName": "vm1" }라고 나옵니다. 참고로, Azure portal UI 화면에서 다음과 같은 설정으로 입력했을 때 모두 실패했습니다.

{ 'VMName': 'vm1' }

{ ""VMName"": ""vm1"" }

{ VMName: vm1 }

{ "VMName": "vm1" }

아마도 azure의 입력 버그가 아닌가 싶은데... 두고 봐야겠습니다. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 5/10/2018 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 



2018-06-27 02시49분
이 글의 마지막 실습에서 { "VMName": "vm1" } 텍스트를 설정했는데도 오류가 나는 것은 해당 스크립트의 param 인자 타입을 다음과 같이 object에서 PSObject로 바꿔주면 해결됩니다.

param (
    [Parameter(Mandatory=$true)][PSObject]$user
)

관련해서 다음의 Form에서 질문해 답변을 들었습니다. ^^

How to pass complex typed argument in Azure runbook schedule's parameter?
; https://social.msdn.microsoft.com/Forums/azure/en-US/5d911ba5-6b7e-4182-9873-35c37b7012b7/how-to-pass-complex-typed-argument-in-azure-runbook-schedules-parameter?forum=azureautomation
정성태

... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
11660정성태9/4/20182367사물인터넷: 33. 세라믹 커패시터의 동작 방식파일 다운로드1
11659정성태8/19/20182535사물인터넷: 32. 9V 전압에서 테스트하는 PN2222A 트랜지스터파일 다운로드1
11658정성태8/18/20184222사물인터넷: 31. 커패시터와 RC 회로파일 다운로드3
11657정성태8/21/20183172사물인터넷: 30. 릴레이(Relay) 제어파일 다운로드3
11656정성태8/18/20181992사물인터넷: 29. 트랜지스터와 병렬로 연결한 LED파일 다운로드1
11655정성태8/18/20183307사물인터넷: 28. 저항과 병렬로 연결한 LED파일 다운로드1
11654정성태8/18/20182416사물인터넷: 27. 병렬 회로의 저항, 전압 및 전류파일 다운로드1
11653정성태8/18/20182262사물인터넷: 26. 입력 전압에 따른 LED의 전압/저항 변화파일 다운로드1
11652정성태9/11/20182111사물인터넷: 25. 컬렉터 9V, 베이스에 5V와 3.3V 전압으로 테스트하는 C1815 트랜지스터파일 다운로드1
11651정성태9/4/20184229사물인터넷: 24. 9V 전압에서 테스트하는 C1815 트랜지스터파일 다운로드3
11650정성태8/18/20182277사물인터넷: 23. 가변저항으로 분압파일 다운로드1
11649정성태8/14/20182529사물인터넷: 22. 저항에 따른 전류 테스트파일 다운로드1
11648정성태10/17/20183318사물인터넷: 21. 퓨즈를 이용한 회로 보호파일 다운로드3
11647정성태8/8/20182509오류 유형: 476. 음수의 음수는 여전히 음수가 되는 수(절대값이 음수인 수)
11646정성태8/8/20182199오류 유형: 475. gacutil.exe 실행 시 "Failure initializing gacutil" 오류 발생
11645정성태8/8/20182723오류 유형: 474. 닷넷 COM+ - Failed to load the runtime. [1]
11644정성태8/8/20183395디버깅 기술: 118. windbg - 닷넷 개발자를 위한 MEX Debugging Extension 소개
11643정성태8/12/20183612사물인터넷: 20. 아두이노 레오나르도 R3 호환 보드의 3.3v 핀의 LED 전압/전류 테스트 [1]파일 다운로드1
11642정성태8/3/20183612Graphics: 20. Unity - LightMode의 ForwardBase에 따른 _WorldSpaceLightPos0 값 변화
11641정성태8/3/20185117Graphics: 19. Unity로 실습하는 Shader (10) - 빌보드 구현파일 다운로드1
11640정성태8/3/20183655Graphics: 18. Unity - World matrix(unity_ObjectToWorld)로부터 Position, Rotation, Scale 값을 복원하는 방법파일 다운로드1
11639정성태8/2/20182937디버깅 기술: 117. windbg - 덤프 파일로부터 추출한 DLL을 참조하는 방법
11638정성태8/2/20182305오류 유형: 473. windbg - 덤프 파일로부터 추출한 DLL 참조 시 "Resolved file has a bad image, no metadata, or is otherwise inaccessible." 빌드 오류
11637정성태8/1/20183400Graphics: 17. Unity - World matrix(unity_ObjectToWorld)로부터 TRS(이동/회전/크기) 행렬로 복원하는 방법파일 다운로드1
11636정성태8/1/20186012Graphics: 16. 3D 공간에서 두 점이 이루는 각도 구하기파일 다운로드1
11635정성태8/1/20182773오류 유형: 472. C# 컴파일 오류 - Your project is not referencing the ".NETFramework,Version=v3.5" framework.
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...