Skip to content

Instantly share code, notes, and snippets.

@tteskac
Last active March 21, 2024 15:35
Show Gist options
  • Save tteskac/0ab34617652433d70b8fc59eb444a85f to your computer and use it in GitHub Desktop.
Save tteskac/0ab34617652433d70b8fc59eb444a85f to your computer and use it in GitHub Desktop.
Simple EC2 scheduler with AWS Lambda function (start during working hours only, stop otherwise)
// Simple EC2 scheduler with AWS Lambda (NodeJs) function (start during working hours only, stop otherwise).
// https://gist.github.com/tteskac/0ab34617652433d70b8fc59eb444a85f
//
// The example code below checks if specific EC2 instance should be running or be stopped to save money.
// This is extremely useful e.g. for development/testing instances to be running only during working hours.
// Below in the code it is specified to run it Monday-Friday between 5-16 (UTC).
// In addition, it will not shutdown the instance if the CPU usage is above idle state,
// e.g. if someone is still using it a bit more outside working hours or some long processing is still happening.
// Use EventBridge (CloudWatch Events) to trigger this lambda function every hour.
//
// How to use:
// Adapt line 68 to your needs (when EC2 instance(s) should be running).
// When executing this Lambda function include Json containing "region" and "instance" or "instances" attributes:
// {
// "region": "eu-central-1",
// "instance": "i-########",
// "instances": ["i-########", ...]
// }
//
// Make sure to put this additional permissions to the default lambda function's role:
// {
// "Version": "2012-10-17",
// "Statement": [
// {
// "Sid": "VisualEditor0",
// "Effect": "Allow",
// "Action": [
// "cloudwatch:GetMetricData",
// "ec2:StartInstances",
// "ec2:StopInstances"
// ],
// "Resource": "*"
// }
// ]
// }
//
// Documentation:
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html#getMetricData-property
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html
// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html
// https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/
const { EC2Client, StartInstancesCommand, StopInstancesCommand } = require("@aws-sdk/client-ec2");
const { CloudWatchClient, GetMetricDataCommand } = require("@aws-sdk/client-cloudwatch");
async function checkInstance(region, instance) {
console.log("Region:", region);
console.log("Instance:", instance);
if (!region || !instance) {
console.error("Both 'region' and 'instance' must be defined!");
return;
}
const ec2 = new EC2Client({region});
const cw = new CloudWatchClient({region});
// Check current day and time.
let date = new Date();
let day = date.getDay();
let hour = date.getHours();
console.log("Current day of week:", day);
console.log("Current hour (UTC):", hour);
// Define when the instance should be running. (e.g. during working hours)
let shouldBeRunning = false;
// IF Monday-Friday AND 5-16 (UTC) THEN true
if (day >= 1 && day <= 5 && hour >= 5 && hour < 16) shouldBeRunning = true;
console.log("Should the instance be running?", shouldBeRunning);
console.log("Defined running periods: Monday-Friday 5:00 - 16:00 (UTC)");
// Make sure not to shut down if still processing something.
if (!shouldBeRunning) {
// Check the CPU metrics for the last 15min.
let endTime = Date.now()
let startTime = endTime - (1000 * 60 * 15);
const paramsMetrics = {
"StartTime": new Date(startTime),
"EndTime": new Date(endTime),
"MetricDataQueries": [
{
"Id": "m1",
"Label": "${AVG}",
"MetricStat": {
"Metric": {
"Namespace": "AWS/EC2",
"MetricName": "CPUUtilization",
"Dimensions": [
{
"Name": "InstanceId",
"Value": instance
}
]
},
"Period": 300,
"Stat": "Average"
}
}
]
};
console.log("Collecting CPU usage...");
let avg = "";
const command = new GetMetricDataCommand(paramsMetrics);
const response = await cw.send(command);
console.log(response);
let mdr = response.MetricDataResults;
avg = mdr[0].Label;
console.log("Average CPU% is", avg);
if (!avg || avg == "--") {
console.log("Probably the instance is already stopped.");
} else if (avg > 5) {
console.log("The instance is still doing something, do not stop it yet...");
shouldBeRunning = true;
}
}
// Finally start or stop the instance.
if (shouldBeRunning) {
console.log("Starting instance(s)...");
const command = new StartInstancesCommand({InstanceIds: [instance]});
const response = await ec2.send(command);
console.log(response);
} else {
console.log("Stopping instance(s)...");
const command = new StopInstancesCommand({InstanceIds: [instance]});
const response = await ec2.send(command);
console.log(response);
}
}
exports.handler = async (event) => {
let region = event.region;
if (!region || (!event.instance && !event.instances)) {
console.error("Both 'region' and ('instance' or 'instances') must be defined!");
return;
}
if (event.instance) {
await checkInstance(region, event.instance);
}
if (event.instances) {
for (let instance of event.instances) {
await checkInstance(region, instance);
}
}
console.log("END");
};
@heidemn-faro
Copy link

I didn't know that you can mix await ... promise() with callbacks 😛

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment