automated shutdown
TRANSCRIPT
Controlling costs using automated shutdown
Mario Berend [email protected]
Azure provides us with some great features when it comes to the flexibility of our virtual datacenter.
For example: let’s say you are managing the public website for a popular clothing brand, and they are
about to introduce their summer collection with a big on- and offline marketing campaign. This
marketing campaign will, obviously, cause a huge increase in web traffic. This scenario will be a
challenge in an on-premise environment. You will probably have to set up one or more extra VM’s to
deal with the extra traffic. Things will get more complicated if your Hyper-V server is running out of
resources… Azure fans will immediately say “autoscaling”. And yes, autoscaling is the answer for
easily adding some temporary muscle to your environment.
So, we are covered for dealing with peak performance moments. But I’ve noticed that the other side
of the spectrum is usually completely forgotten, the moments that we are not using our environment
at all. Let’s say you have a private Azure subscription with some VM’s in a VNet, using it on a regular
basis. I bet that these VM’s will remain powered on during those moments you don’t use them, for
example overnight and during the weekends… Is it not just a waste of money to have these machines
run for no reason, eating our credit cards?
Fortunately, this is where Azure Runbooks and the Scheduler can be of help. Let’s say we want to
have our VM’s powered on only from Monday till Friday, from 8AM till 8PM. First, we have to create
an automation account. We can do this through the portal, I am placing my automation account in
the resource group where my VM’s are located.
This is also possible through Powershell, but it will require quite a lot of scripting to have the RunAs
account created properly. We will skip that for now.
So now that we have our automation account, we can proceed with creating a new runbook. We can
do this through the portal by clicking on our automation account, click Runbooks, and Add a runbook.
Now we will create a new runbook either by using the portal:
Or with Powershell:
New-AzureRmAutomationRunbook -AutomationAccountName AutomationAccount ` -Name TurnOffVMs -ResourceGroupName Avanade_Resourcegroup_Prod -Type PowerShell ` -Description "This runbook contains a script to shut down VM's."
Which will produce the following output:
Now that we have our runbook, it is time to add some scripting. Open the runbook and click Edit. We
can now work on our script:
Let’s have a look at what this script does. It will first set up a connection with our Automation
Account which is required to fire the commands we want to execute within our runbook. Once the
authentication is successful, we can perform our actual actions. Let’s save this script, and go to the
Test pane. Now press the Start button and wait for the script to complete. It will show an output of
all VM’s and their properties which are located in our tenant.
We can also use Powershell to create a runbook from an existing .ps1 file. This is similar to the earlier
creation of our runbook, but this time we are creating the runbook and injecting our script in one
step.
Import-AzureRmAutomationRunbook -ResourceGroupName AVANADE_RESOURCEGROUP_PROD ` -AutomationAccountName AutomationAccount -Name TurnOffVms ` -Path ".\TurnOffVMs_pre.ps1" -Type PowerShell ` -Description "This runbook contains a script to shut down VM's."
You will have to delete the previously created runbook if you want to try the above example. You can
delete a runbook with the following command:
Remove-AzureRmAutomationRunbook -Name TurnOffVMs -ResourceGroupName AVANADE_RESOURCEGROUP_PROD -AutomationAccountName AutomationAccount
Now that we have a runbook with a script, we need to schedule execution. If you are using the portal
you might have noticed that the Schedule button is greyed out. This is because we haven’t published
our script yet. Press the Publish button in the portal, or run the following Powershell command:
Publish-AzureRmAutomationRunbook -AutomationAccountName AutomationAccount ` -Name TurnOffVMs -ResourceGroupName AVANADE_RESOURCEGROUP_PROD
This will produce the following output:
You will see that the State attribute is now set to Published, so we can now proceed with the
creation of our schedule. Click on Schedule, Link a schedule to your runbook, and then on Create a
new schedule.
The Powershell command associated with this action is as follow:
New-AzureRmAutomationSchedule –AutomationAccountName AutomationAccount ` –Name TurnVMsOffDaily –StartTime "12/1/2017 16:30:00" ` –DayInterval 1 -ResourceGroupName AVANADE_RESOURCEGROUP_PROD ` -Description "This schedule will turn off all VM's in our tenant on a daily basis."
You will see the following output:
Now link the schedule to the runbook:
Register-AzurermAutomationScheduledRunbook –AutomationAccountName ` AutomationAccount –Name TurnOffVMs –ScheduleName TurnVMsOffDaily ` -ResourceGroupName AVANADE_RESOURCEGROUP_PROD
When creating a schedule, the start time of the schedule must be at least 5 minutes after the time
you create the schedule. So check your current time, add a few minutes to it, and set that as the
Starttime of your schedule. Grab a coffee and wait for the schedule to kick in.
Open the runbook, and open the Jobs tab. You will see the job status listed as Completed. Open the
job to have a closer look.
No errors, no warnings, this looks good. You can check the Output button to see the result of the
script that was fired by our schedule.
So now we have our automation account, a runbook with some sample code, and a working
scheduled job attached to it. Time to customize the runbook with some code to implement the
automated shutdown. Open the runbook and press edit. Our current code is currently capturing all
VM’s in our tenant and stores them in a variable, and then outputs this variable. We will remove the
$VMs output and create a foreach-loop to iterate through every VM in the $VMs variable. We will
shut down each VM during this loop to perform our shutdown using the following command:
Stop-AzureRmVM -Name $vm.Name ` -ResourceGroupName $vm.ResourceGroupName ` -force
Save and publish your updated script version. This will overwrite our previous version. Now set the
schedule to a new time, and wait for the script to be fired. It will take a while to run. Once
completed, the output will be similar to this:
Cool! All VM’s in our tenant are now in a stopped state, which will save a noticeable amount of
credits each month. Let’s say you set you create a runbook and schedule to shut down all of your
VM’s each evening at 11PM, and start them again at 7AM. This means you cut on 1/3 of the costs of
the running VM’s! Keep in mind though, any dynamically assigned IP address (public or private) will
be released when this script is performed.
Now that we created a script to shut down our VM’s every evening, we also need a script to start the
VM’s each morning. Create a new runbook, add a recurring schedule that fires every morning, and
copy the script from our Shutdown script. We only have to make some slight adaptions to let it start
our VM’s instead of stop:
There we go, we are shutting down and powering on our VM’s without having to do anything for it.
This scenario however, is not giving us much control over which VM’s are shut down. You might want
to shut down only specific VM’s. Good news, we can achieve that with a little customization.
I once faced the scenario that the development team of the project I was assigned to had a VNet
running in Azure, divided into two subnets: one for development/testing purposes, and one for
production. The development machines were only necessary during production hours, while the
production server was required 24/7. So basically what was required in this scenario was the
following:
- Determine which subnet that contains the VMs that need to be shutdown
- Get all interfaces connected to the VNet. Each VM had one NIC connected to a subnet.
- Check which subnets these NICs are connected to
- If the subnet of the NIC matches the subnet that needs to be shutdown, the VMname needs
to be retrieved and eventually the VM needs to shutdown.
This brought me to the following script:
Let’s test it out. Open the Test Pane, start it, and wait for it to complete. Note: at the time of writing,
the modules containing the cmdlets used in this script aren’t natively imported into Azure's
automation accounts. You will have to manually import AzureRM.Network and AzureRM.profile. If
you have any issues firing the script above, please read my article “Powershell modules in runbooks”.
Now check the status of your VMs:
Voila! Both of the VMs in the DevAndTest subnet are stopped, while the VM in Production is still
running. Exactly what we wanted!
That’s it for now! With this article I hope to give you a view on the power of Azure’s runbooks and
how you can use them to control the costs of your virtual data center, or how you can easily save
your Azure credits in your personal tenant.
Happy scripting!
Mario Berend [email protected] | https://www.linkedin.com/in/mario-berend-7b214228
Used scripts:
Creation of a new runbook:
New-AzureRmAutomationRunbook -AutomationAccountName AutomationAccount ` -Name TurnOffVMs -ResourceGroupName Avanade_Resourcegroup_Prod -Type PowerShell ` -Description "This runbook contains a script to shut down VM's."
Connecting with the automation account and retrieval of VM’s:
$connectionName = "AzureRunAsConnection" try { $Conn=Get-AutomationConnection -Name $ConnectionName "Logging in to Azure..." Add-AzureRmAccount ` -ServicePrincipal ` -TenantId $Conn.TenantId ` -ApplicationId $Conn.ApplicationId ` -CertificateThumbprint $Conn.CertificateThumbprint } catch { Write-host "Error while creating connection!" } $VMs = get-azurermvm $VMs
Connecting with the automation account and stopping the VM’s:
$connectionName = "AzureRunAsConnection" try { $Conn=Get-AutomationConnection -Name $ConnectionName "Logging in to Azure..." Add-AzureRmAccount ` -ServicePrincipal ` -TenantId $Conn.TenantId ` -ApplicationId $Conn.ApplicationId ` -CertificateThumbprint $Conn.CertificateThumbprint } catch { Write-host "Error while creating connection!" } foreach($vm in $vms) { Stop-AzureRmVM -Name $vm.Name ` -ResourceGroupName $vm.ResourceGroupName ` -force }
Connecting with the automation account and starting the VM’s:
$connectionName = "AzureRunAsConnection" try { $Conn=Get-AutomationConnection -Name $ConnectionName "Logging in to Azure..." Add-AzureRmAccount ` -ServicePrincipal ` -TenantId $Conn.TenantId ` -ApplicationId $Conn.ApplicationId ` -CertificateThumbprint $Conn.CertificateThumbprint } catch { Write-host "Error while creating connection!" } foreach($vm in $vms) { Start-AzureRmVM -Name $vm.Name ` -ResourceGroupName $vm.ResourceGroupName ` }
Connecting with the automation account and stopping the VM’s in a specified VNet:
$connectionName = "AzureRunAsConnection" try { $Conn=Get-AutomationConnection -Name $ConnectionName "Logging in to Azure..." Add-AzureRmAccount ` -ServicePrincipal ` -TenantId $Conn.TenantId ` -ApplicationId $Conn.ApplicationId ` -CertificateThumbprint $Conn.CertificateThumbprint } catch { Write-host "Error while creating connection!" } $SubnetIDToShutDown = "*DevAndTest*" $interfaces = Get-AzureRmNetworkInterface | select name, IpConfigurations, VirtualMachine, ResourceGroupName foreach($interface in $interfaces) { $ResourceGroupName = $interface.ResourceGroupName $SubnetID = $interface.IpConfigurations.Subnet.id if($SubnetID -like $SubnetIDToShutDown) { $VMName = ($interface.VirtualMachine.Id.Split("/"))[-1] stop-AzureRMVM -Name $VMName -ResourceGroupName $ResourceGroupName -force } }