Skip to content

Instantly share code, notes, and snippets.

@akomakom
Last active March 9, 2023 15:26
Show Gist options
  • Save akomakom/b100a40e777e2f2274d022804981243e to your computer and use it in GitHub Desktop.
Save akomakom/b100a40e777e2f2274d022804981243e to your computer and use it in GitHub Desktop.
AWS Lambda function to kill forgotten instances, pure NodeJS

AWS Lambda function to kill old instances

A pure NodeJS function to kill select EC2 instances if they run longer than a defined period.

This is useful as a safety measure when using EC2 in CI and launching many instances.
A CI crash or user action could prevent proper cleanup, leaving expensive machines running forever (or until the next billing cycle when there is much screaming).

Instructions

You will need the following privileges in your AWS role:

  • EC2 - describe, terminate
  • Lambda - probably most of them
  • EventBridge - read/write.
  • CloudWatch - probably read.

Setup Lambda

  • Create a from-scratch Lambda function
  • In the new role, add full control on EC2
  • Paste the javascript code, adjust config (top), click Deploy, then Test and see how it works.
  • Under Configuration->Triggers, create a new EventBridge trigger. Example cron expression: cron(0 * * * ? *)

Testing

  • Create a small instance with the matching tag(s).
  • Click Test. If enough time elapsed (maxAge), it should be terminated. You can lower maxAge temporarily, deploy, test.
  • Create another instance
  • Wait for the cron to run.
import {EC2Client, DescribeInstancesCommand, TerminateInstancesCommand } from "@aws-sdk/client-ec2";
// CONFIGURATION
const ec2 = new EC2Client({region: 'us-east-1'});
const maxAge = 1000 * 3600 * 3; // Terminate instances older than 3 hours in millis
const tag = 'jenkins'; // Only terminate instances with this tag
// if not null, send a notification to Slack/Mattermost if any instances are terminated,
// this is an Incoming Webhook URL:
const notifyUrl = null;
const thresholdDate = new Date((new Date()).getTime() - maxAge);
async function notifySlack(message) {
/*global fetch*/
await fetch(notifyUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ "text": message })
})
.then(response => console.log("Response for notification is: " + JSON.stringify(response)))
.catch((error) => {
console.error('error sending notification', error);
});
}
export const handler = async(event) => {
const data = await ec2.send(new DescribeInstancesCommand({
Filters: [
{ Name: "architecture", Values: ["x86_64"] },
{ Name: "instance-state-name", Values: ["running"] },
{
Name: "tag-key",
Values: [tag],
},
],
}));
var killList = [];
var message = "I am killing the following AWS instances that have been running longer than " + maxAge + "ms: ";
data.Reservations.forEach(reservation => {
reservation.Instances.forEach(instance => {
console.log("Checking instance " + instance.InstanceId + " with launch time " + instance.LaunchTime);
var instanceLaunchDate = new Date(instance.LaunchTime);
if (instanceLaunchDate < thresholdDate) {
console.log("Will kill this instance");
killList.push(instance.InstanceId);
//find its name
var name = "?";
instance.Tags.forEach(tag => {
if (tag.Key == 'Name') {
name = tag.Value;
}
});
message += instance.InstanceId + " (" + name + "),";
}
});
});
console.log("Final kill list: " + killList);
var killResult = {};
if (killList.length > 0) {
killResult = await ec2.send(new TerminateInstancesCommand({
InstanceIds: killList
}));
console.log("Killed instances (" + message + ") with result: " + JSON.stringify(killResult));
if (notifyUrl != null) {
await notifySlack(message);
}
}
return {
killList: killList,
killResult: killResult
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment