두 번째 ENI(탄력적 네트워크 인터페이스)를 Auto Scaling을 통해 시작된 인스턴스에 자동으로 연결하려면 어떻게 해야 합니까?

최종 업데이트 날짜: 2019년 5월 10일

확장 또는 상태 확인 대체 중 Auto Scaling에서 새 인스턴스를 가동할 때 두 번째 탄력적 네트워크 인터페이스를 자동으로 연결하고 싶습니다.

간략한 설명

Auto Scaling은 이 사용 사례를 직접 지원하지는 않지만, Auto Scaling 수명 주기 후크, Amazon CloudWatch Events 및 AWS Lambda 함수를 함께 사용하면 비슷한 함수를 설정하고 사용할 수 있습니다.

다음을 구성합니다.

  • Auto Scaling에서 새 인스턴스를 시작할 때 "EC2 인스턴스-시작 수명 주기 작업" 이벤트를 전송하는 수명 주기 후크를 생성합니다.
  • Lambda 함수를 트리거하도록 해당 이벤트를 설정합니다.
  • 인스턴스가 대기 상태일 때 두 번째 네트워크 인터페이스를 인스턴스에 자동으로 연결하도록 Lambda 함수를 설정합니다.

​해결 방법

Amazon Lambda 콘솔에서, Auto Scaling이 인스턴스를 시작할 때 두 번째 네트워크 인터페이스를 해당 인스턴스에 자동으로 추가하는 Lambda 함수를 생성합니다.

1.    [함수 생성]을 선택합니다.

2.    [이름] 필드에 Attach_Second_ENI_ASG를 추가하고 런타임에 대해 [Python 2.7]을 선택합니다.

3.    역할의 경우 Lambda에서 설명하고, 생성하고 인스턴스에 인터페이스를 연결할 수 있는 IAM 역할을 선택하거나 [사용자 지정 역할 생성]을 선택합니다.

사용자 지정 역할을 생성하도록 선택한 경우 IAM 콘솔 창이 열립니다. [IAM 역할]에서 [새 IAM 역할 생성]을 선택합니다. 그런 다음, 정책 문서 드롭다운을 확장하고 다음 정책을 추가합니다.

{
                "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"
            }

그런 다음, [허용]을 선택합니다. IAM 콘솔 창을 닫습니다. Lambda 콘솔에서 [기존 역할]에 대한 새 역할을 선택합니다.

4.    [함수 생성]을 선택합니다.

5.    [함수 코드] 필드에 다음을 추가하고 [저장]을 선택합니다.

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']
        LifecycleHookName=event['detail']['LifecycleHookName']
        AutoScalingGroupName=event['detail']['AutoScalingGroupName']
        subnet_id = get_subnet_id(instance_id)
        interface_id = create_interface(subnet_id)
        attachment = attach_interface(interface_id, instance_id)
        if not interface_id:
            complete_lifecycle_action_failure(LifecycleHookName,AutoScalingGroupName,instance_id)
        elif not attachment:
            complete_lifecycle_action_failure(LifecycleHookName,AutoScalingGroupName,instance_id)
            delete_interface(interface_id)
        else:
            complete_lifecycle_action_success(LifecycleHookName,AutoScalingGroupName,instance_id)
       
        
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']))
        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']))

    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']))

    return attachment


def delete_interface(network_interface_id):
    try:
        ec2_client.delete_network_interface(
            NetworkInterfaceId=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):
    try:
        asg_client.complete_lifecycle_action(
                LifecycleHookName=hookname,
                AutoScalingGroupName=groupname,
                InstanceId=instance_id,
                LifecycleActionResult='CONTINUE'
            )
        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):
    try:
        asg_client.complete_lifecycle_action(
                LifecycleHookName=hookname,
                AutoScalingGroupName=groupname,
                InstanceId=instance_id,
                LifecycleActionResult='ABANDON'
            )
        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))

다음 AWS CLI(명령줄 인터페이스) 명령을 사용하여 이벤트를 트리거하는 수명 주기 후크를 생성합니다.

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

CloudWatch 콘솔을 열고 다음 단계를 수행하여 Lambda 함수를 트리거하는 CloudWatch Events 규칙을 생성합니다.

1.    왼쪽 탐색 창에서 이벤트 아래 [규칙]을 선택합니다.

2.    [규칙 생성]을 선택합니다.

3.    [서비스 이름]에서 [Auto Scaling]을 선택합니다.

4.    [이벤트 유형]에서 [인스턴스 시작 및 종료]를 선택합니다.

5.    [특정 인스턴스 이벤트]를 선택하고 [EC2 인스턴스-시작 수명 주기 작업]을 선택합니다.

6.    [특정 그룹 이름]을 선택하고 시작 시 두 번째 네트워크 인터페이스를 자동 연결할 하나 이상의 Auto Scaling 그룹을 선택합니다.

7.    [대상 추가]를 선택하고 새 Lambda 함수를 선택합니다.

8.    [세부 정보 구성]을 선택합니다.

9.    규칙에 이름을 추가하고 [규칙 생성]을 선택합니다.

이제 Auto Scaling에서 새 인스턴스를 시작하면 사용자가 지정한 두 번째 네트워크 인터페이스가 새 인스턴스에 연결됩니다.

참고: 시작 구성에서 Amazon Linux AMI를 사용하지 않는 경우 추가 인터페이스를 가져오기 위해 OS 수준에서 추가 옵션을 구성해야 할 수도 있습니다.

서브넷에서 프라이빗 IP 주소를 소진하거나 계정에서 ENI 한도에 도달하지 않도록 하려면 Auto Scaling에서 인스턴스를 종료할 때 두 번째 네트워크 인터페이스를 삭제하는 코드를 작성해야 합니다.

  • 종료 수명 주기 후크를 사용하고 CloudWatch Events 또는 Amazon Simple Notification Service(Amazon SNS)를 통해 Lambda 함수를 트리거하여 DeviceIndex > 0에서 인터페이스를 분리하고 삭제할 수 있습니다.
  • 또한 계정에서 사용하지 않는 인터페이스를 정기적으로 삭제하는 boto 스크립트를 작성하거나 modify-network-interface-attribute AWS CLI 명령을 사용하여 네트워크 인터페이스의 '종료 시 삭제 여부' 속성을 수정할 수 있습니다.

CloudWatch Events 대신, SNS를 사용하여 후크를 생성할 때 --notification-target-arn을 구성해 Lambda 함수를 호출할 수도 있습니다.

자세한 내용은 다른 계정의 Amazon SNS에서 AWS Lambda 사용을 참조하십시오.


이 문서가 도움이 되었습니까?

AWS에서 개선해야 할 부분이 있습니까?


도움이 필요하십니까?