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
Top comments (1)
Very informative, to the point explanation!