Last active
March 21, 2024 15:35
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I didn't know that you can mix
await ... promise()
with callbacks 😛