Automatically Stop Azure Resource Manager VMs

A question that I get fairly often is how to automatically stop virtual machines in Azure so that they get deallocated and practically does not cost anything. There are many ways to do this, and in this blog post, I am going to describe how to do it with Azure Automation Runbooks.

The virtual machines that are best suited to automate starting and stopping of, are the ones that does not need to give a response quickly. Test environments, machines with background worker roles, and build infrastructure are some good examples. Production web servers are not, at least not by automation runbooks. Im sure you get that it is wiser to rely on the autoscaling load balancing for those servers instead.

Different Approaches to Starting Runbooks

Azure has a feature called Automation that can contain runbooks. A runbook is, simply put, a PowerShell script with some extra hosting features such as logging and a platform to run on. Runbooks can be started by schedule, or through a HTTP Post webhook, or if you want to complicate things, by a PowerShell command in another runbook.

When I first started automating starting and stopping Azure resources I used daily repeating schedules because of their simplicity. But that approach has its limitations. The biggest one is that it is hard to predict when a machine is actually needed. For example, if you schedule your virtual machines to be running monday to friday from say 08:00 to 18:00 you will find that the schedule needs to be tinkered with more often that you think. Vacations, illness, and conferences are are just some occasions where you might have to disable your schedules to save some extra credits.

The better way would be to start your virtual machines first when they are really needed. And then when they are no longer used, to stop them as soon as you know this. This type of automation can be archived by starting the runbooks primarily by webhooks, and only use repeated schedules as a safety if your webhook automation would happen to fail.

The possibilities to invoke rest methods are endless. Make your phone call a start VMs webook when it connects to your work WIFI. Make traffic on a web server call a webhook to postpone your stop VMs runbook schedule. You can surely think of other interesting ways.

Delayed Webhook Runbook Startup

Schedules does not need to be repeated. In fact, a schedule which is configured to start a runbook only once is a great tool to delay the startup of that runbook after a webhook has been used.

How to Get Started

In the Azure portal, you find the Automation account under New - Management.

Create Automation Account

To make it possible to reuse PowerShell scripts between automation accounts, It is convenient to link your automation account to a GitHub repository where you can store all your runbook scripts. You can do this by clicking the Set Up Source Control tile, entering your repository details, and then clicking on Sync. This will add a runbook for every PowerShell script, each with the authoring status New. To be able to run one of the imported runbooks, you have to publish it. You can do that by clicking it, then Edit - Publish.

Automation Account

Each automation account has a number of modules by default, which can be found under Assets - Modules. If your PowerShell scripts depend on another module, you can upload it there.

To make it possible to start a runbook, you click it and then click either Schedule or Webhook. You can then configure any parameters that your PowerShell script needs in that schedule or webhook instance.

A Practical Example

In my Visual Studio Team Services (VSTS) account, I have a project called Akkomation which I use to build a home automation software that I store the source code for on GitHub (more about that in a later blog post).

Each time I check in, a build is triggered in VSTS which runs the tests, and performs analysis on a SonarQube server which I host in Azure.

Since I am the only contributor to the project code is pushed rather seldom, so it does not make sense to have the SonarQube server up and running 24/7. When I do push, I am often interested to see analysis results such as code coverage and code analysis issues.

Build Workflow

The build workflow is to first check if the SonarQube server is running, and if not, start it up with a webhook. VSTS includes an Azure Resource Group Deployment task which can start Azure Resource Manager virtual machines. A benefit of starting the SonarQube server with a webhook is that the build can continue while it is booting up.

Just before the SonarQube analysis begins, the build waits for the service endpoint to come up. Then when the analysis is complete, an runbook which stops the virtual machine is scheduled to start three hours later.

If another build is triggered before then, the SonarQube server is reused, and the stop runbook is rescheduled to run three hours after that the second build completes.

Akkomation Build Workflow

This workflow use two custom tasks:

  • Inline PowerShell, which runs a PowerShell script that is entered in the task instead of running a ps1-file as the normal PowerShell task does. I do not like to check in files in a repo just to make a build workflow possible. At least not in a small project like this one.
  • Invoke Rest Method, which calls a rest method. Surprise! What it also does is that it tries to invoke it once every ten seconds until it succeeds, up until a configurable timeout.

You can download both tasks at my vso-agent-tasks repository on GitHub.

The PowerShell script of the Start SonarQube VM if Not Running task is a simple one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Write-Output "Resetting Stop SonarQube VM runbook schedule."
Invoke-RestMethod -Method Post -Uri "https://s2events.azure-automation.net/webhooks?token=**********" `
-Verbose | Out-Null
try {
Write-Output "Trying to call SonarQube service..."
Invoke-RestMethod -Method Get -Uri "http://johanclasson-sonarqube.westeurope.cloudapp.azure.com:9000" `
-Verbose | Out-Null
Write-Output "Found it!"
}
catch {
Write-Output "Nobody seems to be at home... ($_)"
Write-Output "Starting up the virtual machine."
Invoke-RestMethod -Method Post -Uri "https://s2events.azure-automation.net/webhooks?token=**********" `
-Verbose | Out-Null
}

I hope I have inspired you to do automation of starting and stopping your VMs a little smarter. Best of luck with that!