記事公開日
最終更新日
AWS Cloudformation活用 - TransitGateway構築① マルチアカウントでのVPC間接続

こんにちは。システムソリューション営業本部の松浦です。
今回はCloudFormation(以下CFn)を使用して、マルチアカウント環境でTransitGatewayを使用したVPC間接続構成の作成方法をご紹介いたします。
CFnを使用することで、手動オペレーションを削減して作業時間の短縮が期待でき、
手動オペレーションの削減に伴って設定ミスを抑制することにも繋がります。
CFnでTransitGatewayを構築する場合、実は考慮すべき仕様などが色々あるのですが、
本記事では「まず動かす」ことを目的にご紹介をさせていただきます。
上記考慮不足によるハマりポイントについては、別の記事で改めてご説明する予定です。
環境
今回は以下の3アカウントを使用して環境を構築したいと思います。
・TransitGateway用アカウント
・AccountA
・AccountB
構成
以下の構成を作成します。
今回は接続確認を行うためにそれぞれのVPCにEC2(AmazonLinux2)を起動し、EC2同士でping疎通確認を行います。
EC2にはSystemsManagerを使用してマネジメントコンソールからアクセスします。
使用するCFnテンプレート
TransitGateway用テンプレートとAccountA/B用テンプレートの2つのテンプレートを使用します。以下をクリックすることで表示されるコード部分をそれぞれテキストエディタにコピペし、拡張子をyamlとして任意のファイル名で保存します。
■TransitGateway用テンプレート(クリックしてテンプレートを表示)
AWSTemplateFormatVersion: "2010-09-09"
#Metadata定義。入力パラメータ順の整理のために定義。
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "InputParameters"
Parameters:
- AccountAId
- AccountBId
#入力パラメータ定義
Parameters:
AccountAId:
Type: String
Description: "Enter the account ID for account A (12 digits, no hyphens)"
Default: ""
AccountBId:
Type: String
Description: "Enter the account ID for account B (12 digits, no hyphens)"
Default: ""
#リソース定義
Resources:
#TransitGateway
TransitGateway:
Type: "AWS::EC2::TransitGateway"
Properties:
AmazonSideAsn: 64512
Description: "-"
AutoAcceptSharedAttachments: "enable"
DefaultRouteTableAssociation: "disable" #手動で有効化&ルートテーブルID指定を行う(仕様対策)
DefaultRouteTablePropagation: "disable" #手動で有効化&ルートテーブルID指定を行う(仕様対策)
DnsSupport: "enable"
MulticastSupport: "disable"
VpnEcmpSupport: "enable"
# AssociationDefaultRouteTableId: #本プロパティはAWS側の考慮すべき仕様あり。TableIdは手動で関連付け設定を行う。
# PropagationDefaultRouteTableId: #本プロパティはAWS側の考慮すべき仕様あり。TableIdは手動で関連付け設定を行う。
Tags:
- Key: Name
Value: Transitgateway
#VPC TransitGateway用RouteTable
TransitGatewayRouteTable:
Type: AWS::EC2::TransitGatewayRouteTable
DependsOn: TransitGateway
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: tgwaccount-tgwroute
#TGW静的ルート
#仕様対策のため静的ルートは手動で設定する
#リソース共有
#RAM 他アカウントとTGW共有
ResourceShare:
Type: "AWS::RAM::ResourceShare"
DependsOn:
- TransitGateway
Properties:
Name: tgwaccount-ram
ResourceArns:
- !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:transit-gateway/${TransitGateway}"
Principals:
- !Ref AccountAId
- !Ref AccountBId
Tags:
- Key: Name
Value: tgwaccount-ram
・Transitgateway
・TransitGatewayRouteTable
・ResourceShare(RAM)
■AccountA/B用テンプレート(クリックしてテンプレートを表示)
AWSTemplateFormatVersion: "2010-09-09"
#Metadata定義。入力パラメータ順の整理のために定義。
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "InputParameters"
Parameters:
- Account
- TransitGatewayId
- LinuxEc2AMI
#入力パラメータ定義
Parameters:
Account:
Type: String
Description: Choose Account(AccountA or AccountB)
AllowedValues:
- "AccountA"
- "AccountB"
TransitGatewayId:
Type: String
Description: TransitGatewayId
Default: ""
LinuxEc2AMI:
Type: String
Description: LinuxEc2AMI
Default: "ami-02c3627b04781eada"
#Conditions - AccountA or AccountBの判定に使用する
Conditions:
AccountACondition: !Equals
- !Ref Account
- "AccountA"
AccountBCondition: !Equals
- !Ref Account
- "AccountB"
#Mappings定義。
Mappings:
#アカウント毎に可変のパラメータ
AccountMap:
#VPCのCIDR
VpcCidr:
AccountA: "172.16.0.0/16"
AccountB: "172.17.0.0/16"
#サブネットのCIDR
SubnetACidr:
AccountA: "172.16.100.0/24"
AccountB: "172.17.100.0/24"
#ターゲット(通信先アカウントのサブネット)のCIDR
TargetCidr:
AccountA: "172.17.100.0/24"
AccountB: "172.16.100.0/24"
#リソース定義
Resources:
#VPC用リソース
#VPC設定
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !FindInMap [AccountMap , VpcCidr , !Ref Account]
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: "default"
Tags:
- Key: Name
Value: !Sub ${Account}-vpc
#サブネット
SubnetA:
Type: AWS::EC2::Subnet
DependsOn: VPC
Properties:
VpcId: !Ref VPC
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !FindInMap [AccountMap , SubnetACidr , !Ref Account]
MapPublicIpOnLaunch: "false"
Tags:
- Key: Name
Value: !Sub ${Account}-pri-tgwsubnet-a
#ルートテーブル設定
#ルートテーブル
RouteTableA:
Type: "AWS::EC2::RouteTable"
DependsOn:
- VPC
- SubnetA
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${Account}-pri-tgwrtb-a
#他アカウント向けルート
RouteTableARoute1:
Type: "AWS::EC2::Route"
DependsOn:
- RouteTableA
- TransitGatewayAttach
Properties:
DestinationCidrBlock: !FindInMap [AccountMap , TargetCidr , !Ref Account]
RouteTableId: !Ref RouteTableA
TransitGatewayId: !Ref TransitGatewayId
#ルートテーブルとサブネットの関連付け
RouteTableAAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
DependsOn:
- RouteTableA
- SubnetA
Properties:
RouteTableId: !Ref RouteTableA
SubnetId: !Ref SubnetA
#TransitGateway Attachment
TransitGatewayAttach:
Type: AWS::EC2::TransitGatewayVpcAttachment
DependsOn: SubnetA
Properties:
VpcId: !Ref VPC
Options:
DnsSupport : enable
Ipv6Support: disable
SubnetIds:
- !Ref SubnetA
TransitGatewayId: !Ref TransitGatewayId
Tags:
- Key: Name
Value: !Sub ${Account}-tgwattachment
######EC2 + SystemsManager#######
#SG-EC2
Ec2SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
DependsOn: VPC
Properties:
VpcId: !Ref VPC
GroupName: !Sub ${Account}-sg-ec2
GroupDescription: !Sub ${Account}-sg-ec2
SecurityGroupIngress:
- IpProtocol: "icmp"
FromPort: -1
ToPort: -1
CidrIp: !FindInMap [AccountMap , TargetCidr , !Ref Account]
Description: ping-Inbound
SecurityGroupEgress:
- IpProtocol: "tcp"
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: Endpoint-HTTPS-Outbound
- IpProtocol: "icmp"
FromPort: -1
ToPort: -1
CidrIp: !FindInMap [AccountMap , TargetCidr , !Ref Account]
Description: ping-Outbound
Tags:
- Key: Name
Value: !Sub ${Account}-sg-ec2
#SG-SsmEndpoint
SsmEndpointSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
DependsOn: VPC
Properties:
VpcId: !Ref VPC
GroupName: !Sub ${Account}-sg-ssm-endpoint
GroupDescription: !Sub ${Account}-sg-ssm-endpoint
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 443
ToPort: 443
CidrIp: !FindInMap [AccountMap , SubnetACidr , !Ref Account]
Description: !Sub ${Account}Inbound
SecurityGroupEgress:
# デフォルトのアウトバウンド全許可削除のための定義
- IpProtocol: "-1"
CidrIp: 127.0.0.1/32
Tags:
- Key: Name
Value: !Sub ${Account}-sg-ssm-endpoint
#SSM-Endpoint(SSM)
SsmEndPoint:
Type: AWS::EC2::VPCEndpoint
DependsOn:
- VPC
- SubnetA
- SsmEndpointSecurityGroup
Properties:
VpcId: !Ref VPC
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref SsmEndpointSecurityGroup
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'
SubnetIds:
- !Ref SubnetA
VpcEndpointType: Interface
#SSM-Endpoint(ec2messages)
Ec2MessagesEndPoint:
Type: AWS::EC2::VPCEndpoint
DependsOn:
- VPC
- SubnetA
- SsmEndpointSecurityGroup
Properties:
VpcId: !Ref VPC
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref SsmEndpointSecurityGroup
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'
SubnetIds:
- !Ref SubnetA
VpcEndpointType: Interface
#SSM-Endpoint(SSMmessages)
SsmMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
DependsOn:
- VPC
- SubnetA
- SsmEndpointSecurityGroup
Properties:
VpcId: !Ref VPC
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref SsmEndpointSecurityGroup
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'
SubnetIds:
- !Ref SubnetA
VpcEndpointType: Interface
#IAMRole
#EC2Role
Ec2Role:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
"Version": "2012-10-17"
"Statement":
-
"Effect": "Allow"
"Principal":
"Service":
- "ec2.amazonaws.com"
"Action":
- "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
RoleName: !Sub ${Account}EC2Role
Description: !Sub ${Account}EC2Role
Tags:
- Key: Name
Value: !Sub ${Account}EC2Role
#InstanceProfile
Ec2InstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
DependsOn: Ec2Role
Properties:
Roles:
- !Ref Ec2Role
#AmaznonLinux EC2
AmazonLinuxEc2:
Type: AWS::EC2::Instance
DependsOn:
- SubnetA
- Ec2InstanceProfile
- Ec2SecurityGroup
Properties:
AvailabilityZone: ap-northeast-1a
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
DeleteOnTermination: true
Encrypted: false
Iops: 3000
VolumeSize: 8
VolumeType: gp3
CreditSpecification:
CPUCredits: standard
DisableApiTermination: false
EbsOptimized: false
HibernationOptions:
Configured: false
IamInstanceProfile: !Ref Ec2InstanceProfile
ImageId: !Ref LinuxEc2AMI
InstanceInitiatedShutdownBehavior: stop
InstanceType: t3.nano
Monitoring: false
NetworkInterfaces:
- AssociatePublicIpAddress: false
DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref Ec2SecurityGroup
Ipv6AddressCount: 0
SubnetId: !Ref SubnetA
SourceDestCheck: true
Tags:
- Key: Name
Value: !Sub ${Account}-EC2
Tenancy: default
指定することでアカウントを区別し、CIDR等の可変パラメータを制御します。
以下のリソースを作成しています。
・VPC
・Subnet
・RouteTable / Route / SubnetRouteTableAssociation
・TransitGatewayVpcAttachment
・SecurityGroup(EC2用 / SystemsManager Endpoint用)
・Endpoint(SystemsManager用に3種作成)
・IAMRole / InstanceProfile(EC2用。SystemsManagerでの接続のために必要)
・EC2インスタンス(AmazonLinux2)
実際に構築してみる
作業の流れ
ざっくりした流れは以下の通りです。
・TransitGatewayアカウントでTransitGatewayスタック作成
・TransitGatewayアカウントでTrainsitGatewayルートテーブルの手動設定
・AccountAでRAM承認/スタック作成
・AccountBでRAM承認/スタック作成
・AccountA/BのEC2で疎通確認。
それでは実際に構築を始めていきましょう!
TransitGatewayアカウントでの操作
・TransitGatewayテンプレートを使用してCFnスタックを作成します。
Cloudformation - スタック - スタックの作成 - 新しいリソースを使用(標準)
("スタックの作成"画面)
・使用するテンプレートを選択します。
※S3使用 or ファイルアップロードはご使用の環境に応じて任意の方法を選択して下さい
テンプレートの準備完了
- 「Amazon S3 URL」 または 「テンプレートファイルのアップロード」を指定
- TransitGateway用テンプレートのS3URLを入力 または TransitGateway用テンプレートファイルをアップロード
- 次へ
("スタックの詳細を指定"画面)
・スタック名と各種パラメータを入力します。
スタック名を入力 - 各種パラメータを入力 - 次へ
<スタックの名前>
・任意の値を入力(今回は「tgwaccount-stack」とします)
<パラメーター>
・AccountAId:アカウントAのIDを入力
・AccountBId:アカウントBのIDを入力 
※今回はわかりやすくするためアカウントIDを入力する形式としていますが、
マルチアカウント環境の場合はRAMで共有するアカウントを追加する機会が多いと考えられるため、
実運用ではCFnテンプレートにアカウントIDを直接記載する方が現実的かと思います。
("スタックオプションの設定"画面)
・今回はスタックオプションは使用せず、デフォルトのままとします。
何も変更せずに画面下部へスクロールし「次へ」を押下
↓スクロール
("レビュー"画面)
・設定内容の確認画面です。内容に問題が無い事を確認後、スタックを作成します。
画面下部へスクロールし「スタックの作成」を押下
↓スクロール
(スタックのイベント画面)
スタックのイベント画面に遷移します。
スタックおよび各種リソースの作成状況のイベントを参照することができます。
論理ID「tgwaccount-stack」(スタック名)のステータスが「CREATE_COMPLETE」となればスタックおよび各種リソースの作成が完了です。
・作成されたTransitGatewayのIDを控えておきます。
(AccoutA/Bでスタックを作成する時に入力パラメータとして使用します。)
VPC - Transit Gateway - TransitGatewayIDを確認
・ここで一部手動設定が必要になります。
「関連付けルートテーブル」の有効化およびルートテーブル指定と、
「伝播ルートテーブル」の有効化およびルートテーブル指定を実施します。
※なんで手動変更が必要?と感じるかと思いますが、実はこちらも仕様による影響を考慮しています。
CFnテンプレートでは意図して関連付けルートテーブルと伝播ルートテーブルの紐づけを行っていないため、
ここで手動で設定を行います。(理由の詳細については今後別の記事でご紹介予定です!)
VPC - Transit Gateway - TransitGatewayにチェック - アクション - TransitGatewayを変更 
「デフォルトルートテーブルの関連付け」と「デフォルトルートテーブル伝播」にチェック
「デフォルト関連付けルートテーブル ID」と「デフォルト伝播ルートテーブル ID」で「tgwaccount-tgwroute」を選択
※上記実施後、右下の「TransitGatewayを変更」で変更を適用します。
↓以下の通りになるように変更
AccountAでの操作
TransitGatewayの作成が完了したため、続けてAccountAで操作を行います。
・RAMでリソースの共有を承認します
ResourceAccessManager - リソースの共有 - tgwaccount-ramを選択 - 「リソースの共有を承認」を押下
※確認のポップアップが表示されるため、「OK」を押下し承認操作を行います。

左ペインの「共有リソース」をクリックすると、TransitGatewayが共有されていることを確認できます。

VPCコンソールにもTransitGatewayが表示されます。
VPC - TransitGateway
※NameタグがTransitGatewayアカウントから引き継がれないため、
運用面を考慮してNameタグを付与することをおすすめします。
・AccountA/Bテンプレートを使用してCFnスタックを作成します
Cloudformation - スタック - スタックの作成 - 新しいリソースを使用(標準)
("スタックの作成"画面)
・使用するテンプレートを選択します。
※S3使用 or ファイルアップロードはご使用の環境に応じて任意の方法を選択して下さい
テンプレートの準備完了
- 「Amazon S3 URL」 または 「テンプレートファイルのアップロード」を指定
- AccountA/B用テンプレートのS3URLを入力 または AccountA/B用テンプレートファイルをアップロード
- 次へ
("スタックの詳細を指定"画面)
・スタック名と各種パラメータを入力します。
スタック名を入力 - 各種パラメータを入力 - 次へ
<スタックの名前>
・任意の値を入力(今回は「AccountA-stack」とします)
※AccountBでの操作時は「AccountB-stack」とします
<入力パラメーター>
・Account:AccountAを選択します。
※AccountBでの操作時はAccountBを選択します
・LinuxEc2AMI:AmazonLinux2のAMIを指定します。
(以下のキャプチャ取得時点の最新AMIが自動入力されるようにしています)
・TransitGatewayId:TransitGatewayのIDを入力します。
("スタックオプションの設定"画面)
・今回はスタックオプションは使用せず、デフォルトのままとします。
何も変更せずに画面下部へスクロールし「次へ」を押下
↓スクロール
("レビュー"画面)
・設定内容の確認画面です。
今回はIAMリソースを作成するため確認文が表示されます。
内容に問題が無い事を確認後、スタックを作成します。
画面下部へスクロールし「AWS CloudFormationによってIAMリソースが~」にチェック - 「スタックの作成」を押下
↓スクロール
(スタックのイベント画面)
スタックのイベント画面に遷移します。
スタックおよび各種リソースの作成状況のイベントを参照することができます。
論理ID「AccountA-stack」(スタック名)のステータスが「CREATE_COMPLETE」となればスタックおよび各種リソースの作成が完了です。
・作成されたEC2のプライベートIPアドレスを確認します
EC2 - インスタンス - AccountA-EC2のインスタンスIDを押下
AccountBでの操作
続けてAccountBで操作を行います。
・操作はAccountAとほぼ同一ですが、スタック作成時のパラメータ入力ではAccountBを選択します。
それ以外はAccountAと同一手順です。
(オプション)TransitGatewayアカウントでの操作
ここでTransitGatewayアカウントで設定が更新されていることを確認してみます。
・TransitGatewayルートテーブルを見ると、AccountA,AccountBのVPC CIDRが伝播されてルートテーブルが更新されていることがわかります。
VPC - TransitGatewayルートテーブル - tgwaccount-tgwroute - ルート
・また、TransitGatewayアタッチメントにAccountA,AccountBのアタッチメントが追加されていることを確認できます。
VPC - TransitGateway接続
※Nameタグが元のアカウントから引き継がれないため、運用面を考慮してNameタグを付与することをおすすめします。
・これでTransitGatewayにより2つのVPCが接続されていることが確認できました!
疎通確認
それでは本当に接続が行えているかどうか、EC2間で通信をして確かめてみましょう。
・AccountAでSystemsManagerを使用してEC2へ接続します。
EC2 - インスタンス - AccountA-EC2にチェック - 接続 を押下
セッションマネージャー - 接続 を押下
コンソール画面で操作可能になったら、AccountBのEC2のIPアドレスに対してpingコマンドを実行してみましょう。
⇒AccountAのEC2からAccountBのEC2へ疎通可能であることが確認できました!
(キャプチャは割愛しますが、もちろん逆方向のAccoutB→AccoutAへの疎通も可能です)
まとめ
最後までお読みいただき、誠にありがとうございました!
「AWS Cloudformation活用 - TransitGateway構築」(連載予定)の第1回目として、CloudFormationでのマルチアカウントVPC間接続構成の作成方法をご紹介いたしました。
マルチアカウント管理を考慮したネットワーク構成でTransitGatewayの使用を検討されている場合、本記事がお役に立てれば幸いです。
もし「このサービスについて知りたい」「AWS環境の構築、移行」「記事内で言及した仕様について早急に知りたい」などのリクエストがございましたら、弊社お問合せフォームまでお気軽にご連絡ください! のちほど当ブログにてご紹介させていただくか、複雑な内容に関するお問い合わせの内容の場合には直接営業からご連絡を差し上げます。
また、よろしければ以下のリンクもご覧ください!
<QES関連ソリューション/ブログ>
<QESが参画しているAWSのセキュリティ推進コンソーシアムがホワイトペーパーを公開しました>
※Amazon Web Services、”Powered by Amazon Web Services”ロゴ、およびブログで使用されるその他のAWS商標は、米国その他の諸国における、Amazon.com, Inc.またはその関連会社の商標です。


