Last active
January 25, 2021 11:13
-
-
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
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
<?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; | |
} |
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
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