はじめに
前回、AutoScalingでスケールアウト時に、別VPCに作っておいたENIをアタッチする仕組みを、ユーザーデータで実装した内容を記事にしました。
今回は、都度ENIを作ってアタッチする仕組みをユーザーデータで実装しました。
概要
ユーザーデータでインスタンス起動時に、以下を行います。
- ENIを作成
- インスタンスとは別VPCに作ります
- インスタンスにアタッチ
- ”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がアタッチされています。
AutoScalingの設定を変更して2台にすると、2台目も同様に2つのENIがアタッチされます。
おわりに
今回はユーザーデータを使って、別VPCにENIを作ってEC2にアタッチさせてみました。
前回と同様、アタッチのタイミングがStep Functionsより早いはずです。
一方、EC2に余分な権限(ENIの作成・アタッチ・変更)を付けていますので、その点許容する必要があります。
この記事がどなたかのお役に立てれば幸いです。