EC2 charges are based on the number of hours the instance is running, regardless of whether you are actually using it or not. For example, a dev server for testing might only be used during working hours and is not needed outside of those times.
So, help reduce costs when running EC2 instances in a dev environment for testing. This article aims to support scheduling the start/stop of EC2 instances instead of manually starting and stopping them.
AWS Services Used in This Article
Lambda, EC2, Amazon EventBridge, IAM, Route53
Architecture
Steps to Implement
- Create EC2 Instance, get InstanceID
- Create a Lambda Function to Start/Stop EC2 Instances, using code javascript below
import { EC2Client, StartInstancesCommand, StopInstancesCommand } from "@aws-sdk/client-ec2";
export const handler = async (event) => {
if (event.type == "" || event.instanceId == "" || !["START", "STOP"].includes(event.type)) {
return {
statusCode: 200,
body: JSON.stringify('Not run'),
};
}
const client = new EC2Client({ region: process.env.AWS_REGIONREGION });
let command = null;
let result = null;
if(event.type == "START") {
command = new StartInstancesCommand({
InstanceIds: [event.instanceId],
});
}
if(event.type == "STOP") {
command = new StopInstancesCommand({
InstanceIds: [event.instanceId],
});
}
try {
result = await client.send(command);
console.log(`${event.type == "STOP" ? "Stopping" : "Starting"} instances: ${event.instanceId} : DONE`);
} catch (err) {
console.log(`${event.type == "STOP" ? "Stopping" : "Starting"} instances: ${event.instanceId} : FAIL`);
console.error(err);
}
return {
statusCode: 200,
body: JSON.stringify(result),
};
};
//Parameter request
{
"instanceId": "i-00xxxxxxxxxxxxx",
"type": "START" // orSTOP
}
3. Add IAM role to Lambda function, step as below
//Policy JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*"
}
]
}
You can test the above lambda function again with the Test function and read the log in Cloudwatch.
4. Create a schedule to run a lambda function using Amazon EventBridge
Step 4, review again and then click on Create schedule button
Thus, we have created a schedule at 7:00 am every day to automatically turn on EC2 Instance as specified
Create an additional scheduler to turn off at a specified time, follow the steps above and automatically turn EC2 on and off according to the predetermined schedule.
In case you are using Route 53
In case you are using Route53 to point to the Public IPv4 address, but this address gets reassigned after each EC2 instance stop/start, this section guides you through updating the A Record of Route53 (if using Elastic IPs, this step is not necessary as they are static).
- Hosted zone has been created in Route53 as shown below
- Create a new Lambda function to retrieve the Public IPv4 address of the EC2 instance and assign it to the A Record of Route53
import { EC2Client, DescribeInstancesCommand } from "@aws-sdk/client-ec2";
import { Route53Client, ChangeResourceRecordSetsCommand } from "@aws-sdk/client-route-53";
export const handler = async (event) => {
//get instance publicv4Ip
const ec2Client = new EC2Client({ region: process.env.AWS_REGIONREGION });
const commandGetIp = new DescribeInstancesCommand({
InstanceIds: [event.instanceId]
});
const result = await ec2Client.send(commandGetIp);
let publicIpAddress = "";
const reservations = result.Reservations;
if (reservations && reservations.length > 0) {
const instances = reservations[0].Instances;
if (instances && instances.length > 0) {
const instance = instances[0];
publicIpAddress = instance.PublicIpAddress;
}
}
//set new publicv4Ip to Route53 record
const route53Client = new Route53Client({ region: process.env.AWS_REGIONREGION });
try {
const commandSetRecord = new ChangeResourceRecordSetsCommand({
HostedZoneId: process.env.HostedZoneId,
ChangeBatch: {
Changes: [
{
Action: "UPSERT",
ResourceRecordSet: {
Name: process.env.DomainName,
Type: "A",
TTL: 300,
ResourceRecords: [{ Value: publicIpAddress }],
},
},
],
},
});
const response = await route53Client.send(commandSetRecord);
console.log("Successfully updated Route 53 record:", response);
} catch (error) {
console.error("Error updating Route 53 record:", error);
}
return {
statusCode: 200,
body: publicIpAddress,
};
};
//Param request
{
"instanceId": "i-00xxxxxxxxxxxxx"
}
- Add a policy rule for this function to be able to interact with the Route53 service, JSON as below
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "route53:ChangeResourceRecordSets",
"Resource": "*"
}
]
}
- A policy rule to be able to read the Public IPv4 address of the EC2 instance.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
- Create an EventBridge schedule to run after starting the EC2 instance for about 2–3 minutes, as the EC2 instance needs time to start and be assigned a new IP.
- So, the process of updating the new Public IPv4 address of the EC2 instance for Route53 is now complete.
Thank you for reading the entire article.