DEV Community

Cover image for EventBridge Scheduler- Start/Stop EC2 instance
Rakesh Sanghvi
Rakesh Sanghvi

Posted on • Originally published at awsmantra.com

EventBridge Scheduler- Start/Stop EC2 instance

It has been always challenging to bring down Cloud costs. In my organization, we have EC2, RDS, and ECS instances that we can shut down during off hours in our lower environment. This is a simple pattern that starts and stops ec2 instances using Universal Target. Using a universal target we can call the underlying AWS API without writing a single line of code using AWS SDK. As of now, it's having more than 270+ API calls.

ec2

This example uses AWS CLI, and CDK (Typescript). Assuming you already Bootstrapping your account to deploy the CDK app.

Here is the main stack which creates nested stacks of role, EC2Start and EC2Stop.

import {Stack, StackProps} from 'aws-cdk-lib';
import {Construct} from 'constructs';
import {SchedulesRole} from "./scheduler-role";
import {Ec2Start} from "./ec2-start";
import {Ec2Stop} from "./ec2-stop";

export class EC2InstanceStartStopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);


    // Create Role
    const schedulesRole = new SchedulesRole(this,"SchedulerRoleStack")

    // Create EC2Start schedules
    const ec2Start = new Ec2Start(this,"Ec2Start", {
      roleArn: schedulesRole.roleArn,
    })

    // Create EC2Stop schedules
    const ec2Stop = new Ec2Stop(this,"Ec2Stop", {
      roleArn: schedulesRole.roleArn,
    })

  }
}
Enter fullscreen mode Exit fullscreen mode

Give minimal permission to start/stop EC2 instances

import { StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import {Effect, PolicyStatement, Role, ServicePrincipal} from "aws-cdk-lib/aws-iam";

export class SchedulesRole extends cdk.NestedStack  {
    private readonly _role: Role;
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);


        // Add scheduler assumeRole
        this._role  = new Role(this,  "scheduler-ec2-start-stop", {
           assumedBy: new ServicePrincipal('scheduler.amazonaws.com'),
            roleName: "scheduler-ec2-start-stop"
        })

        // Add policy
        this._role.addToPolicy(  new PolicyStatement( {
            sid: 'EC2StartStopPermissions',
            effect: Effect.ALLOW,
            actions: [
                "ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            resources: ["*"], //Give the least privileges
        }))
    }

    get roleArn(): string {
        return this._role.roleArn;
    }
}
Enter fullscreen mode Exit fullscreen mode

Caution:- if the underlying EBS volume attached to EC2 is encrypted, please make sure you give appropriate KMS permission in the above policy.

Create an Ec2Start schedule. This will start all EC2 Instances at 8 AM CST time. AWS EventScheudler automatically adjusts DST time. See AWS StartInstances API

import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { Role } from "aws-cdk-lib/aws-iam";
import {CfnSchedule} from "aws-cdk-lib/aws-scheduler";

interface Ec2StartProps extends cdk.NestedStackProps {
    roleArn: string
}

export class Ec2Start extends cdk.NestedStack {
    private readonly role: Role;

    constructor(scope: Construct, id: string, props: Ec2StartProps) {
        super(scope, id, props);

        // Start all EC2 Instance 8 am Central Time
        new CfnSchedule(this,"ec2-start-scheduler", {
            name: "ec2-start-scheduler",
            flexibleTimeWindow: {
                mode: "OFF"
            },
            scheduleExpression: "cron(0 8 ? * * *)",
            scheduleExpressionTimezone: 'America/Chicago',
            description: 'Event that start EC2 instances',
            target: {
                arn: 'arn:aws:scheduler:::aws-sdk:ec2:startInstances',
                roleArn: props.roleArn,
               input: JSON.stringify(
               { InstanceIds:['i-05c757e84518Z1234',
                              'i-0e8cf751ca6dD9678']}),
            },
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Create an EC2Stop schedule. See AWS StopInstances API

import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { Role } from "aws-cdk-lib/aws-iam";
import {CfnSchedule} from "aws-cdk-lib/aws-scheduler";

interface Ec2SopProps extends cdk.NestedStackProps {
    roleArn: string
}

export class Ec2Stop extends cdk.NestedStack {
    private readonly role: Role;

    constructor(scope: Construct, id: string, props: Ec2SopProps) {
        super(scope, id, props);

        // Start all EC2 Instance 8 am Central Time
        new CfnSchedule(this,"ec2-stop-scheduler", {
            name: "ec2-stop-scheduler",
            flexibleTimeWindow: {
                mode: "OFF"
            },
            scheduleExpression: "cron(0 20 ? * * *)",
            scheduleExpressionTimezone: 'America/Chicago',
            description: 'Event that start EC2 instances',
            target: {
                arn: 'arn:aws:scheduler:::aws-sdk:ec2:stopInstances',
                roleArn: props.roleArn,
                input: JSON.stringify(
                      { InstanceIds: ['i-05c757e84518Z1234',
                                      'i-0e8cf751ca6dD9678']}),
            },
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Deployment Instructions :-

1) git clone git@github.com:awsmantra/eventbridge-schedules-ec2-start-stop.git

2) cd eventbridge-schedules-ec2-start-stop

3) npm install

4) cdk deploy --all -a "npx ts-node bin/app.ts" --profile dev
Enter fullscreen mode Exit fullscreen mode

Cleanup :-

cdk destroy --profile dev
Enter fullscreen mode Exit fullscreen mode

You can download the source code form here

All Complex systems that work evolved from simpler systems that worked --Gall's Law

Top comments (1)

Collapse
 
tanushree_aggarwal profile image
Tanushree Aggarwal

Very informative, to the point explanation!