Three ways to start/stop EC2 instances periodically

TOC

Three ways to start/stop EC2 instances periodically

Consider how to start/stop EC2 instances periodically.

The first is to use EventBridge to periodically invoke Lambda functions for instance start/stop. This method is called “Instance Scheduler”.

https://aws.amazon.com/solutions/implementations/instance-scheduler-on-aws/?nc1=h_ls

The second is to define a maintenance window and periodically run the SSM Automation runbooks (AWS-StartEC2Instance, AWS-StopEC2Instance) for instance start/stop.

https://docs.aws.amazon.com/systems-manager-automation-runbooks/latest/userguide/automation-aws-startec2instance.html

https://docs.aws.amazon.com/systems-manager-automation-runbooks/latest/userguide/automation-aws-stopec2instance.html

The third is to use the EventBridge Scheduler to periodically call the API for instance start/stop.

https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduler.html

On this page, we will try the above three methods using CloudFormation.

Environment

Diagram of three ways to start/stop EC2 instances periodically.

Create three EC2 instances.
The OS will be the latest Amazon Linux 2023.

For each instance, configure the settings introduced at the beginning of this section.
Start/stop instances every 5 minutes.

The runtime environment for Lambda functions is Python 3.8.

CloudFormation template files

The above configuration is built with CloudFormation.
The CloudFormation templates are placed at the following URL

https://github.com/awstut-an-r/awstut-saa/tree/main/04/004

Explanation of key points of template files

How to use EventBridge and Lambda functions

Lambda Functions

Resources:
  StartInstanceFunction:
    Type: AWS::Lambda::Function
    Properties:
      Environment:
        Variables:
          INSTANCE_ID: !Ref Instance
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          import boto3
          import os

          instance_id = os.environ['INSTANCE_ID']
          region = os.environ['REGION']

          ec2_client = boto3.client('ec2', region_name=region)

          def lambda_handler(event, context):
            response = ec2_client.start_instances(
              InstanceIds=(instance_id,)
            )
            print(response)
      FunctionName: !Sub "${Prefix}-StartInstance"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout

  StopInstanceFunction:
    Type: AWS::Lambda::Function
    Properties:
      Environment:
        Variables:
          INSTANCE_ID: !Ref Instance
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          import boto3
          import os

          instance_id = os.environ['INSTANCE_ID']
          region = os.environ['REGION']

          ec2_client = boto3.client('ec2', region_name=region)

          def lambda_handler(event, context):
            response = ec2_client.stop_instances(
              InstanceIds=(instance_id,)
            )
            print(response)
      FunctionName: !Sub "${Prefix}-StopInstance"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

Create two Lambda functions.
One starts the instance, the other stops it.

Create a client object for boto3 EC2.
Execute the start_instances and stop_instances methods of this object.

The following are the IAM roles for both functions.

Resources:
  FunctionRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: InstanceStartStopPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                Resource: !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${Instance}"
Code language: YAML (yaml)

The contents of the instance start/stop actions allowed.

EventBridge Rules

This page focuses on how to start and stop EC2 instances periodically.

For more information on how to use EventBridge rules to periodically invoke Lambda functions, please see the following page.

あわせて読みたい
Schedule expressions in EventBridge (CloudWatch Events) to execute Lambda functions periodically 【Invoke Lambda functions periodically】 EventBridge allows you to set up recurring actions. In this case, we will invoke Lambda functions periodically. 【En...
Resources:
  StartInstanceScheduleRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: cron(0,10,20,30,40,50 * * * ? *)
      State: ENABLED
      Targets:
        - Arn: !GetAtt StartInstanceFunction.Arn
          Id: !Sub "${Prefix}-StartInstanceScheduleRule"

  StopInstanceScheduleRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: cron(5,15,25,35,45,55 * * * ? *)
      State: ENABLED
      Targets:
        - Arn: !GetAtt StopInstanceFunction.Arn
          Id: !Sub "${Prefix}-StopInstanceScheduleRule"
Code language: YAML (yaml)

One is for starting and the other for stopping.
Each targets the aforementioned function.

Set the activation status with a cron expression.
Functions for startup are called at 0, 10, … 50 minutes every hour.
Function for stop calls every 5, 15, … 55 minutes of every hour.
The combination of these functions will repeat starting and stopping every 5 minutes.

If you use an EventBridge rule to make a Lambda function periodic, the EventBridge will invoke the function.
So you need to authorize EventBridge to call the function.

Resources:
  StartInstanceFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt StartInstanceFunction.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt StartInstanceScheduleRule.Arn
Code language: YAML (yaml)

The above is the permission to invoke the function for starting, but the same applies for stopping.
Specify with EventBridge rules and Lambda functions.

How to use the SSM Automation runbook

Set up a maintenance window to periodically run the SSM Automation runbook.

For more information on this page, please see the following page.

あわせて読みたい
Create AMI using SSM Automation (one-time/scheduled) 【Create AMI using SSM Automation (one-time/scheduled)】 There are several ways to create an AMI. For example, the following page shows how to create one fro...

Although not used in this case, the SSM Automation runbook can also be run periodically using the EventBridge rule.

Please refer to the following page for more information.

あわせて読みたい
Use EventBridge rule to run SSM Automation runbook periodically 【Use EventBridge rule to run SSM Automation runbook periodically】 On the following pages, we have shown you how to run the SSM Automation runbook. https://...

First, check the maintenance window.

Resources:
  MaintenanceWindow1:
    Type: AWS::SSM::MaintenanceWindow
    Properties:
      AllowUnassociatedTargets: true
      Cutoff: 1
      Description: StartInstance
      Duration: 2
      Name: !Sub "${Prefix}-MaintenanceWindow1"
      Schedule: cron(0,10,20,30,40,50 * * * ? *)
      ScheduleTimezone: Asia/Tokyo
Code language: YAML (yaml)

Above is the window for startup, but the same is true for stop.
The Schedule property allows you to specify when to execute the runbook.
The settings are the same as in the first method.

Next, check the targets in the maintenance window.

Resources:
  MaintenanceWindowTarget1:
    Type: AWS::SSM::MaintenanceWindowTarget
    Properties:
      Name: !Sub "${Prefix}-MaintenanceWindowTarget1"
      ResourceType: INSTANCE
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance
      WindowId: !Ref MaintenanceWindow1
Code language: YAML (yaml)

Specify an ID to define the target instance to start and stop.

Finally, confirm the task to be performed in the maintenance window.

Resources:
  MaintenanceWindowTask1:
    Type: AWS::SSM::MaintenanceWindowTask
    Properties:
      MaxConcurrency: 1
      MaxErrors: 1
      Name: !Sub "${Prefix}-MaintenanceWindowTask1"
      Priority: 10
      Targets:
        - Key: WindowTargetIds
          Values:
            - !Ref MaintenanceWindowTarget1
      TaskArn: AWS-StartEC2Instance
      TaskInvocationParameters:
        MaintenanceWindowAutomationParameters:
          Parameters:
            AutomationAssumeRole:
              - !GetAtt SSMAutomationRole.Arn
            InstanceId:
              - "{{RESOURCE_ID}}"
      TaskType: AUTOMATION
      WindowId: !Ref MaintenanceWindow1
Code language: YAML (yaml)

For the startup task, specify the SSM Automation runbook AWS-StartEC2Instance.
For stopping, specify AWS-StopEC2Instance.

The following are the IAM roles in performing this task

Resources:
  SSMAutomationRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - ssm.amazonaws.com
      Policies:
        - PolicyName: CreateImagePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                Resource: !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${Instance}"
Code language: YAML (yaml)

The contents of the instance start/stop actions allowed.

How to use EventBridge Scheduler

Resources:
  StartInstanceSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Description: StartInstance
      FlexibleTimeWindow:
        Mode: "OFF"
      Name: !Sub "${Prefix}-StartInstanceSchedule"
      ScheduleExpression: cron(0,10,20,30,40,50 * * * ? *)
      State: ENABLED
      Target:
        Arn: arn:aws:scheduler:::aws-sdk:ec2:startInstances
        Input: !Sub '{"InstanceIds": ["${Instance}"]}'
        RoleArn: !GetAtt SchedulerRole.Arn

  StopInstanceSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Description: StopInstance
      FlexibleTimeWindow:
        Mode: "OFF"
      Name: !Sub "${Prefix}-StopInstanceSchedule"
      ScheduleExpression: cron(5,15,25,35,45,55 * * * ? *)
      State: ENABLED
      Target:
        Arn: arn:aws:scheduler:::aws-sdk:ec2:stopInstances
        Input: !Sub '{"InstanceIds": ["${Instance}"]}'
        RoleArn: !GetAtt SchedulerRole.Arn
Code language: YAML (yaml)

The upper schedule is for startup and the lower schedule is for shutdown.

There are two points.

The first is the ScheduleExpression property.
Actions can be scheduled with this property.
This one also defines a schedule with a cron expression.

The second is the Target property.

Specify the API operation to be executed for the Arn property.

Arn – The complete service ARN, including the API operation you want to target, in the following format: arn:aws:scheduler:::aws-sdk:service:apiAction.

Using universal targets

The API for EC2 is specified with the following notation.

arn:aws:scheduler:::aws-sdk:ec2:[apiAction]

In this requirement, startInstances is performed for starting and stopInstances is performed for stopping.

Specify arguments for calling both APIs in the Input property.

Input – A well-formed JSON you specify with the request parameters that EventBridge Scheduler sends to the target API.

Using universal targets

The following page shows that when calling both APIs, just pass the ID of the target instance in InstanceIds.

https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html

https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StopInstances.html

The instance ID is passed to this property in JSON format.

The RoleArn property is described as follows

RoleArn – The ARN for the execution role you want to use for the target. The execution role you specify must have the permissions to call the API operation you want your schedule to target.

Using universal targets

Specify the following IAM roles

Resources:
  SchedulerRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - scheduler.amazonaws.com
      Policies:
        - PolicyName: CreateImagePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                Resource: !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${Instance}"
Code language: YAML (yaml)

The contents of the instance start/stop actions allowed.

Architecting

Use CloudFormation to build this environment and check its actual behavior.

Create CloudFormation stacks and check the resources in the stacks

Create CloudFormation stacks.
For information on how to create stacks and check each stack, please see the following page.

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After reviewing the resources in each stack, information on the main resources created in this case is as follows

  • First configuration
    • EC2 instance 1: i-0b9e6d15e61ee820c
    • Lambda function 1: saa-04-004-StartInstance
    • Lambda function 2: saa-04-004-StopInstance
    • EventBridge Rule 1: saa-04-004-LambdaStack-HT-StartInstanceScheduleRul-RUFGLCMCBAMB
    • EventBridge Rule 2: saa-04-004- LambdaStack-HT-StopInstanceScheduleRule-Q509A3N87K0U
  • Second configuration
    • EC2 instance 2: i-0c9f61ea77b7a59e4
    • Maintenance window 1: mw-01d6f438ebac17258
    • Maintenance window 2: mw-03e3e07dfcdd5a7fb
  • Third configuration
    • EC2 instance 3: i-0e54794687e579ba1
    • EventBridge Scheduler 1: saa-04-004-StartInstanceSchedule
    • EventBridge Scheduler 2: saa-04-004-StopInstanceSchedule

Now that you are ready, check each resource from the AWS Management Console.

First configuration

Check the Lambda function.

Deetail of Lambda 1.
Deetail of Lambda 2.

You can see that both functions have been successfully created.

Check EventBridge.

Detail of EventBridge 1.
Detail of EventBridge 2.
Detail of EventBridge 3.
Detail of EventBridge 4.

A rule is created to invoke a function every 10 minutes, with a 5-minute gap.

Second configuration

Check the status of the creation of the maintenance window.

Detail of SSM 1.
Detail of SSM 2.
Detail of SSM 3.

Maintenance window for startup.

Detail of SSM 4.
Detail of SSM 5.
Detail of SSM 6.

Maintenance window for stop.

Both are created successfully.

Third configuration

Detail of EventBridge 5.
Detail of EventBridge 6.

Scheduler for startup.

Detail of EventBridge 7.
Detail of EventBridge 8.

Scheduler for stop.

Both are created successfully.

Operation Check

Now that you are ready, check the EC2 instance.

Instance 1

Detail of EC2 1.
Detail of EC2 2.

Instance 2

Detail of EC2 3.
Detail of EC2 4.

Instance 3

Detail of EC2 5.
Detail of EC2 6.

Indeed, all three units start and stop repeatedly every 5 minutes.

All three methods were used to start and stop instances periodically.

Summary

We have introduced three methods for starting and stopping EC2 instances periodically.

TOC