Skip to content

Instantly share code, notes, and snippets.

@ajbrown
Last active January 25, 2021 11:13
Show Gist options
  • Save ajbrown/4f59ff50cf68f0a632715f042f83c8ed to your computer and use it in GitHub Desktop.
Save ajbrown/4f59ff50cf68f0a632715f042f83c8ed to your computer and use it in GitHub Desktop.
Lambda and client code examples for ASG Leader detection. For more info, see https://ajbrown.org/2017/02/10/leader-election-with-aws-auto-scaling-groups.html
<?php
public function isLeader() {
$isLeader = true; // get my instance-id from the metadata service
$instanceId = exec('curl http://169.254.169.254/latest/meta-data/instance-id');
if (!empty($instanceId)) {
$isLeader = false;
$ec2 = \Aws\Ec2\Ec2Client::factory(['key' => AWS_ACCESS_KEY_ID, 'secret' => AWS_SECRET_KEY, 'region' => 'us-east-1']);
$data = $ec2->describeInstances(array('InstanceIds' => [$instanceId]));
if (!empty($data) && !empty($data['Reservations'])) {
$tags = $data['Reservations'][0]['Instances'][0]['Tags'];
foreach ($tags as $tag) {
if ($tag['Key'] == 'app:isLeader') {
$isLeader = true;
break;
}
}
$tag = null;
}
}
return $isLeader;
}
console.log('Loading ElectASGLeader');
var aws = require('aws-sdk');
var autoscaling = new aws.AutoScaling();
var ec2 = new aws.EC2();
var validStates = [ 'Pending', 'Pending:Wait', 'Pending:Proceed', 'InService' ];
var leaderTagKey = 'app:isLeader';
var leaderTagValue = 'true';
exports.handler = function(event, context) {
console.log('Received event:');
console.log(event);
var data = event.Records[0].Sns;
console.log( "SNS Message:", data );
leader = null,
json = JSON.parse(data.Message)
;
//list all instances currently in the autoscaling group
autoscaling.describeAutoScalingGroups( { 'AutoScalingGroupNames' : [json.AutoScalingGroupName] }, function(err, data) {
if( err ) {
console.log( 'Error loading autoscaling groups: ', err );
context.fail();
return;
}
var asg = data.AutoScalingGroups.pop();
var candidates = [];
var allInstanceIds = [];
asg.Instances.forEach( function( instance ) {
allInstanceIds.push( instance.InstanceId );
if( validStates.indexOf( instance.LifecycleState ) >= 0 ) {
candidates.push( instance.InstanceId );
console.log( 'Instance ' + instance.InstanceId + ' is a candidate for leader.' );
}
} );
ec2.describeInstances( { 'InstanceIds' : candidates }, function(err, data) {
if( err ) {
console.log( 'Error loading autoscaling groups: ', err );
context.fail();
return;
}
var leaders = [];
var newLeader = null;
//find all leader instances
data.Reservations.forEach( function( reservation ) {
reservation.Instances.forEach( function( instance ) {
instance.Tags.forEach( function( tag ) {
if( tag.Key == leaderTagKey ) {
leaders.push( instance );
}
});
});
});
//if there's already a leader, don't change anything.
if( leaders.length == 1 ) {
console.log( 'Retaining leader instance ' + leaders[0] );
context.succeed( leaders[0] );
return;
// if there is more than one leader, keep one of them.
} else if( leaders.length > 1 ) {
newLeader = leaders[0];
// if there are no leaders and the triggering instance is coming online, make it the leader.
} else if( json.Event == 'autoscaling:EC2_INSTANCE_LAUNCH' ) {
newLeader = json.EC2InstanceId
//Otherwise, just pick a leader.
} else {
newLeader = candidates[0];
}
//flip the tags on all instances.
ec2.deleteTags( { 'Resources': allInstanceIds, 'Tags': [ { 'Key': leaderTagKey } ] }, function(err, data) {
if( err ) {
console.log( 'Error deleting tags from non-candidate instances: ', err );
context.fail();
return;
}
console.log( 'Cleared tags on ' + allInstanceIds.length + ' insances' );
var params = {
'Resources': [newLeader],
'Tags': [ { 'Key': leaderTagKey, 'Value': leaderTagValue } ]
};
ec2.createTags( params, function() {
if( err ) {
console.log( 'Error creating leader tag on leader instance: ', err );
context.fail();
return;
}
console.log( 'Successfully tagged new leader instance' );
context.succeed( newLeader );
} )
});
});
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment