2
1

AutoScalingで、別VPCにENIを作ってEC2にアタッチ

Posted at

はじめに

以前、AutoScalingでスケールアウト時に、別VPCに作っておいたENIをアタッチする内容を記事にしました。

本方法だと、事前にENIを作成しておきます。

今回は、都度ENIを作ってアタッチするようにしてみました。

概要

  • AutoScalingグループにライフサイクルフックを追加して、EventBridge -> StepFunctionsでアタッチします
  • StepFunctionsで、ENIを作成しアタッチします
    • ENIはアタッチ後に、”EC2終了後に自動削除を有効”の設定にします

構成図

image.png

CloudFormationテンプレート

長いですが、CloudFormationは以下になります。

クリックで表示

AZは一つだけです。

AWSTemplateFormatVersion: '2010-09-09'

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Parameters
        Parameters:
          - VpcNameSource
          - VpcCIDRSource
          - VpcNameTarget
          - VpcCIDRTarget
          - ImageId
          - InstanceTypeName
          - AvailabilityZone1

Parameters:

  VpcNameSource:
    Type: String
    Default: sourceVpc
    Description: Name of the VPC
  VpcCIDRSource:
    Type: String
    Default: 10.79.0.0/16
    Description: CIDR block for the VPC

  VpcNameTarget:
    Type: String
    Default: targetVpc
    Description: Name of the VPC
  VpcCIDRTarget:
    Type: String
    Default: 10.80.0.0/16
    Description: CIDR block for the VPC

  ImageId:
    Type: String
    Default: ami-012261b9035f8f938
  InstanceTypeName:
    Type: String
    Default: t3.nano
  AvailabilityZone1:
    Type: AWS::EC2::AvailabilityZone::Name

Resources:
  ##################################################
  # Source VPC
  ##################################################
  VPCSource:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VpcCIDRSource
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VpcNameSource

  InternetGatewaySource:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameSource}-igw

  VPCGatewayAttachmentSource:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref VPCSource
      InternetGatewayId: !Ref InternetGatewaySource

  PublicSubnetSource1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPCSource
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Select [ 0, !Cidr [ !Ref VpcCIDRSource, 24, 8 ] ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameSource}-subnet

  PublicRouteTableSource:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPCSource
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameSource}-rtb-public
  PublicRouteSource:
    Type: 'AWS::EC2::Route'
    DependsOn: VPCGatewayAttachmentSource
    Properties:
      RouteTableId: !Ref PublicRouteTableSource
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGatewaySource

  SubnetRouteTableAssociationSource:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref PublicSubnetSource1
      RouteTableId: !Ref PublicRouteTableSource

  SecurityGroupForSource:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPCSource
      GroupDescription: "Source VPC SG"

  ##################################################
  # EC2
  ##################################################
  SessionManagerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  InstanceProfile:
    DependsOn: SessionManagerRole
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: '/'
      Roles:
        - !Ref SessionManagerRole

  # LaunchTemplate
  myLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        ImageId: !Ref ImageId
        InstanceType: !Ref InstanceTypeName
        IamInstanceProfile:
          Name: !Ref InstanceProfile
        SecurityGroupIds:
        - !GetAtt SecurityGroupForSource.GroupId

  myASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - !Ref PublicSubnetSource1
      LaunchTemplate:
        LaunchTemplateId: !Ref myLaunchTemplate
        Version: !GetAtt myLaunchTemplate.LatestVersionNumber
      MaxSize: '1'
      MinSize: '0'
      DesiredCapacity: '1'
      LifecycleHookSpecificationList:
        - DefaultResult: CONTINUE
          HeartbeatTimeout: 60
          LifecycleHookName: testLifeCycleHook
          LifecycleTransition: autoscaling:EC2_INSTANCE_LAUNCHING
          NotificationMetadata: message

  ##################################################
  # Target VPC
  ##################################################
  VPCTarget:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VpcCIDRTarget
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VpcNameTarget

  InternetGatewayTarget:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameTarget}-igw

  VPCGatewayAttachmentTarget:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref VPCTarget
      InternetGatewayId: !Ref InternetGatewayTarget

  PublicSubnetTarget1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPCTarget
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Select [ 0, !Cidr [ !Ref VpcCIDRTarget, 24, 8 ] ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameTarget}-subnet

  PublicRouteTableTarget:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPCTarget
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameTarget}-rtb-public
  PublicRouteTarget:
    Type: 'AWS::EC2::Route'
    DependsOn: VPCGatewayAttachmentTarget
    Properties:
      RouteTableId: !Ref PublicRouteTableTarget
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGatewayTarget

  SubnetRouteTableAssociationTarget:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref PublicSubnetTarget1
      RouteTableId: !Ref PublicRouteTableTarget

  SecurityGroupForTarget:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPCTarget
      GroupDescription: "Target VPC SG"

  ##################################################
  # Role 
  ##################################################
  # for StepFunctions
  StepFunctionsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'states.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      Policies:
        - PolicyName: writeEni
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateNetworkInterface
                  - ec2:AttachNetworkInterface
                  - ec2:ModifyNetworkInterfaceAttribute
                Resource: '*'

  # for EventBridge
  EventBridgeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'events.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      Policies:
        - PolicyName: invokeEb
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - states:StartExecution
                Resource: '*'

  ##################################################
  # StateMachine
  ##################################################
  MyStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      StateMachineName: attachEniOnOtherVpc-StateMachine
      DefinitionString: !Sub |-
        {
          "Comment": "A description of my state machine",
          "StartAt": "AddSubnetMap",
          "States": {
            "AddSubnetMap": {
              "Type": "Pass",
              "Result": {
                "SecurityGroupId": "${SecurityGroupForTarget}",
                "TargetSubnets": [
                  {
                    "Availability Zone": "${AvailabilityZone1}",
                    "Subnet ID": "${PublicSubnetTarget1}"
                  }
                ]
              },
              "ResultPath": "$.SubnetMap",
              "Next": "MakeParameter"
            },
            "MakeParameter": {
              "Type": "Pass",
              "Next": "CreateNetworkInterface",
              "Parameters": {
                "TargetSubnet.$": "$.SubnetMap.TargetSubnets[?(@.['Availability Zone']==$['detail']['Details']['Availability Zone'])]",
                "Groups.$": "States.Array($.SubnetMap.SecurityGroupId)"
              },
              "ResultPath": "$.TargetParameter"
            },
            "CreateNetworkInterface": {
              "Type": "Task",
              "Parameters": {
                "SubnetId.$": "$.TargetSubnet[0]['Subnet ID']",
                "Groups.$": "$.Groups"
              },
              "Resource": "arn:aws:states:::aws-sdk:ec2:createNetworkInterface",
              "ResultPath": "$.CreatedNetworkInterface",
              "InputPath": "$.TargetParameter",
              "Next": "AttachNetworkInterface"
            },
            "AttachNetworkInterface": {
              "Type": "Task",
              "Parameters": {
                "DeviceIndex": 1,
                "InstanceId.$": "$.detail.EC2InstanceId",
                "NetworkInterfaceId.$": "$.CreatedNetworkInterface.NetworkInterface.NetworkInterfaceId"
              },
              "Resource": "arn:aws:states:::aws-sdk:ec2:attachNetworkInterface",
              "ResultPath": "$.AttachedNetworkInterface",
              "Next": "ModifyNetworkInterfaceAttribute"
            },
            "ModifyNetworkInterfaceAttribute": {
              "Type": "Task",
              "End": true,
              "Parameters": {
                "NetworkInterfaceId.$": "$.CreatedNetworkInterface.NetworkInterface.NetworkInterfaceId",
                "Attachment": {
                  "AttachmentId.$": "$.AttachedNetworkInterface.AttachmentId",
                  "DeleteOnTermination": "true"
                }
              },
              "Resource": "arn:aws:states:::aws-sdk:ec2:modifyNetworkInterfaceAttribute"
            }
          }
        }
      RoleArn: !GetAtt StepFunctionsRole.Arn

  ##################################################
  # Events::Rule
  ##################################################
  MyNewEventsRule:
    Type: 'AWS::Events::Rule'
    Properties:
      Description: Test Events Rule
      Name: catchScaleout
      EventPattern:
        source:
          - aws.autoscaling
        detail-type: 
          - "EC2 Instance Launch Successful"
      State: ENABLED
      Targets:
        - Arn: !GetAtt MyStateMachine.Arn
          Id: Id1234
          RoleArn: !GetAtt EventBridgeRole.Arn

StepFunctionsの説明

フローは以下になります。
image.png

以下のような動きをします。

  1. AddSubnetMap
    • ENI作成時に指定する、サブネットIDとセキュリティグループIDの定義情報をセットします
      • AZと、対応する作成先のサブネットIDの対応情報として追加します
      • セキュリティグループはどのAZでも同じものを使うので、AZに依らず共通情報として追加します
    • AZが複数ある場合は、ここにAZの数だけ定義を書きます
  2. MakeParameter
    • スケールアウトしたEC2のAZの情報を用いて、AddSubnetMapでセットした情報からパラメーターに使う情報をフィルターします
  3. CreateNetworkInterface
    • フィルターした情報を使って、ENIを作ります
  4. AttachNetworkInterface
    • 作ったENIを、スケールアウトしたEC2にアタッチします
  5. ModifyNetworkInterfaceAttribute
    • EC2終了時にENIが自動で削除されるように設定変更します

実行結果

CFnテンプレートを実行すると、EC2インスタンスが1つ作成され、別VPCのENIがアタッチされています。
image.png
二つとも、終了時に削除する設定になっています。
image.png
ENIも2つだけです。
image.png

ここで、動いているEC2を終了して、既存のENIが削除され、新しくENIが作られるか見ました。
image.png
ENIも確認し、以前のENIは削除されていることを確認しました。
image.png

AutoScalingの設定を変更して2台動かすと、ENIが4つ(同VPCのと、別VPCのの2対が、2台分)作成されました。
image.png

また1台に戻してみます。
片方のENIが開放されました。
image.png

おわりに

今回はAutoScalingでスケールアウト時に、別VPCにENIを作成してアタッチするような仕組みを作ってみました。
あらかじめENIを作る方法と比較して、スケールアウト最大数に応じたENIを作っておく必要がない点がよいかと思います。
ただIPを固定したい場合は本方法ではなく、事前にENIを作っておく方法がよいかと思います。

この記事がどなたかのお役に立てれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1