Skip to content

Instantly share code, notes, and snippets.

@santtu
Created April 24, 2015 21:13
Show Gist options
  • Save santtu/7e759d30f11dca78e3cf to your computer and use it in GitHub Desktop.
Save santtu/7e759d30f11dca78e3cf to your computer and use it in GitHub Desktop.
Example of an AWS Lambda function implementing a custom termination policy for auto scale group
console.log('Loading function');
var minInstances = 1;
var region = 'us-east-1';
var EventEmitter = require("events").EventEmitter;
var aws = require('aws-sdk');
var http = require('http');
var ec2 = new aws.EC2({region: region});
var autoscaling = new aws.AutoScaling({region: region});
var cloudwatch = new aws.CloudWatch({region: region});
// This routine will select one instance to terminate based on the
// smallest "total time" value collected by `getTotalTime`.
function chooseAndTerminate(values, context) {
var currentId = null;
var currentValue = Infinity;
console.log("chooseAndTerminate:", values);
var count = Object.keys(values).length;
if (count <= minInstances) {
console.log("Only", count, "healthy instances, <= minimum of",
minInstances);
context.succeed();
return;
}
for (var id in values) {
value = values[id];
if (value < currentValue) {
currentValue = value;
currentId = id;
}
}
if (currentId !== null) {
console.log("Found candidate:", currentId, "value", currentValue);
console.log("Terminating instance");
autoscaling.terminateInstanceInAutoScalingGroup(
{InstanceId: currentId,
ShouldDecrementDesiredCapacity: true
}, function(error, data) {
console.log("Instance termination in group");
if (error)
context.fail(error);
if (data)
console.log("Data:", data);
context.succeed();
});
} else {
console.log("could not decide!");
context.succeed();
}
}
// This routine fetches a custom metric from the instance. In this
// time http://<instance>:80/total-time is assumed to return a single
// integer value.
function getTotalTime(ip) {
var e = new EventEmitter();
var req = http.request(
{host: ip, path: "/total-time"},
function(response) {
var str = '';
response.on('data',
function(chunk) {
str += chunk;
});
response.on('end',
function() {
e.emit('value', parseInt(str));
e.emit('done');
});
});
req.on('error',
function(error) {
e.emit('error', error);
e.emit('done');
});
req.end();
return e;
}
exports.handler = function(event, context) {
console.log(JSON.stringify(event, null, 2));
// Fetch alarm and group names from the CloudWatch notification.
var alarmData = JSON.parse(event.Records[0].Sns.Message);
console.log("Alarm data:", alarmData);
var alarmName = alarmData.AlarmName;
var groupName = null;
alarmData.Trigger.Dimensions.map(function(v) {
if (v.name == 'AutoScalingGroupName')
groupName = v.value;
});
if (groupName === null)
context.fail("No auto scaling group name found");
console.log("Downscale triggered for group", groupName,
"by alarm", alarmName);
// Go through all running instances in the auto scaling group.
ec2.describeInstances(
{Filters:
[{Name: "tag:aws:autoscaling:groupName", Values: [groupName]},
{Name: "instance-state-name", Values: ["running"]}]},
function(error, data) {
if (error)
context.fail(error);
if (data) {
values = {};
count = data.Reservations.length;
if (count === 0) {
console.log("Strange, nothing in the auto scaling group!");
context.succeed();
return;
}
data.Reservations.map(function(r) {
r.Instances.map(function(i) {
var id = i.InstanceId;
var ip = i.PublicIpAddress;
console.log("Instance:", id, "at", ip);
getTotalTime(ip)
.on('value',
function(v) {
values[id] = v;
})
.on('done',
function() {
count--;
if (count === 0)
chooseAndTerminate(values, context);
});
});
});
}
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment