TL;DR
- Automate the start and stop of AWS EC2 instances to save costs.
- Achieve potential savings of up to 71% in development/staging environments.
- Use AWS Lambda for instance management and AWS EventBridge for scheduling.
- This guide includes steps for deploying an EC2 instance and setting up Lambda functions with CloudFormation.
AWS EC2 Charged Rules
AWS does not charged you when EC2 instance are stopped. You can stop EC2 resources when they are not being used. Most of Dev/Staging resources do not need to run 24/7. If you start and stop them during business hours. It can save a lot of money. Take a look at the comparison summarizing the costs associated with each scenario for running a t2.micro EC2 instance with an automated start/stop configuration versus running it continuously (24/7).
Cost Component | Instance Running 24/7 | Automated Start/Stop Configuration |
---|---|---|
EC2 Hourly Rate | $0.0116/hour | $0.0116/hour |
Total Running Hours per Month | 720 hours | 160 hours |
Monthly Cost for EC2 Instance | $8.352 | $1.856 |
EBS Volume Size | 8 GiB | 8 GiB |
EBS Monthly Cost | $0.80 | $0.80 |
Total Monthly Cost | $9.152 | $2.656 |
Savings Amount | - | $6.496 |
Percentage Savings | - | 71% |
Now, you can save cost around 70% for your dev/staging environment.
Automation with AWS Services
I decided to use these AWS services for
- AWS Lambda for using AWS-SDK to control EC2 instance start/stop
- AWS EventBridge for scheduling jobs to invoke Lambda Functions
Get Started
Deploy EC2 with AWS CloudFormation
First, we will create EC2 instance with t2.micro type for demonstrate. I will create instance in my Thailand Region.
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance with auto start/stop during business hours
Parameters:
InstanceType:
Type: String
Default: t2.micro
Description: EC2 instance type
KeyName:
Type: String
Description: Name of an existing EC2 KeyPair to enable SSH access to the instance.
Resources:
# EC2 Instance
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: ami-0c55b159cbfafe1f0
KeyName: !Ref KeyName
IamInstanceProfile: !Ref EC2InstanceProfile
Outputs:
InstanceId:
Description: The Instance ID of the EC2 instance.
Value: !Ref MyEC2Instance
Run CloudFormation CLI to deploy and then you will see EC2 created in AWS Management Console
Setup Lambda Function for Start/Stop EC2
We can use AWS-SDK with AWS Lambda to console start/stop on our EC2. I decided to use NodeJS. First, we create folder called lambda_functions
and run npm init
than create 2 file for each start/stop function.
πlambda_functions
βββ package.json
βββ startInstance.js
βββ stopInstance.js
with content in package.json
{
"name": "dev-ec2-start-stop-lambda-function",
"version": "1.0.0",
"type": "module",
"dependencies": {
"@aws-sdk/client-ec2": "^3.0.0"
}
}
and then startInstance.js
import { EC2Client, StartInstancesCommand } from '@aws-sdk/client-ec2';
const ec2Client = new EC2Client({ region: 'ap-southeast-7' });
export const handler = async (event) => {
const command = new StartInstancesCommand({ InstanceIds: [event.instance_id] });
await ec2Client.send(command);
return `Started your instance: ${event.instance_id}`;
};
with stopInstance.js
import { EC2Client, StopInstancesCommand } from '@aws-sdk/client-ec2';
const ec2Client = new EC2Client({ region: 'ap-southeast-7' });
export const handler = async (event) => {
const command = new StopInstancesCommand({ InstanceIds: [event.instance_id] });
await ec2Client.send(command);
return `Stopped your instance: ${event.instance_id}`;
};
Next, zip folder and upload it to an S3 Bucket. We will use it in next CloudFormation Template.
Setup Event Bridge with CloudFormation
Now we modify our CloudFormation template to Add EventBridge to invoke Lambda function to start/stop within during business hour.
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance with auto start/stop during business hours in Thailand timezone
Parameters:
InstanceType:
Type: String
Default: t4g.nano
Description: EC2 instance type
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: Name of an existing EC2 KeyPair to enable SSH access to the instance.
S3Bucket:
Type: String
Description: S3 bucket where Lambda code is stored
S3Key:
Type: String
Description: S3 key for the Lambda code zip file
Resources:
DevEC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: ami-08f3e969e66cd963a
KeyName: !Ref KeyName
DevSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
LambdaStartFunction:
Type: AWS::Lambda::Function
Properties:
Handler: './lambda_functions/startInstance.handler'
Role: !GetAtt LambdaExecutionRole.Arn
Code:
S3Bucket: !Ref S3Bucket
S3Key: !Ref S3Key
Runtime: nodejs18.x
Timeout: 30
LambdaStopFunction:
Type: AWS::Lambda::Function
Properties:
Handler: './lambda_functions/stopInstance.handler'
Role: !GetAtt LambdaExecutionRole.Arn
Code:
S3Bucket: !Ref S3Bucket
S3Key: !Ref S3Key
Runtime: nodejs18.x
Timeout: 30
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: EC2StartStopPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:StartInstances
- ec2:StopInstances
Resource: '*'
StartInstanceRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: 'cron(0 1 * * ? *)' # 08:00 UTC (Thailand Time)
Targets:
- Arn: !GetAtt LambdaStartFunction.Arn
Id: "StartEC2Instance"
Input: !Sub '{"instance_id": "${DevEC2Instance}"}'
StopInstanceRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: 'cron(0 11 * * ? *)' # 18:00 UTC (Thailand Time)
Targets:
- Arn: !GetAtt LambdaStopFunction.Arn
Id: "StopEC2Instance"
Input: !Sub '{"instance_id": "${DevEC2Instance}"}'
EventRulePermissionStart:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref LambdaStartFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt StartInstanceRule.Arn
EventRulePermissionStop:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref LambdaStopFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt StopInstanceRule.Arn
Outputs:
InstanceId:
Description: "Instance ID of the newly created EC2 instance"
Value: !Ref DevEC2Instance
Conclusion
After we apply CloudFormation Template. Our EC2 Instance will start/stop automatically during business hour we config in Amazon EventBridge. This can provide you with about 70% cost saving for resources that do not need to run 24/7 like dev/staging environment.