Saving Costs with Precision: Scheduling EC2 Instances for Off-Hours

Kevin Kiruri
8 min readSep 29, 2023

--

In the ever-evolving world of cloud computing, optimizing resources and reducing operational costs remain paramount goals for organizations of all sizes. One particularly effective strategy to achieve this is the automation of EC2 instance start and stop schedules. Imagine a scenario where your Amazon Web Services (AWS) EC2 instances automatically power down during off-hours, weekends, or holidays, only to spring back to life exactly when you need them. This level of precision and efficiency isn’t a futuristic dream but a practical reality that can significantly impact your bottom line. In this blog, we delve into the art of time-based EC2 instance scheduling, exploring how it not only curtails unnecessary expenses but also streamlines your cloud operations, ultimately putting you in control of your AWS bills like never before. Say goodbye to the days of manual intervention, and let automation usher in a new era of cost-effective, hassle-free cloud computing.

Prerequisites

  1. Have an AWS account. If you don’t have one, sign up here and enjoy the benefits of the Free-Tier Account
  2. Download the files in the following repo: Start-Stop-EC2

Launching EC2 instances

Let’s first start by creating 4 instances in our AWS account. We will have a ‘env’ tag. 2 of the instances will have a value of ‘dev’ for the ‘env’ tag and the other 2 will have the value ‘test’ for the ‘env’ tag. We will be using the tag later in this blog as we run lambda functions based on tags

The following are the steps:

  1. Create 2 ec2 instances, adding a tag key ‘env’ with value ‘dev’

2. Create another pair of instances with tag key ‘env’ and value ‘test’

3. Now we have a total of 4 running instances

Creating a Role

  1. Search for IAM on the search box and click on the service that appears

2. Click on ‘Policies’ on the Left panel, then ‘Create Policy’ on the top right of the page

3. Select the JSON Policy Editor and paste the following policy in the editor

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StartStopDescribeInstances",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:DescribeInstances"
],
"Resource": "*"
}
]
}

4. Scroll down and click on ‘Next’

5. Give the policy a name then scroll down and click on ‘Create Policy’

6. Once the policy is created, click on ‘Roles’ on the Left sidebar, then click on ‘Create role’

7. Select the service as ‘Lambda’, then click ‘Next’

8. On the ‘Add Permissions’ page, search for the policy name you used in step 5 then select it when it appears and click ‘Next’

9. On the next page, give the role a name, then scroll to the bottom of the page and click on ‘Create role’

Creating a Lambda Function

  1. Search for Lambda on the Search box, then click on the service that appears

2. On the page that appears, click on ‘Create Function’

3. Select ‘Author from Scratch’. On the Basic information, give the function a name and then select the Latest Python runtime (At the time of writing this blog, the latest is Python 3.11)

4. Under the ‘Permissions’ section, expand ‘Change default execution role’ . For the execution role, select ‘Use an existing role’, then select the role we created earlier. Then, scroll to the bottom and click on ‘Create function’

5. On the function page that appears, select the ‘Code’ tab and paste the following code in the ‘lamba_function’ code section

# Start the instances in the dev environment:

import boto3

# AWS region and tag details
aws_region = 'us-east-1'
tag_key = 'env'
tag_value = 'dev'

# Create an EC2 client
ec2 = boto3.client('ec2', region_name=aws_region)

def lambda_handler(event, context):
# Get instances with the specified tag and value
instances_to_start = get_instances_with_tag(tag_key, tag_value)

if instances_to_start:
# Start the instances
start_instances(instances_to_start)
return {
'statusCode': 200,
'body': 'Started instances: {}'.format(instances_to_start)
}
else:
print('No instances')
return {
'statusCode': 200,
'body': 'No instances found with tag "{}" and value "{}".'.format(tag_key, tag_value)
}

def get_instances_with_tag(key, value):
try:
response = ec2.describe_instances(
Filters=[
{
'Name': 'tag:' + key,
'Values': [value]
}
]
)

instances = []
for reservation in response['Reservations']:
for instance in reservation['Instances']:
instances.append(instance['InstanceId'])

print(instances)
return instances

except Exception as e:
print('Error:', e)
return []


def start_instances(instance_ids):
try:
ec2.start_instances(InstanceIds=instance_ids)
print('Starting instances:', instance_ids)
except Exception as e:
print('Error:', e)

This code will be used to start the instances in the ‘dev’ environment. Remember to set the region in which you are running the instances in

6. Click on ‘Deploy’ to update the changes. The ‘Deploy’ button should be inactive on success

7. To test the function, click on ‘Test’ to the left of ‘Deploy’

8. Configure the test event by giving it a name then click on ‘Save’

9. To test the Lambda function. Make sure your instances are stopped.

10. Click on ‘Test’ on the Lambda function Page

11. The instances in the dev environment start.

12. To create the function to stop the instances, repeat from step 1–11 but use the following code for the lambda-function

#Stop instances in the dev environment

import boto3

# AWS region and tag details
aws_region = 'us-east-1'
tag_key = 'env'
tag_value = 'dev'

# Create an EC2 client
ec2 = boto3.client('ec2', region_name=aws_region)

def lambda_handler(event, context):
# Get instances with the specified tag and value
instances_to_stop = get_instances_with_tag(tag_key, tag_value)

if instances_to_stop:
# Stop the instances
stop_instances(instances_to_stop)
return {
'statusCode': 200,
'body': 'Stopped instances: {}'.format(instances_to_stop)
}
else:
print('No instances')
return {
'statusCode': 200,
'body': 'No instances found with tag "{}" and value "{}".'.format(tag_key, tag_value)
}

def get_instances_with_tag(key, value):
try:
response = ec2.describe_instances(
Filters=[
{
'Name': 'tag:' + key,
'Values': [value]
}
]
)

instances = []
for reservation in response['Reservations']:
for instance in reservation['Instances']:
instances.append(instance['InstanceId'])

return instances

except Exception as e:
print('Error:', e)
return []

def stop_instances(instance_ids):
try:
ec2.stop_instances(InstanceIds=instance_ids)
print('Stopping instances:', instance_ids)
except Exception as e:
print('Error:', e)

Scheduling the Lambda Function

  1. Search for ‘cloudwatch’ on the services search tab and select the service

2. On the Left panel, expand ‘Events’ then click on ‘Rules’

3. On the ‘Rules’ page that appears, click on ‘Create rule’

4. Give the Rule a name, a description and select ‘Schedule’ for the Rule type, then click on ‘Continue in EventBridge Scheduler’ at the bottom of the page

5. on the ‘Create schedule’ page, scroll down to ‘Schedule pattern’ and set the following configurations:

a. Occurrence: Recurring schedule

b. Schedule type: Cron-based schedule

c. Cron expression: Input the cron for when you would like to run the function. Use crontab to develop your cron expression. In my case, I will use 0 8 ?* 2–6 * to mean that I want the schedule to run every weekday (Mon — Fri) at 08:00 AM.

The section at the bottom shows the next 10 triggers

6. Set the Flexible time window to ‘Off’ and scroll to the bottom of the page and click on ‘Next’

7. On the ‘Select target’ page, select ‘AWS Lambda’ as the Target API and select the function to start the EC2s from the dropdown

8. Scroll to the bottom and click on ‘Skip review and create schedule’

9. On the Review and ‘create schedule’ page, scroll to the bottom and click on ‘Create schedule’

10. This creates the schedule

11. Repeat steps 1–10 to schedule the Lambda function to stop the instances. The only thing we will be changing is the name of the schedule and the cron schedule

12. After creating the schedule, you should have both schedules on your ‘schedules’ dashboard

Summary

In conclusion, automating the start and stop of EC2 instances through Lambda functions and scheduling is an effective cost-saving strategy. I welcome collaboration and invite you to connect with me on LinkedIn to discuss cloud management, serverless architectures, and more. You can also access the code and resources used in this tutorial on my GitHub repository, making it easier for you to implement these cost-saving measures in your AWS environment. Thank you for joining me on this journey to optimize AWS costs and streamline operations; let’s continue learning and innovating together. Connect with me on LinkedIn and explore the code on my GitHub repository.

--

--

No responses yet