記事公開日
最終更新日
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.またはその関連会社の商標です。