[CFn]2アカウント間のVPCをVPC Peeringで接続する
2アカウント間のVPCをVPC Peeringで接続する
DevelopersIO BASECAMP一期参加者の加藤です。
今回はアカウント跨ぎで2つのVPCをピアリング接続してみたいと思います。
以下が構成図になります。
VPC Peeringの概要
以下ドキュメントになります。
作成手順
アカウント1でこのテンプレートを実行してください。
「PJPrefix」パラメーターは好きな文字列、「Account2AccountId」には接続する先のアカウントのアカウントIDを入力してください。
AWSTemplateFormatVersion: "2010-09-09"
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Stack
Parameters:
- PJPrefix
- Label:
default: for This Account
Parameters:
- VPCCidrBlock
- PrivateSubnetCidrBlock
- ImageId
- InstanceType
- Label:
default: for opposite Account
Parameters:
- Account2AccountId
- Account2PrivateSubnetCidrBlock
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
PJPrefix: # リソース名やNameタグ値の接頭辞に利用
Type: String
VPCCidrBlock: # VPCのCidr
Type: String
Default: 10.1.0.0/16
PrivateSubnetCidrBlock: # EC2インスタンスのCidr
Type: String
Default: 10.1.0.0/24
ImageId: # EC2のImageId
Type: String
Default: ami-079a2a9ac6ed876fc
InstanceType: # EC2のInstanceType
Type: String
Default: t2.micro
Account2AccountId: # 対向アカウントのアカウントID
Type: String
MinLength: 12
MaxLength: 12
Account2PrivateSubnetCidrBlock:
Type: String
Default: 10.2.0.0/24
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# Role
# ------------------------------------------------------------#
VPCPeeringRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${PJPrefix}-vpcpeeringrole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
AWS: !Ref Account2AccountId
Policies:
- PolicyName: 'AcceptVpcPeering'
PolicyDocument:
Statement:
- Effect: Allow
Action: 'ec2:AcceptVpcPeeringConnection'
Resource: '*'
#≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡#
# Role
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- ec2.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# InstanceProfile
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref EC2Role
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-vpc
# ------------------------------------------------------------#
# VPCEndpoint
# ------------------------------------------------------------#
VPCEndpointSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
VPCEndpointSSMMessages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
VPCEndpointEC2Messages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
VPCEndpointS3:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref PrivateSubnetRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-rtb-for-private-subnet
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateSubnetRouteTable
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnetCidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs ]
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-subnet-private
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
# for VPC
EC2:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref EC2InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref PrivateSubnet
GroupSet:
- !Ref EC2SecurityGroup
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-ec2
# ------------------------------------------------------------#
# SecurityGroup
# ------------------------------------------------------------#
# for VPC
# SecurityGroup
VPCEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupName: !Sub ${PJPrefix}-sg-for-vpce-
GroupDescription: for VPCE
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-sg-for-vpce
# Ingress
VPCEndpointSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
FromPort: 443
ToPort: 443
IpProtocol: tcp
SourceSecurityGroupId: !Ref EC2SecurityGroup
GroupId: !GetAtt VPCEndpointSecurityGroup.GroupId
#≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡#
# SecurityGroup
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EC2SecurityGroup
GroupName: !Sub ${PJPrefix}-sg-for-ec2
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-sg-for-ec2
# Ingress
EC2SecurityGroupIngressFromAccount2PrivateSubnetCidrBlock:
Type: AWS::EC2::SecurityGroupIngress
Properties:
FromPort: -1
ToPort: -1
IpProtocol: icmp
CidrIp: !Ref Account2PrivateSubnetCidrBlock
GroupId: !GetAtt EC2SecurityGroup.GroupId
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
PeerRoleArn:
Value: !GetAtt VPCPeeringRole.Arn
Account1AccountId:
Value: !Ref AWS::AccountId
Account1VPCId:
Value: !Ref VPC
pingcommand:
Value: !Sub ping -c 3 ${EC2.PrivateIp}
Description: Ping command to be executed on the opposite account.
↓
アカウント2でこのテンプレートを実行してください。
※「PJPrefix」パラメーターは好きな文字列、その他の空欄パラメーターはAccount1.ymlの同名出力値をコピペしてください。
AWSTemplateFormatVersion: "2010-09-09"
Description: VPC Peering Inviter
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Stack
Parameters:
- PJPrefix
- Label:
default: for This Account
Parameters:
- VPCCidrBlock
- PrivateSubnetCidrBlock
- ImageId
- InstanceType
- Label:
default: for opposite Account
Parameters:
- PeerRoleArn
- Account1AccountId
- Account1VPCId
- Account1VPCCidrBlock
- Account1PrivateSubnetCidrBlock
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
PJPrefix: # リソース名やNameタグ値の接頭辞に利用
Type: String
VPCCidrBlock: # VPCのCidr
Type: String
Default: 10.2.0.0/16
PrivateSubnetCidrBlock: # EC2インスタンスのCidr
Type: String
Default: 10.2.0.0/24
ImageId: # EC2のImageId
Type: String
Default: ami-079a2a9ac6ed876fc
InstanceType: # EC2のInstanceType
Type: String
Default: t2.micro
Account1VPCCidrBlock:
Type: String
Default: 10.1.0.0/16
Account1PrivateSubnetCidrBlock:
Type: String
Default: 10.1.0.0/24
Account1VPCId:
Type: String
Account1AccountId:
Type: String
MinLength: 12
MaxLength: 12
PeerRoleArn:
Type: String
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# VPCPeeringConnection
# ------------------------------------------------------------#
VPCPeeringConnection:
Type: AWS::EC2::VPCPeeringConnection
Properties:
VpcId: !Ref VPC
PeerVpcId: !Ref Account1VPCId
PeerOwnerId: !Ref Account1AccountId
PeerRoleArn: !Ref PeerRoleArn
# ------------------------------------------------------------#
# Role
# ------------------------------------------------------------#
# Role
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- ec2.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# InstanceProfile
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref EC2Role
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-vpc
# ------------------------------------------------------------#
# VPCEndpoint
# ------------------------------------------------------------#
VPCEndpointSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
VPCEndpointSSMMessages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
VPCEndpointEC2Messages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
VPCEndpointS3:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref PrivateSubnetRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-rtb-for-private-subnet
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateSubnetRouteTable
RouteforPeer:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateSubnetRouteTable
DestinationCidrBlock: !Ref Account1VPCCidrBlock
VpcPeeringConnectionId: !Ref VPCPeeringConnection
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnetCidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs ]
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-subnet-private
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
# for VPC
EC2:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref EC2InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref PrivateSubnet
GroupSet:
- !Ref EC2SecurityGroup
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-ec2
# ------------------------------------------------------------#
# SecurityGroup
# ------------------------------------------------------------#
# for VPC
# SecurityGroup
VPCEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupName: !Sub ${PJPrefix}-sg-for-vpce-
GroupDescription: for VPCE
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-sg-for-vpce
# Ingress
VPCEndpointSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
FromPort: 443
ToPort: 443
IpProtocol: tcp
SourceSecurityGroupId: !Ref EC2SecurityGroup
GroupId: !GetAtt VPCEndpointSecurityGroup.GroupId
#≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡#
# SecurityGroup
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EC2SecurityGroup
GroupName: !Sub ${PJPrefix}-sg-for-ec2
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-sg-for-ec2
# Ingress
EC2SecurityGroupIngressFromAccount1PrivateSubnetCidrBlock:
Type: AWS::EC2::SecurityGroupIngress
Properties:
FromPort: -1
ToPort: -1
IpProtocol: icmp
CidrIp: !Ref Account1PrivateSubnetCidrBlock
GroupId: !GetAtt EC2SecurityGroup.GroupId
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
pingcommand:
Value: !Sub ping -c 3 ${EC2.PrivateIp}
Description: Ping command to be executed on the opposite account.
↓
アカウント1の「xxxxxx-rtb-for-private-subnet」というルートテーブルに、
送信先:「10.2.0.0/16」と入力。
ターゲット:「ピアリング接続」→「pcx-xxxxxxxxxxxxxxxxx」を選択。
し、「変更を保存」
以上となります。
詳細説明
① Account1.ymlでは、アカウント2をプリンシパルとして、「ec2:AcceptVpcPeeringConnection」というアクションを許可するクロスアカウントロールを作成しています。
② Account2.ymlでは両方のVPCと、(オーナーとして)アカウント1を指定したVPCピアリングを作成しています。
③ Account2.ymlではピアリングを通じてアカウント1のVPCのCidrへの経路を指定するルートをルートテーブルに追加しています。
④ 両方のテンプレートでは
・VPC
・Subnet
・EC2
・対向アカウントのSubnetのからの通信を許可するSecurityGroup
・EC2に接続する為のVPCエンドポイント×4
・VPCエンドポイント用のSecurityGroup
・セッションマネージャーで接続する為に必要なIAMロール
を作成しています。
⑤ 最後に、Account1.yml実行時点では存在しない為、テンプレートの中で向き先として指定する事が出来なかったアカウント1→アカウント2のルート(③の反対にあたる経路)を手動で追加していただくようにしています。
疎通確認
※両方のスタックの「出力」値に「pingcommand」という値を出しておきました。
コピーして、”反対側の”アカウントのEC2で実行ください。
アカウント1→アカウント2
アカウント2→アカウント1
無事疎通確認が出来ました。
別リージョンに存在するVPCに接続するには
アカウント1のVPCをap-northeast-1のまま、アカウント2のVPCをus-east-1にした状態で接続するにはどうすべきかです。
以下プロパティをAWS::EC2::VPCPeeringConnectionリソースに追加すれば接続可能となります。
PeerRegion: ap-northeast-1 # 反対側のVPCがあるリージョンを指定
※先ほどのテンプレートではEC2用のAMIがus-east-1に対応していませんので、パラメーター入力画面で変更の必要があります。
今回体験出来たエラー
その①
Resource of type 'AWS::EC2::VPCPeeringConnection' with identifier 'pcx-xxxxxxxxxxxxxxxxx' did not stabilize.
こちらに原因が書いてあります。
その②
Roles may not be assumed by root accounts.
ルートユーザーではなくIAMユーザーでログインし直し、スタックを実行した所当該エラーは発生しませんでした。
上記エラーはルートユーザー、或いはAdmin権限をしっかり理解する意味で重要な気がしますが、素直にいって現状私の理解が説明を可能にするレベルに到達していません。
心苦しいですが、判明次第別で書かせてください。
終わりに
お読みいいただき有難うございました!
参考にさせていただいた記事
Discussion