EventBridge Scheduler- Start/Stop EC2 instance

Search for a command to run...

No comments yet. Be the first to comment.
Use Case:- I am sure you all remember how much boilerplate code we used to write when we convert the DynamoDB stream to the business object. Here is a small snippet of the GoLang program. m := &EntitySearch{} for name, value := range recordI...

Use Case:- Our company has an SFTP server in the lower environment, but we don't have regular traffic on it. Our QA team only uses it once every two weeks during production release testing. If you create an AWS SFTP server, you will be charged $0.30 ...

Use Case:- We have a business use case where we need to call our nightly job for multiple tenants. Initially, I planned to use EventBridge Bus and Rules to achieve this goal. However, we discovered that EventBridge Rules has a limit of 5 targets, whi...

Little Background:- in 2019 we started moving from server to serverless and started adopting AWS Lambda + GoLang for any new development. My chief architect and current CTO Ben Pyle(Blog) came up with the GoLang pattern which we used in all our Micro...

As I mentioned in my prior post, we used the ECS Fargate instance in my organization. During off hours, we shut down the ECS Fargate instance every day in our lower environment to cut costs. Here is a prior article where I have explained about Start/...

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.

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,
})
}
}
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;
}
}
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']}),
},
});
}
}
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']}),
},
});
}
}
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
Cleanup :-
cdk destroy --profile dev
You can download the source code form here.
All Complex systems that work evolved from simpler systems that worked
--Gall's Law