Sohaib walks you through attaching
a second ENI to your instance automatically

attach-second-eni-auto-scaling-sohaib

I want to attach a second ENI automatically when Auto Scaling spins up a new instance during scale out or health check replacements.

Auto Scaling does not directly support this use case, but using a combination of Auto Scaling lifecycle hooks, Amazon CloudWatch Events, and AWS Lambda functions will perform a similar function. Configure the following:

  • Create a lifecycle hook that sends the event "EC2 Instance-launch Lifecycle Action" when Auto Scaling launches a new instance.
  • Set that event to trigger a Lambda function.
  • Set the Lambda function to automatically attach the second ENI to the instance when the instance is in the wait state.

From the AWS Lambda console, create a Lambda function that automatically adds a second ENI to an instance when Auto Scaling launches it:

1.    Choose Create a Lambda function. When prompted to select a blueprint, choose Skip.

2.    Add Attach_Second_ENI_ASG to the Name field, and add the following to the Lambda function code field:

import boto3

import botocore

from datetime import datetime

 

ec2_client = boto3.client('ec2')

asg_client = boto3.client('autoscaling')

 

def lambda_handler(event, context):

 

    if event["detail-type"] == "EC2 Instance-launch Lifecycle Action":

        instance_id = event["detail"]["EC2InstanceId"]

        subnet_id = get_subnet_id(instance_id)

        interface_id = create_interface(subnet_id)

        attachment = attach_interface(interface_id, instance_id)

 

        if interface_id and attachment == None:

        log("Removing network interface {} after attachment failed.".format(interface_id))

        delete_interface(interface_id)

   

        try:

            asg_client.complete_lifecycle_action(

                LifecycleHookName = event['detail']['LifecycleHookName'],

                AutoScalingGroupName = event['detail']['AutoScalingGroupName'],

                LifecycleActionToken = event['detail']['LifecycleActionToken'],

                LifecycleActionResult = 'CONTINUE'

                )

                if attachment:

                    log('{"Error": "0"}')

                else:

                    log('{"Error": "1"}')

        except botocore.exceptions.ClientError as e:

            log("Error completing life cycle hook for instance {}: {}".format(instance_id, e.response['Error']['Code']))

            log('{"Error": "1"}')

   

   

def get_subnet_id(instance_id):

    try:

        result = ec2_client.describe_instances(InstanceIds=[instance_id])

        vpc_subnet_id = result['Reservations'][0]['Instances'][0]['SubnetId']

        log("Subnet id: {}".format(vpc_subnet_id))

   

    except botocore.exceptions.ClientError as e:

        log("Error describing the instance {}: {}".format(instance_id, e.response['Error']['Code']))

        vpc_subnet_id = None

   

    return vpc_subnet_id

def create_interface(subnet_id):

    network_interface_id = None

   

    if subnet_id:

            try:

                network_interface = ec2_client.create_network_interface(SubnetId=subnet_id)

                network_interface_id = network_interface['NetworkInterface']['NetworkInterfaceId']

                log("Created network interface: {}".format(network_interface_id))

            except botocore.exceptions.ClientError as e:

                log("Error creating network interface: {}".format(e.response['Error']['Code']))

   

    return network_interface_id

   

def attach_interface(network_interface_id, instance_id):

    attachment = None

   

    if network_interface_id and instance_id:

        try:

            attach_interface = ec2_client.attach_network_interface(

                NetworkInterfaceId=network_interface_id,

                InstanceId=instance_id,

                DeviceIndex=1

            )

            attachment = attach_interface['AttachmentId']

            log("Created network attachment: {}".format(attachment))

        except botocore.exceptions.ClientError as e:

            log("Error attaching network interface: {}".format(e.response['Error']['Code']))

   

    return attachment

   

def delete_interface(network_interface_id):

    try:

        ec2_client.delete_network_interface(

            NetworkInterfaceId=network_interface_id

        )

    except botocore.exceptions.ClientError as e:

        log("Error deleting interface {}: {}".format(network_interface_id, e.response['Error']['Code']))

   

def log(message):

    print (datetime.utcnow().isoformat() + 'Z ' + message)

3.    For Role, either choose an IAM role that allows Lambda to describe, create, and attach an interface to an instance, or choose Basic execution role.

If you chose Basic execution role, an IAM console window opens, and you are prompted to define a policy for lambda_basic_execution. Expand the policy document dropdown, and add the following policy:

{

    "Statement": [

        {

            "Action": [

                "logs:CreateLogGroup",

                "logs:CreateLogStream",

                "logs:PutLogEvents"

            ],

            "Effect": "Allow",

            "Resource": "arn:aws:logs:*:*:*"

        },

        {

            "Action": [

                "ec2:CreateNetworkInterface",

                "ec2:DescribeNetworkInterfaces",

                "ec2:DetachNetworkInterface",

                "ec2:DeleteNetworkInterface",

                "ec2:AttachNetworkInterface",

                "ec2:DescribeInstances",

                "autoscaling:CompleteLifecycleAction"

            ],

            "Effect": "Allow",

            "Resource": "*"

        }

    ],

    "Version": "2012-10-17"

}}

4.    Choose Allow. The IAM console window closes, and the lambda_basic_execution role is selected in the Role dropdown.

5.    Choose Next.

6.    Choose Create function.

Create a lifecycle hook to trigger your event using the following CLI command:

aws autoscaling put-lifecycle-hook \

    --lifecycle-hook-name my-lifecycle-launch-hook \

    --auto-scaling-group-name my-asg \

    --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING \

    --heartbeat-timeout 300 \

    --default-result CONTINUE

Open the CloudWatch console and create a CloudWatch Events rule to trigger your Lambda function by performing the following steps:

1.    From the left navigation pane, select Events.

2.    Choose Create rule.

3.    From the Select event source dropdown, choose Auto Scaling.

4.    Choose Specific instance event(s), and select EC2 Instance-launch Lifecycle Action.

5.    Choose Specific group name(s), and select the Auto Scaling group or groups that want to auto-attach a second ENI to on launch.

6.    On the right side of the screen, select Add target. Lambda function is automatically chosen from the predefined list of targets.

7.    Choose Configure details.

If configured correctly, when Auto Scaling launches a new instance, the second ENI you specified will be attached to the new instance.

Note: If you are not using the Amazon Linux AMI in your launch configuration, you might need to do some additional configuration on the OS level to bring up the additional interface.

You also need to write code that deletes the second ENI when Auto Scaling terminates the instance to avoid exhausting private IP addresses in the subnet and reaching the ENI limit in your account. A similar approach can be used to use a Termination lifecycle hook and trigger a Lambda function through CloudWatch Events or Amazon Simple Notification Service (Amazon SNS) to detach and delete the interface with DeviceIndex > 0. Or, you can write a boto script that periodically deletes all the unused interfaces in your account.

You can also use SNS instead of CloudWatch Events to invoke your lambda function by configuring --notification-target-arn when creating the hook. This can be ideal if you are seeking cross-account or cross-region support, because CloudWatch Events currently does not support that.

For additional information, see Using AWS Lambda with Amazon SNS from Different Accounts.


Did this page help you? Yes | No

Back to the AWS Support Knowledge Center

Need help? Visit the AWS Support Center

Published: 2016-04-29