はじめに
以前、スケールアウトするEC2に別のVPCのENIをアタッチする仕組みをStep Functionsで作りました。
これを、ユーザーデータでやってみました。
概要
- AutoScalingでENIを追加でアタッチする機能はない
- ユーザーデータでインスタンス起動時に、ENIをアタッチするシェルを実行します
- 事前に別VPCにENIを作っておきます
 
- ENIは同じAZに作っておく必要があります
- EC2にENI関連の権限を付与しておく必要があります
参考
構成図
以前の方法と比較してStep Functionsが無い分、シンプルになっています。
CloudFormationテンプレート
CloudFormationは以下になります。
以前と同様、EniTarget01とEniTarget02という名前で、別VPC上に事前にENIを作成しています。
クリックで表示
AWSTemplateFormatVersion: '2010-09-09'
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Parameters
        Parameters:
          - VpcNameSource
          - VpcCIDRSource
          - VpcNameTarget
          - VpcCIDRTarget
          - ImageId
          - InstanceTypeName
          - AvailabilityZone
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
  AvailabilityZone:
    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
  PublicSubnetSource:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPCSource
      AvailabilityZone: !Ref AvailabilityZone
      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 PublicSubnetSource
      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
      Policies:
        - PolicyName: writeEni
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeNetworkInterfaces
                  - ec2:AttachNetworkInterface
                Resource: '*'
  InstanceProfile:
    DependsOn: SessionManagerRole
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: '/'
      Roles:
        - !Ref SessionManagerRole
  ##################################################
  # 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
  PublicSubnetTarget:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPCTarget
      AvailabilityZone: !Ref AvailabilityZone
      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 PublicSubnetTarget
      RouteTableId: !Ref PublicRouteTableTarget
  SecurityGroupForTarget:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPCTarget
      GroupDescription: "Target VPC SG"
  EniTarget01:
    Type: AWS::EC2::NetworkInterface
    Properties:
      Tags:
        - Key: Name
          Value: TargetEni01
      GroupSet:
        - !GetAtt SecurityGroupForTarget.GroupId
      SubnetId: !Ref PublicSubnetTarget
  EniTarget02:
    Type: AWS::EC2::NetworkInterface
    Properties:
      Tags:
        - Key: Name
          Value: TargetEni02
      GroupSet:
        - !GetAtt SecurityGroupForTarget.GroupId
      SubnetId: !Ref PublicSubnetTarget
  ##################################################
  # AutoScaling
  ##################################################
  # LaunchTemplate
  myLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        ImageId: !Ref ImageId
        InstanceType: !Ref InstanceTypeName
        IamInstanceProfile:
          Name: !Ref InstanceProfile
        SecurityGroupIds:
          - !GetAtt SecurityGroupForSource.GroupId
        UserData:
          Fn::Base64: !Sub
            - |
              #!/bin/bash
              function eni_attach() {
                INSTANCE_ID=`ec2-metadata -i | awk '{print $2}'`
                TARGET_AZ=`ec2-metadata -z | awk '{print $2}'`
                ALLOC_ID=$(/usr/bin/aws ec2 describe-network-interfaces \
                  --filters "Name=vpc-id, Values=${VPCTarget}" "Name=availability-zone, Values=$TARGET_AZ" "Name=status, Values=available" \
                  --query "sort_by(NetworkInterfaces, &PrivateIpAddress)[].NetworkInterfaceId | [0]" \
                  --output text)
                /usr/bin/aws ec2 attach-network-interface --instance-id=$INSTANCE_ID \
                  --device-index=1 --network-interface-id=$ALLOC_ID
              }
              eni_attach || sudo shutdown -h now
            - {
                VPCTarget: !Ref VPCTarget
              }
  myASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - !Ref PublicSubnetSource
      LaunchTemplate:
        LaunchTemplateId: !Ref myLaunchTemplate
        Version: !GetAtt myLaunchTemplate.LatestVersionNumber
      MaxSize: '1'
      MinSize: '0'
      DesiredCapacity: '1'
ユーザーデータは、以下のサイトを参考にしました。ほぼ同じなので詳細は以下をご覧ください。
インスタンスのVPCやAZの情報は、以下のサイトを参考にec2-metadataで取得しています。
実行結果
CFnテンプレートを実行すると、10.80.0.0/16にENIが作られます。

加えて、EC2インスタンスが1つ作成され、別VPCのENIがアタッチされています。

この1台を終了して、AutoScalingで立ち上げさせます。
別のマシンが立ち上がり、そちらでもENIがアタッチされました。

AutoScalingの設定を変更して、2台動かすようにしてみます。
2台目にもアタッチされました。

おわりに
今回はユーザーデータを使って、スケールアウト時にEC2に別VPCのENIをアタッチしました。
以前のStep Functionsで行う方法では、スケールアウト成功のイベント時をトリガーにしていましたが、今回はEC2起動時なのでENIがアタッチするタイミングは早くなったと思われます。
一方、EC2に余分な権限(ENIを検索・アタッチ)を付けていますので、その点許容する必要があります。
この記事がどなたかのお役に立てれば幸いです。



