はじめに
以前、AutoScalingでスケールアウト時に、別VPCに作っておいたENIをアタッチする内容を記事にしました。
本方法だと、事前にENIを作成しておきます。
今回は、都度ENIを作ってアタッチするようにしてみました。
概要
- AutoScalingグループにライフサイクルフックを追加して、EventBridge -> StepFunctionsでアタッチします
- StepFunctionsで、ENIを作成しアタッチします
- ENIはアタッチ後に、”EC2終了後に自動削除を有効”の設定にします
構成図
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の説明
以下のような動きをします。
- AddSubnetMap
- ENI作成時に指定する、サブネットIDとセキュリティグループIDの定義情報をセットします
- AZと、対応する作成先のサブネットIDの対応情報として追加します
- セキュリティグループはどのAZでも同じものを使うので、AZに依らず共通情報として追加します
- AZが複数ある場合は、ここにAZの数だけ定義を書きます
- ENI作成時に指定する、サブネットIDとセキュリティグループIDの定義情報をセットします
- MakeParameter
- スケールアウトしたEC2のAZの情報を用いて、AddSubnetMapでセットした情報からパラメーターに使う情報をフィルターします
- CreateNetworkInterface
- フィルターした情報を使って、ENIを作ります
- AttachNetworkInterface
- 作ったENIを、スケールアウトしたEC2にアタッチします
- ModifyNetworkInterfaceAttribute
- EC2終了時にENIが自動で削除されるように設定変更します
実行結果
CFnテンプレートを実行すると、EC2インスタンスが1つ作成され、別VPCのENIがアタッチされています。
二つとも、終了時に削除する設定になっています。
ENIも2つだけです。
ここで、動いているEC2を終了して、既存のENIが削除され、新しくENIが作られるか見ました。
ENIも確認し、以前のENIは削除されていることを確認しました。
AutoScalingの設定を変更して2台動かすと、ENIが4つ(同VPCのと、別VPCのの2対が、2台分)作成されました。
おわりに
今回はAutoScalingでスケールアウト時に、別VPCにENIを作成してアタッチするような仕組みを作ってみました。
あらかじめENIを作る方法と比較して、スケールアウト最大数に応じたENIを作っておく必要がない点がよいかと思います。
ただIPを固定したい場合は本方法ではなく、事前にENIを作っておく方法がよいかと思います。
この記事がどなたかのお役に立てれば幸いです。