Schedule EC2 instances in a single-account
Posted: | Updated: | Tags: cloud aws terraform projectThis week I open sourced a Terraform project I’ve been using for the past few months. This solution allows the user to schedule the start or stop of EC2 instances in a single AWS account. This schedule is defined through Terraform and created EventBridge Schedulers. This post will be a snapshot in time of how the solutions looks at the time of publishing. An up to date and concise description of the solution can be found on its GitHub page.
Now, the AWS Solutions Library already maintains a feature rich Instance Scheduler with its own CLI, cross-account capabilities, SSM integration, support for EC2 and RDS and automated tagging. That is a more production ready and well-architected solution. Mine is built to be simpler and fit my need to start and stop a few development instances automatically keeping my costs to a minimum. Besides it’s more fun to build stuff.
Solution Overview
The solution creates an Amazon EventBridge Scheduler Group, in which you can use the provided Terraform module to create one or more schedulers that manifest itself on AWS as an EventBridge Scheduler. Each scheduler can start or stop one or more EC2 instances that are targetted by a key/value tag. The scheduler triggers a Lambda function when the time is right with all the appropriate details and the deed is done. Simple really.
Usage and examples
Within the repository you will find two modules shared-resources
and schedule
. The shared-resources
module creates the Lambda function, EventBridge Scheduler Group and all IAM roles and policies required, this only needs to be setup once per account. The schedule
module creates a new EventBridge Scheduler resource and is where you can define your schedule, key/value tag for the EC2 instances and whether you would like to start or stop those instances.
The main.tf
file contains a working example of this solution, I will break it down by section here.
module "shared-resources" {
source = "./modules/shared-resources"
scheduler_group_name = var.scheduler_group_name
}
This creates an instance of the shared-resources
module and passes in a name for the scheduler group.
module "workday_start" {
source = "./modules/schedule"
scheduler_name = "workday-start"
scheduler_group_name = var.scheduler_group_name
schedule_expression = "cron(00 9 ? * 2-6 *)"
scheduler_target_arn = module.shared-resources.start_stop_lambda_function_arn
scheduler_target_role_arn = module.shared-resources.scheduler_execution_role_arn
scheduler_target_input = jsonencode({
start = true
tag_name = "scheduler"
tag_value = "dev"
})
}
This next block creates our first schedule, it’s called “workday-start” and is triggered at 9am on Monday to Friday. As part of the input to the Lambda function it starts EC2 instances with tag key of “scheduler” and value of “dev”. This is all customisable. Other than those values the group name, Lambda function, IAM role details are passed to the module which were outputs from shared-resources
.
module "everyday_stop" {
source = "./modules/schedule"
scheduler_name = "everyday-stop"
scheduler_group_name = var.scheduler_group_name
schedule_expression = "cron(00 22 * * ? *)"
scheduler_target_arn = module.shared-resources.start_stop_lambda_function_arn
scheduler_target_role_arn = module.shared-resources.scheduler_execution_role_arn
scheduler_target_input = jsonencode({
start = false
tag_name = "scheduler"
tag_value = "dev"
})
}
This next block stops instances everyday of the week at 10pm with the tag key of “scheduler” and the value of “dev”. The name given to this scheduler is “everday-stop” and the same output variables from shared-resources
are passed in here as input.
Cost
Amazon EventBridge and AWS Lambda both come with a generous Always Free Tier which this solution fits well within unless you already have high usage on either of these services. EventBridge Scheduler is charged based on number of invocations with the first 14,000,000 invocations for free each month. Lambda is charged based on a invocation (a request) and the duration your code runs. The cost is dependant compute architecture selected and memory allocated. The function created defaults to x86_64 and 128 MB of memory.