Auto-Shutdown Azure Linux VM after Inactivity

If you develop on Azure, or have jumpbox VMs you know the tears your wallet cries when you accidentally leave your VMs on for a night/weekend/week/month. Faced with this same problem I wrote a simple script to deallocate my Azure VMs (so that I don’t pay for them) if I’m not logged into them.

The Script

The script below has three conditions:

  • Are there no active SSH connections?
  • Has the system been up for at least 10 minutes?
  • Was the last SSH connection over 15 minutes ago?

If those three conditions are true, the VM is deallocated. In this way once I log off of the VM it will shutdown 15 minutes later (in the event I was disconnected accidentally), but provided the OS has been on for at least ten minutes to provide me plenty of time to connect if I’ve just turned the machine on. This script will also properly support multiple users: it will turn off when these conditions are true for all users (ie, the system has been up for sufficiently long and no users have been SSH’d in for 15 minutes).

Start by creating a directory for the script:

mkdir ~/.autoshutdown
cd ~/.autoshutdown
touch autoshutdown.sh
chmod +x autoshutdown.sh

Now open up the autoshutdown.sh file you created in a text editor and paste the following into it:

#!/bin/bash

### SCRIPT CONFIGURATION PARAMETERS
SPNAME="<spNameGUID>"
TENANT="<tenantGUID>"
PASSWORD=`cat pass.txt`

VMNAME=`cat /etc/hostname`
RESOURCEGROUP="${VMNAME}"

SSHTIMEOUT=15
MINSYSUPTIMEMS=600000
TEMPDIR=/tmp/autoshutdown
TEMPFILE="${TEMPDIR}/timetest"
### END SCRIPT CONFIGURATION PARAMETERS

UPTIME=`awk '{print $1*1000}' /proc/uptime`
NUMSSHCONNS=`ps auxwww | grep sshd: | grep -v grep | wc -l`

function dologin {
	azure login -u "${SPNAME}" -p `cat pass.txt` --service-principal --tenant "$TENANT"
}

function dodeallocate {
	echo "Deallocating VM..."
	dologin
	azure vm deallocate $RESOURCEGROUP $VMNAME
	exit
}

while test $# -gt 0
do
    case "$1" in
        --test)
        	echo "Testing azure login..."
        	dologin
        	exit $?
            ;;
        --force)
        	echo "Shutting down vm without testing parameters..."
        	dodeallocate
        	exit $?
            ;;
    esac
    shift
done

# If there are SSH connections, write the tempfile and exit
if [ $NUMSSHCONNS -gt 0 ]; then
	mkdir -p $TEMPDIR
	touch $TEMPFILE
	exit 0
fi

# Check the system uptime, and the time of the last SSH connection
if [ $UPTIME -gt $MINSYSUPTIMEMS ] && test `find ${TEMPFILE} -mmin +${SSHTIMEOUT}`; then
	dodeallocate
	exit $?
fi

You can edit the timing parameters to control how long the system must be up initially, and how long the period of inactivity must be before a shutdown is triggered by editing the variables at the top of the script.

Requirements

The script has one dependency not typically pre-installed: The Azure CLI. To install it, make sure you have a recent version of NodeJS on your system, then run:

sudo npm install -g azure-cli

Once Azure is installed you need to get an authorization key that won’t expire. To do that you’ll need to create an Azure App and Service Principal. This will give you a long-lasting key that won’t expire like if you just login to the cli. First, sign into the Azure CLI on the VM:

# Login to Azure
azure login

Create a key that will act as the password for the service principal:

# Create a service principal password
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1 > ~/.autoshutdown/pass.txt

Next create an Azure AD App for the service principal. Replace --home-page and --identifier-uris with a domain you own. These are oauth parameters, but we won’t use them, so they can be basically anything.

# Create active directory app
azure ad app create --name "autoshutdown" --home-page "https://michaelblouin.ca" --identifier-uris "https://michaelblouin.ca" --password `cat ~/.autoshutdown/pass.txt`

The above command will output a GUID called AppId. Copy that guid into the below command as appGUID

# Create service principal
azure ad sp create <appGUID>

Likewise, this command will output the Object ID of the service principal as Object Id. Copy that and paste it into the command below as spGUID. Also take note of the Service Principal Name which is also a GUID. Then, grant the service principal the permissions to perform startup/shutdown permissions on your VMs using the builtin Azure role Virtual Machine Contributor:

# Assign service principal permissions
azure role assignment create --roleName "Virtual Machine Contributor" --objectId <spGUID>

Get the tenant ID for your subscription:

# Copy tenant id output below
azure account show

Lastly copy the Service Principal Name GUID output when you created the service principal and the Tenant ID output from your azure account, and paste it in autoshutdown.sh as the SPNAME and TENANT variables between quotes like below. You’ll also need to enter the name of the resource group you created the VM in – if you can’t remember do an azure vm list.

...
SPNAME="<spNameGUID>"
TENANT="<tenantGUID>"
...
RESOURCEGROUP="<resourceGroupName>"

Now logout of Azure on the VM (insert your email address in the command):

# Logout
azure logout <userEmailAddress>

Finally, run autoshutdown.sh --test to verify that you have properly configured the script. If that works you can test that the script properly deallocates the vm with autoshutdown.sh --force (Note: naturally this will turn off the VM immediately). Once you’ve confirmed everything works install the script as shown below.

Installation

The last step is to install the script on the system. Run crontab -e and place the following on a new line on your crontab to run the script every minute. (Be sure to crontab -e as the user whose home directory contains the script. If that’s you then don’t sudo).

* * * * * ~/.autoshutdown/autoshutdown.sh

Conclusion

That was easy. Now you have a VM that automatically shuts down when you’re no longer using it. Nice! If this tutorial was helpful, please consider following or tweeting @michaelblouin on Twitter to show your support! If you had any troubles setting things up let me know in a comment.

, , , ,

10 Responses to Auto-Shutdown Azure Linux VM after Inactivity

  1. Mukul Bana May 5, 2016 at 4:35 am #

    what if any service is running in background….
    I was looking for something that when VM is going to shutdown, SOME JOB could DEallocate the VM.

    • Michael Blouin May 14, 2016 at 4:16 am #

      That’s an interesting idea. I’m on vacation for now but I’m sure you could manage that. Maybe I’ll take a look when I get back!

    • Michael Blouin May 28, 2016 at 1:09 pm #

      Hi Mukul,

      After some thought I think you could accomplish an automatic deallocation on shutdown by following the instructions in this blog (minus setting up the cronjob) and then placing the following script in either /etc/rc.shutdown or /etc/rc0.d (depending on which *nix you’re using):

      #!/bin/bash
      /path/to/autoshutdown.sh --force

      This should trigger the script to be run when the system enters the run level that causes shutdown. I haven’t tried this method yet, but I think it should work. Good luck!

      • Craig Hancock January 5, 2017 at 12:50 pm #

        Great article EXTREMELY useful but quick question what is the azure cli option to re-allocate I don’t see that in the documentation so I assume you have to do that manually somehow like re-attach the disks then start.

        • Michael Blouin January 5, 2017 at 1:09 pm #

          Hey Craig,

          You should be able to use the azure vm start command to bring it back up. Data disks, if still attached in the config, should come back.

  2. Taylor Plumer March 27, 2017 at 11:48 pm #

    Hi Michael, think you’d like vmpower.io because you can deallocate VMs when they’re not needed by simply scheduling time slots on a weekly calendar. It also supports utilization based shutdown so your VMs can turn off after low cpu/memory/disk utilization.

    • Michael Blouin March 27, 2017 at 11:58 pm #

      That’s pretty neat, hadn’t heard of it. For this particular application I like not needing a separate service to manage (and give the keys to the kingdom :P), but it looks like they really nail their core scenarios.

  3. ChrisTimperley April 24, 2017 at 8:47 pm #

    This is neat. I don’t suppose there’s any way to deallocate the entire resource group within the same script?

    • Michael Blouin April 26, 2017 at 12:09 pm #

      Hey Chris,
      The current version can’t but if you wanted to turn off the whole RG you would just have to list the resources in the group and filter to VMs, then ensure that the current VM is the last to turn off. Shouldn’t be too hard using the script as a baseline.

      • ChrisTimperley April 26, 2017 at 12:15 pm #

        That’s a nice idea. For my particular use case (running experiments), I ended up copying my Azure credentials (~/.azure) to the VM, allowing me to invoke az group delete.

Leave a Reply to Michael Blouin Cancel reply

Proudly made in Canada