EventBridge Scheduler to Start and Stop EC2 instances

Sign Up to Build

About this Architecture

Here is some information about this architecture.

How to Build This Solution

Here are the steps you can follow to build this solution on your own.

In this lab, you'll explore how to use EventBridge to schedule the starting and stopping of EC2 instances. This provides an efficient way to manage resources, ensuring that you only use and pay for what you need. It's a vital skill for optimizing cloud costs and resource utilization.

Get Your AWS Credentials

If you're using the Skillmix Labs feature, open the lab settings (the beaker icon) on the right side of the code editor. Then, click the Start Lab button to start hte lab environment.

Wait for the credentials to load. Then run this in the terminal.

Be sure to enter in your own access key and secret key and name your profile 'smx-lab'.

$ aws configure --profile smx-lab
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: us-west-2
Default output format [None]: 

Note: If you're using your own AWS account you'll need to ensure that you've created and configured a named AWS CLI profile named smx-lab.

Create the Terraform Config

Next, we'll create the required_providers config. This config is used to specify the required provider plugins for our Terraform project. In this case, we are specifying the aws provider from HashiCorp with a minimum version of 4.64.0.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.64.0"
    }
  }
}

Create the aws Config

Next, we'll create the aws config which is used to configure the AWS provider in Terraform. This config specifies the AWS profile to use (smx-lab) and the AWS region to operate in (specified by the variable var.region).

provider "aws" {
  profile = "smx-lab"
  region  = "${var.region}"
}

Create the region Variable

Next, we'll create the region config. This config is used to specify the AWS Region where we will be deploying our resources.

variable "region" {
  type        = string
  description = "AWS Region where deploying resources"
  default     = "us-east-1"
}

Create the aws_profile_name Variable

Next, we'll create the aws_profile_name config. This config is used to specify the AWS CLI credentials profile name. By default, it is set to default.

variable "aws_profile_name" {
  type        = string
  description = "AWS CLI credentials profile name"
  default     = "default"
}

Create the vpc_cidr Variable

Next, we'll create the vpc_cidr config. This config is used to define the CIDR block for the VPC.

variable "vpc_cidr" {
  type        = string
  description = "CIDR block for VPC"
  default     = "10.0.0.0/16"
}

Create the subnet_cidr Variable

Next, we'll create the subnet_cidr config. This config is used to define the CIDR block for the subnet. The default value is set to '10.0.0.0/24'.

variable "subnet_cidr" {
    type=string
    description = "CIDR block for the subnet"
    default = "10.0.0.0/24"
}

Create the aws_ami Data Config

Next, we'll create the aws_ami config. This config is used to retrieve the most recent Amazon Linux 2 AMI that matches the specified filter criteria. The filter criteria include the owner alias being 'amazon' and the name starting with 'amzn2-ami-hvm'.

data "aws_ami" "amazon-linux-2" {
  most_recent = true

  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

Create the project_name Local Config

Next, we'll create the project_name config. This config is used to define the local variable project_name with the value 'tf-test'.

locals {
  project_name = "tf-test"
}

Create the aws_vpc Resource

Next, we'll create the aws_vpc config which is used to define an Amazon Virtual Private Cloud (VPC) in Terraform.

resource "aws_vpc" "vpc" {
  cidr_block = "${var.vpc_cidr}"
}

Create the aws_subnet Resource

Next, we'll create the aws_subnet config. This config is used to define a subnet in an AWS VPC.

resource "aws_subnet" "subnet" {
  vpc_id     = "${aws_vpc.vpc.id}"
  cidr_block = "${var.subnet_cidr}"
  tags = {
    Name = "private-subnet-1"
  }
}

Create the aws_instance Resource

Next, we'll create the aws_instance config. This config is used to create an EC2 instance in AWS.

resource "aws_instance" "test-ec2" {
  ami           = "${data.aws_ami.amazon-linux-2.id}"
  instance_type = "t3.micro"
  subnet_id     = "${aws_subnet.subnet.id}"

  tags = {
    Name = "tf-test-ec2"
  }
}

Create the aws_scheduler_schedule Resource

Next, we'll create the aws_scheduler_schedule config. This config is used to define a schedule for starting EC2 instances.

resource "aws_scheduler_schedule" "ec2-start-schedule" {
  name                        = "ec2-start-schedule"
  flexible_time_window        = [{"mode": "OFF"}]
  schedule_expression         = "cron(0 8 ? * MON-FRI *)"
  schedule_expression_timezone= "US/Eastern"
  description                 = "Start instances event"
  target {
    arn       = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
    role_arn  = "${aws_iam_role.scheduler-ec2-role.arn}"
    input     = "${jsonencode({'InstanceIds': ['${aws_instance.test-ec2.id}']})}"
  }
}

This config creates a scheduler schedule named ec2-start-schedule that uses a cron expression to specify the time and days of the week when the schedule should run. It also sets the timezone to US/Eastern. The schedule is associated with a target that specifies the ARN of the AWS SDK action to start EC2 instances. The target also references an IAM role and provides input in JSON format, specifying the instance IDs of the EC2 instances to start.

Create the aws_scheduler_schedule Resource

Next, we'll create the aws_scheduler_schedule config. This config is used to define a schedule for stopping EC2 instances.

resource "aws_scheduler_schedule" "ec2-stop-schedule" {
  name                          = "ec2-stop-schedule"
  flexible_time_window          = [{"mode": "OFF"}]
  schedule_expression           = "cron(0 17 ? * MON-FRI *)"
  schedule_expression_timezone  = "US/Eastern"
  description                   = "Stop instances event"
  target {
    arn       = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
    role_arn  = "${aws_iam_role.scheduler-ec2-role.arn}"
    input     = "${jsonencode({'InstanceIds': ['${aws_instance.test-ec2.id}']})}"
  }
}

This config creates a scheduler schedule named ec2-stop-schedule that stops EC2 instances according to the specified cron expression. The flexible_time_window is set to OFF, meaning the schedule will not be flexible and will strictly adhere to the specified cron expression. The schedule_expression_timezone is set to US/Eastern to ensure the schedule is based on the Eastern Time Zone. The description provides a brief description of the schedule.

The target block specifies the action to be performed when the schedule is triggered. In this case, it stops instances using the arn of the AWS SDK for EC2's stopInstances function. The role_arn references the ARN of the IAM role scheduler-ec2-role that has the necessary permissions to perform the action. The input parameter is a JSON-encoded string specifying the InstanceIds of the EC2 instances to be stopped.

Create the aws_iam_policy Resource

Next, we'll create the aws_iam_policy config. This config is used to define an IAM policy named 'scheduler_ec2_policy'. The policy allows the 'ec2:StartInstances' and 'ec2:StopInstances' actions on the specified EC2 instances.

resource "aws_iam_policy" "scheduler_ec2_policy" {
  name   = "scheduler_ec2_policy"
  policy = "${jsonencode({
    'Version': '2012-10-17',
    'Statement': [
      {
        'Sid': 'VisualEditor0',
        'Effect': 'Allow',
        'Action': ['ec2:StartInstances', 'ec2:StopInstances'],
        'Resource': ['${aws_instance.test-ec2.arn}:*', '${aws_instance.test-ec2.arn}']
      }
    ]
  })}"
}

Create the aws_iam_role Resource

Next, we'll create the aws_iam_role config. This config is used to create an IAM role in AWS called "scheduler-ec2-role". The role will have a managed policy attached to it, specified by the ARN of the "scheduler_ec2_policy" policy. The role will also have an assume role policy that allows the "scheduler.amazonaws.com" service to assume this role.

resource "aws_iam_role" "scheduler-ec2-role" {
  name               = "scheduler-ec2-role"
  managed_policy_arns = ["${aws_iam_policy.scheduler_ec2_policy.arn}"]
  assume_role_policy  = "${jsonencode({
    'Version': '2012-10-17',
    'Statement': [
      {
        'Action': 'sts:AssumeRole',
        'Effect': 'Allow',
        'Sid': '',
        'Principal': {
          'Service': 'scheduler.amazonaws.com'
        }
      }
    ]
  })}"
}

Deploy the Solution

Let's deploy this thing! If you haven't done so, start the Skillmix lab session and get the account credentials. Configure your Terraform environment to use those credentials.

Then, open a terminal or command prompt, navigate to the folder with your Terraform file, and execute these commands:

# initiatlize the project 
$ terraform init 

# show the plan 
$ terraform plan 

# apply the changes 
$ terraform apply

Wait for the changes to be applied before proceeding.

Test the Solution

  1. After deployment, view the schedule created in the Amazon EventBridge console under Scheduler>Schedules.

  2. View the ec2-start-schedule. Navigate to the Target tab and note the Payload value which is in the format: {"InstanceIds":["i-006c2e9f4e706bf48"]}

  3. Navigate to the EC2 console and find the EC2 instance from the Payload value. Check the instance is powered on during 08:00 and 17:00 in the US/Eastern timezone.

Source: https://github.com/aws-samples/serverless-patterns/blob/main/eventbridge-schedule-to-ec2-terraform/variables.tf