1
0

AutoScalingで、別VPCにENIを作ってEC2にアタッチ(ユーザーデータ編)

Posted at

はじめに

前回、AutoScalingでスケールアウト時に、別VPCに作っておいたENIをアタッチする仕組みを、ユーザーデータで実装した内容を記事にしました。

今回は、都度ENIを作ってアタッチする仕組みをユーザーデータで実装しました。

概要

ユーザーデータでインスタンス起動時に、以下を行います。

  1. ENIを作成
    • インスタンスとは別VPCに作ります
  2. インスタンスにアタッチ
  3. ”EC2終了後に自動削除を有効”の設定に変更

そのため、上記を行う権限をインスタンスに付与しています。

参考

以前、Step Functionsを使って同様のことをやっていますので、そちらも参考にしてください。

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
      Policies:
        - PolicyName: writeEni
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateNetworkInterface
                  - ec2:AttachNetworkInterface
                  - ec2:ModifyNetworkInterfaceAttribute
                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

  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"

  ##################################################
  # 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}'`

                declare -A TargetSubnets=(
                  ["${AvailabilityZone1}"]="${PublicSubnetTarget1}"
                )

                NETWORKINTERFACEID=`aws ec2 create-network-interface \
                    --subnet-id "${!TargetSubnets[$TARGET_AZ]}" \
                    --groups "${SecurityGroupId}" \
                    --output text --query 'NetworkInterface.NetworkInterfaceId'`

                ATTACHEMENTID=`aws ec2 attach-network-interface \
                    --network-interface-id $NETWORKINTERFACEID \
                    --instance-id $INSTANCE_ID \
                    --device-index 1 \
                    --output text --query 'AttachmentId'`

                aws ec2 modify-network-interface-attribute \
                  --network-interface-id $NETWORKINTERFACEID \
                  --attachment AttachmentId=$ATTACHEMENTID,DeleteOnTermination=true

              }
              eni_attach || sudo shutdown -h now
            - {
                PublicSubnetTarget1: !Ref PublicSubnetTarget1 ,
                AvailabilityZone1: !Ref AvailabilityZone1 ,
                SecurityGroupId: !Ref SecurityGroupForTarget
              }

  myASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - !Ref PublicSubnetSource1
      LaunchTemplate:
        LaunchTemplateId: !Ref myLaunchTemplate
        Version: !GetAtt myLaunchTemplate.LatestVersionNumber
      MaxSize: '1'
      MinSize: '0'
      DesiredCapacity: '1'

シェルの解説

以下の部分は、"対象のアベイラビリティゾーン"=>"作成する(別VPCの)サブネット"の情報を格納する連想配列です。

declare -A TargetSubnets=(
  ["${AvailabilityZone1}"]="${PublicSubnetTarget1}"
)

今回はAZが1つなので1要素ですが、複数のAZに作る場合はその分の情報を要素として追加します。

連想配列は以下を参考にしました。

ユーザーデータの中でシェル変数を使う場合は、${!HOGEHOGE}と、!を入れて使います。

NETWORKINTERFACEID=`aws ec2 create-network-interface \
    --subnet-id "${!TargetSubnets[$TARGET_AZ]}" \
    --groups "${SecurityGroupId}" \
    --output text --query 'NetworkInterface.NetworkInterfaceId'`

以下を参考にしました。

実行結果

CFnテンプレートを実行すると、EC2インスタンスが1つ作成され、別VPCのENIがアタッチされています。
image.png

二つとも、終了時に削除する設定になっています。
image.png

AutoScalingの設定を変更して2台にすると、2台目も同様に2つのENIがアタッチされます。
image.png

ENIも、1台目と合わせて4つ作られています。
image.png

おわりに

今回はユーザーデータを使って、別VPCにENIを作ってEC2にアタッチさせてみました。
前回と同様、アタッチのタイミングがStep Functionsより早いはずです。

一方、EC2に余分な権限(ENIの作成・アタッチ・変更)を付けていますので、その点許容する必要があります。

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

1
0
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
1
0