How do I automatically attach a second elastic network interface (ENI) to an instance launched through Auto Scaling?

Last updated: 2019-05-10

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

Short Description

Auto Scaling does not directly support this use case, but by using a combination of Auto Scaling lifecycle hooks, Amazon CloudWatch Events, and AWS Lambda functions, you can set up and use 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 network interface to the instance when the instance is in the wait state.


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

1.    Choose Create function.

2.    Add Attach_Second_ENI_ASG to the Name field, and choose Python 2.7 for Runtime.

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

If you chose to create a custom role, an IAM console window opens. For IAM Role, choose Create a new IAM Role. Then expand the policy document dropdown, and add the following policy:

                "Statement": [
                        "Action": [
                        "Effect": "Allow",
                        "Resource": "arn:aws:logs:*:*:*"
                        "Action": [
                        "Effect": "Allow",
                        "Resource": "*"
                "Version": "2012-10-17"

Then, choose Allow. The IAM console window closes. On the Lambda console, select the new role for Existing role.

4.    Choose Create function.

5.    Add the following to the Function code field, and then choose Save:

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 not interface_id:
        elif not attachment:
def get_subnet_id(instance_id):
        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']))
        vpc_subnet_id = None

    return vpc_subnet_id

def create_interface(subnet_id):
    network_interface_id = None

    if subnet_id:
            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']))

    return network_interface_id

def attach_interface(network_interface_id, instance_id):
    attachment = None

    if network_interface_id and instance_id:
            attach_interface = ec2_client.attach_network_interface(
            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']))

    return attachment

def delete_interface(network_interface_id):
        log("Deleted network interface: {}".format(network_interface_id))
        return True

    except botocore.exceptions.ClientError as e:
        log("Error deleting interface {}: {}".format(network_interface_id, e.response['Error']))
def complete_lifecycle_action_success(hookname,groupname,instance_id):
        log("Lifecycle hook CONTINUEd for: {}".format(instance_id))
    except botocore.exceptions.ClientError as e:
            log("Error completing life cycle hook for instance {}: {}".format(instance_id, e.response['Error']))
            log('{"Error": "1"}')    
def complete_lifecycle_action_failure(hookname,groupname,instance_id):
        log("Lifecycle hook ABANDONed for: {}".format(instance_id))
    except botocore.exceptions.ClientError as e:
            log("Error completing life cycle hook for instance {}: {}".format(instance_id, e.response['Error']))
            log('{"Error": "1"}')    

def log(error):
    print('{}Z {}'.format(datetime.utcnow().isoformat(), error))

Create a lifecycle hook to trigger your event using the following AWS Command Line Interface (AWS 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 ABANDON

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 Rules under Events.

2.    Choose Create rule.

3.    For Service Name, choose Auto Scaling.

4.    For Event Type, choose Instance Launch and Terminate.

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

6.    Choose Specific group name(s), and then select the Auto Scaling group or groups to auto-attach a second network interface on launch.

7.    Select Add target, and then select the new Lambda function.

8.    Choose Configure details.

9.    Add a name to the rule, and then choose Create rule.

Now, when Auto Scaling launches a new instance, the second network interface you specified is attached to the new instance.

Note: If you're not using the Amazon Linux AMI in your launch configuration, you might need to configure additional options on the OS level to bring up the additional interface.

To avoid exhausting private IP addresses in the subnet and reaching the ENI limit in your account, you must write code that deletes the second network interface when Auto Scaling terminates the instance:

  • You can use a Termination lifecycle hook, and then trigger a Lambda function through CloudWatch Events or Amazon Simple Notification Service (Amazon SNS) to detach and delete the interface with DeviceIndex > 0.
  • You can also write a boto script that periodically deletes the unused interfaces in your account, or you can modify the 'delete on termination' attribute of the network interface using the modify-network-interface-attribute AWS CLI command.

You can also use SNS instead of CloudWatch Events to invoke your Lambda function by configuring --notification-target-arn when creating the hook.

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