Stop and start instances at scheduled intervals across AWS accounts and regions

Analía Lorenzatto
etermax technology
Published in
4 min readFeb 9, 2018

One of the Best Practices recommended by AWS guys is stopping and starting instances at predefined times to avoid incurring unnecessary charges.
In line with it, we’ve been challenged to implement this solution going through different approaches while company was growing: from having one AWS account to many.

Solution overwiew

We are going to explain briefly each solution from the beginning up to our current implementation.

First solution: automate the starting/stopping tasks in just ONE AWS account.

At the very beginning, we had just one AWS account. So putting a list of instances’s name to start/stop in a file and then called it by following scripts worked for us, for a long time in fact! :)

$ crontab -l# Stop / start instances
00 9 * * mon-fri /usr/local/aws_scripts/start_instances.sh
00 18 * * mon-fri /usr/local/aws_scripts/stop_instances.sh

Second solution: we had TWO AWS accounts, time to change the approach..

With another account, the previous scripts were not enough to cover our need of stopping/starting instances properly.

It was clear, that was not a long-term scalable solution. So we decided changing the approach: instead of using files with instances’s names, we had to use Tags. Adding a Tag to the instances that needed to be started, and another for those to be stopped. The idea gas great at that moment, since we could get ride of many files of instances’s names.

At this point, we had two Crontab entries per AWS account.

$ crontab -l# Start instances
00 9 * * mon-fri /usr/local/aws_scripts/start_instances_staging_account.sh
00 9 * * mon-fri /usr/local/aws_scripts/start_instances_test_account.sh
# Stop instances
00 18 * * mon-fri /usr/local/aws_scripts/stop_instances_staging_account.sh
00 18 * * mon-fri /usr/local/aws_scripts/stop_instances_test_account.sh

Our last and current solution

While we were comfortable with our second solution, many AWS accounts started being incorporated to the company. Again.. we had to re-think our solution to a one more scalable. We came up with the idea of replacing Bash by Ruby, since it would give us the chance of using aws-sdk (AWS Api) to authenticate and start / stop instances across many AWS accounts and regions. This is our current solution.

  1. Create Crontab entries.
$ crontab -l#-------------#
# Daily Tasks #
#-------------#
57 00 * * mon-fri ruby /usr/local/aws_tasks/stop_instances.rb
45 10 * * mon-fri ruby /usr/local/aws_tasks/start_instances.rb

2. Install Ruby and aws-sdk gem.

apt-get update && apt-get install ruby rubygems-integration && gem install aws-sdk && cd /usr/local/aws_tasks

3. Set AWS credentials for each account / region to run the scripts, one after the other, respecting the format.

vim aws-credentials.yaml

staging_account:
access_key_id: ACCESS_KEY_FOR_ACCOUNT_ONE
secret_access_key: xxxxxxxxxx
region: us-east-1
test_account:
access_key_id: ACCESS_KEY_FOR_ACCOUNT_TWO
secret_access_key: xxxxxxxxxx
region: us-east-1

4. Create following scripts

vim stop_instances.rb

#!/usr/bin/env rubyrequire_relative ‘./library/aws_filter_by_tag’
require_relative ‘./library/aws_shutdown_list_of_instances’
creds = YAML.load(File.read(‘aws-credentials.yaml’))creds.each do |account|instances_stop = aws_filter_by_tag(account, ‘shutdown’, ‘true’) aws_shutdown_list_of_instances(account, instances_stop)

end

vim start_instances.rb

#!/usr/bin/env rubyrequire_relative './library/aws_filter_by_tag'
require_relative './library/aws_startup_list_of_instances'
creds = YAML.load(File.read('aws-credentials.yaml'))
creds.each do |account|
instances_start = aws_filter_by_tag(account, 'startup', 'true')
aws_startup_list_of_instances(account, instances_start)
end

At this point, you should have something like this:

$ ls -l /usr/local/aws_tasksstart_instances.rb
stop_instances.rb

Under library directory, you need to have other 2 smaller scripts in order not to repeat code.

vim aws_filter_by_tag

#!/usr/bin/env ruby
# Giving argument tag(key, value) to get a list of instances that accomplish passing tag.
require 'aws-sdk'
require_relative 'aws_ec2_resource_for'
def aws_filter_by_tag (account, tag_key, tag_value)
key_value = []
ec2 = aws_ec2_resource_for(account)
ec2.instances({filters: [{name: 'tag:'"#{tag_key}", values: ["#{tag_value}"]}]}).each do |instance|
key_value << instance.id
end
return key_value
end

vim aws_startup_list_of_instances

#!/usr/bin/env ruby
# start a list of instances passing by arguments
require ‘aws-sdk’require_relative ‘aws_ec2_resource_for’
def aws_startup_list_of_instances (account,ec2_array)
ec2 = aws_ec2_resource_for(account)
ec2_array.each do |instancia_id|
ec2.instances.each do |instance|
puts instance.start if instance.id == instancia_id
end
end
end

vim aws_shutdown_list_of_instances

#!/usr/bin/env ruby
# stop a list of instances passing by arguments
require 'aws-sdk'
require_relative 'aws_ec2_resource_for'
def aws_shutdown_list_of_instances (account, ec2_array)
ec2 = aws_ec2_resource_for(account)
ec2_array.each do |instancia_id|
ec2.instances.each do |instance|
puts instance.stop if instance.id == instancia_id
end
end
end

5. Last step: tag the instances that are going to be started / stopped. Open the AWS console, choose the instance at issue, go to “Tags”. And set there key:value “startup:true” and “shutdown:true”.

Once you have followed all the steps, you should be able to manage the state of your instances.

Summary

In this post, we’ve reviewed the process of starting / stopping instances since we started using only one AWS account to many.

--

--