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.
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.
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!
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!
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.
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.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.
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.
This is neat. I don’t suppose there’s any way to deallocate the entire resource group within the same script?
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.
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
.