Monday, 23 May 2016

Automatically assign EIP via Lambda on launch of an ASG instance


Pre-requisite: EIP Allocation Id.
The following lambda code automatically assigns a previously created EIP to a new instance launched in a Autoscaling group. You need to define "Lifecycle hooks" for an ASG for the lambda function to be triggered. To view the lifecycle hook:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-> ~ aws autoscaling describe-lifecycle-hooks --auto-scaling-group-name Lambda_EIP_TEST
{
    "LifecycleHooks": [
        {
            "GlobalTimeout": 172800, 
            "HeartbeatTimeout": 3600, 
            "AutoScalingGroupName": "Lambda_EIP_TEST", 
            "LifecycleHookName": "Lambda-OpenVPN-Hook", 
            "DefaultResult": "ABANDON", 
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
        }
    ]
}

Lambda code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
console.log('Loading function');
var AWS = require('aws-sdk');

exports.handler = function(event, context) {
console.log('AutoScalingLaunchTerminate()');
console.log('Here is the event:', JSON.stringify(event, null, 2));
console.log('Event.Instance Id: ', event.detail.EC2InstanceId);

var Lifecycleparams = {
   AutoScalingGroupName: event.detail.AutoScalingGroupName, /* required */
   LifecycleActionResult: 'CONTINUE', /* required */
   LifecycleHookName: event.detail.LifecycleHookName, /* required */
   LifecycleActionToken: event.detail.LifecycleActionToken
};
var autoscaling = new AWS.AutoScaling();
console.log('Before completeLifeCycleAction .....:', Lifecycleparams.LifecycleActionToken);
autoscaling.completeLifecycleAction(Lifecycleparams, function(err, data) {
   if (err) {
      console.log(err, err.stack);
   } // an error occurred
   else {
      console.log(data);
      var params = {
         AllocationId: 'eipalloc-d65c42b3',
         AllowReassociation: true,
         DryRun: false,
         InstanceId: event.detail.EC2InstanceId
   };
   var ec2 = new AWS.EC2();
   console.log('Before associateAddress .....:', params.InstanceId);
   ec2.associateAddress(params, function(err, data) {
      if (err) {console.log(err, err.stack);} // an error occurred
      else { console.log(data); context.succeed('Ready');} // successful response
   });
   console.log('After associateAddress .....:', params.InstanceId);
   } // successful response
});
console.log('After completeLifeCycleAction .....:', Lifecycleparams.LifecycleActionToken);
};

Make sure that the Lambda function has access to the AWS resources (execution role: http://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html). In this case it is autoscaling CompleteLifecycleAction and  EC2 AssociateAddress. During the creation of the Lambda function you have an option to choose or create a one-click IAM role.
Another thing to note is the association of the lambda function with a VPC. By default, lambda function runs in a default AWS VPC. You can also choose to run in your provided VPC. In this case, choose "No VPC", which will execute the function in the AWS VPC.
Event
The lambda function get executed on an Event. There are loads of events on which lambda can be triggered. In this case, it is the EC2 Launch event of autoscaling. For this to work, you need to enable ASG Lifecycle hooks. Follow steps in Scenario 4 of the Lambda example to configure the ASG Event rule and associate it with the Lambda function.
Logs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
START RequestId: c1a18c88-1cef-11e6-bf82-394cfdb4d343 Version: $LATEST
2016-05-18T11:57:55.397Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 AutoScalingLaunchTerminate()
2016-05-18T11:57:55.397Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 Here is the event:
{
"version": "0",
"id": "a2e7cb9c-8c6f-4ec3-9bc1-161122b32718",
"detail-type": "EC2 Instance-launch Lifecycle Action",
"source": "aws.autoscaling",
"account": "XXXXXXXXX",
"time": "2016-05-18T11:57:54Z",
"region": "eu-west-1",
"resources": [
"arn:aws:autoscaling:eu-west-1:XXXXXXXX:autoScalingGroup:38c88f38-9b16-4919-a641-1a2c2901b2d1:autoScalingGroupName/Lambda_EIP_TEST"
],
"detail": {
"LifecycleActionToken": "31089eef-6611-4cdb-8ad3-2e9a1abc3508",
"AutoScalingGroupName": "Lambda_EIP_TEST",
"LifecycleHookName": "Lambda-OpenVPN-Hook",
"EC2InstanceId": "i-80931d0c",
"LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
}
}
2016-05-18T11:57:55.397Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 Event.Instance Id: i-80931d0c
2016-05-18T11:57:55.398Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 Before completeLifeCycleAction .....: 31089eef-6611-4cdb-8ad3-2e9a1abc3508
2016-05-18T11:57:55.405Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 After completeLifeCycleAction .....: 31089eef-6611-4cdb-8ad3-2e9a1abc3508
2016-05-18T11:57:55.625Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 { ResponseMetadata: { RequestId: 'c1c4565f-1cef-11e6-8f64-cd1610bf3f3a' } }
2016-05-18T11:57:55.805Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 Before associateAddress .....: i-80931d0c
2016-05-18T11:57:55.863Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 After associateAddress .....: i-80931d0c
2016-05-18T11:57:56.179Z c1a18c88-1cef-11e6-bf82-394cfdb4d343 { AssociationId: 'eipassoc-ca9bbcad' }
END RequestId: c1a18c88-1cef-11e6-bf82-394cfdb4d343
REPORT RequestId: c1a18c88-1cef-11e6-bf82-394cfdb4d343 Duration: 782.79 ms Billed Duration: 800 ms Memory Size: 128 MB Max Memory Used: 57 MB 

NOTE: In case you are not using the inline Node.js editor on the AWS console and have a CI setup which stores the code in GitHub or any other CVS and you then reference the code in Lambda via a ZIP file, MAKE SURE to set the HANDLER to the name of the ZIP.

Eg. If the zip file being uploaded via Terraform or AWS CLI is "assign-eip-eni.zip" then the handler should be set to: "assign-eip-eni.handler".