1. システムとオフィスの融合|株式会社QES
  2. media
  3. AWS
  4. AWS Cloudformation活用 - TransitGateway構築① マルチアカウントでのVPC間接続

QESブログ

AWS Cloudformation活用 - TransitGateway構築① マルチアカウントでのVPC間接続

AWS
  • LINEで送る
  • このエントリーをはてなブックマークに追加

こんにちは。システムソリューション営業本部の松浦です。

今回は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
 →AccountAとBで同一テンプレートを使用し、スタック作成時に入力パラメータとしてAccountA/Bを
  指定することでアカウントを区別し、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 - スタック - スタックの作成 - 新しいリソースを使用(標準)
cfn-tgw1-add1.jpg
("スタックの作成"画面)
・使用するテンプレートを選択します。
※S3使用 or ファイルアップロードはご使用の環境に応じて任意の方法を選択して下さい
テンプレートの準備完了
- 「Amazon S3 URL」 または 「テンプレートファイルのアップロード」を指定
-  TransitGateway用テンプレートのS3URLを入力 または TransitGateway用テンプレートファイルをアップロード
-  次へ

cfn-tgw1-add2b.jpg
("スタックの詳細を指定"画面)
・スタック名と各種パラメータを入力します。
スタック名を入力 - 各種パラメータを入力 - 次へ
<スタックの名前>
・任意の値を入力(今回は「tgwaccount-stack」とします)
<パラメーター>
・AccountAId:アカウントAのIDを入力
・AccountBId:アカウントBのIDを入力        cfn-tgw1-tgwaccount1.jpg
 ※今回はわかりやすくするためアカウントIDを入力する形式としていますが、
  マルチアカウント環境の場合はRAMで共有するアカウントを追加する機会が多いと考えられるため、
  実運用ではCFnテンプレートにアカウントIDを直接記載する方が現実的かと思います。

("スタックオプションの設定"画面)
・今回はスタックオプションは使用せず、デフォルトのままとします。
何も変更せずに画面下部へスクロールし「次へ」を押下
cfn-tgw1-add3b.jpg
↓スクロール
cfn-tgw1-add9.jpg
("レビュー"画面)
・設定内容の確認画面です。内容に問題が無い事を確認後、スタックを作成します。
画面下部へスクロールし「スタックの作成」を押下
cfn-tgw1-add4b.jpg
↓スクロール
cfn-tgw1-add10.jpg
(スタックのイベント画面)
スタックのイベント画面に遷移します。
スタックおよび各種リソースの作成状況のイベントを参照することができます。
論理ID「tgwaccount-stack」(スタック名)のステータスが「CREATE_COMPLETE」となればスタックおよび各種リソースの作成が完了です。
cfn-tgw1-cfn1.jpg
・作成されたTransitGatewayのIDを控えておきます。
 (AccoutA/Bでスタックを作成する時に入力パラメータとして使用します。)
VPC - Transit Gateway - TransitGatewayIDを確認
cfn-tgw1-tgwaccount2.jpg
・ここで一部手動設定が必要になります。
 「関連付けルートテーブル」の有効化およびルートテーブル指定と、
 「伝播ルートテーブル」の有効化およびルートテーブル指定を実施します。
 ※なんで手動変更が必要?と感じるかと思いますが、実はこちらも仕様による影響を考慮しています。
  CFnテンプレートでは意図して関連付けルートテーブルと伝播ルートテーブルの紐づけを行っていないため、
  ここで手動で設定を行います。(理由の詳細については今後別の記事でご紹介予定です!)
VPC - Transit Gateway - TransitGatewayにチェック - アクション - TransitGatewayを変更  cfn-tgw1-tgwaccount3.jpg
「デフォルトルートテーブルの関連付け」と「デフォルトルートテーブル伝播」にチェック
「デフォルト関連付けルートテーブル ID」と「デフォルト伝播ルートテーブル ID」で「tgwaccount-tgwroute」を選択
※上記実施後、右下の「TransitGatewayを変更」で変更を適用します。
cfn-tgw1-tgwaccount4.jpg
↓以下の通りになるように変更
cfn-tgw1-tgwaccount5.jpg

AccountAでの操作

TransitGatewayの作成が完了したため、続けてAccountAで操作を行います。 
・RAMでリソースの共有を承認します
ResourceAccessManager - リソースの共有 - tgwaccount-ramを選択 - 「リソースの共有を承認」を押下
※確認のポップアップが表示されるため、「OK」を押下し承認操作を行います。

cfn-tgw1-accounta1.jpg

cfn-tgw1-accounta2.jpg
左ペインの「共有リソース」をクリックすると、TransitGatewayが共有されていることを確認できます。
cfn-tgw1-accounta3.jpg
VPCコンソールにもTransitGatewayが表示されます。
VPC - TransitGateway
cfn-tgw1-accounta4.jpg
※NameタグがTransitGatewayアカウントから引き継がれないため、
 運用面を考慮してNameタグを付与することをおすすめします。

 ・AccountA/Bテンプレートを使用してCFnスタックを作成します
Cloudformation - スタック - スタックの作成 - 新しいリソースを使用(標準)
cfn-tgw1-add1.jpg
("スタックの作成"画面)
・使用するテンプレートを選択します。
※S3使用 or ファイルアップロードはご使用の環境に応じて任意の方法を選択して下さい
テンプレートの準備完了
- 「Amazon S3 URL」 または 「テンプレートファイルのアップロード」を指定
-  AccountA/B用テンプレートのS3URLを入力 または AccountA/B用テンプレートファイルをアップロード
-  次へ

cfn-tgw1-add5.jpg
("スタックの詳細を指定"画面)
・スタック名と各種パラメータを入力します。
スタック名を入力 - 各種パラメータを入力 - 次へ
<スタックの名前>
・任意の値を入力(今回は「AccountA-stack」とします)
 ※AccountBでの操作時は「AccountB-stack」とします
<入力パラメーター>
・Account:AccountAを選択します。
 ※AccountBでの操作時はAccountBを選択します
・LinuxEc2AMI:AmazonLinux2のAMIを指定します。
 (以下のキャプチャ取得時点の最新AMIが自動入力されるようにしています)
・TransitGatewayId:TransitGatewayのIDを入力します。
cfn-tgw1-accounta5.jpg
("スタックオプションの設定"画面)
・今回はスタックオプションは使用せず、デフォルトのままとします。
何も変更せずに画面下部へスクロールし「次へ」を押下
cfn-tgw1-add8.jpg
↓スクロール
cfn-tgw1-add9.jpg
("レビュー"画面)
・設定内容の確認画面です。
 今回はIAMリソースを作成するため確認文が表示されます。
 内容に問題が無い事を確認後、スタックを作成します。
画面下部へスクロールし「AWS CloudFormationによってIAMリソースが~」にチェック - 「スタックの作成」を押下
cfn-tgw1-add6b.jpg
↓スクロール
cfn-tgw1-add7b.jpg
(スタックのイベント画面)
スタックのイベント画面に遷移します。
スタックおよび各種リソースの作成状況のイベントを参照することができます。
論理ID「AccountA-stack」(スタック名)のステータスが「CREATE_COMPLETE」となればスタックおよび各種リソースの作成が完了です。
cfn-tgw1-cfn2.jpg
・作成されたEC2のプライベートIPアドレスを確認します
EC2 - インスタンス - AccountA-EC2のインスタンスIDを押下
cfn-tgw1-accounta6.jpg

AccountBでの操作

続けてAccountBで操作を行います。 
・操作はAccountAとほぼ同一ですが、スタック作成時のパラメータ入力ではAccountBを選択します。
 それ以外はAccountAと同一手順です。

cfn-tgw1-accountb1.jpg

(オプション)TransitGatewayアカウントでの操作

ここでTransitGatewayアカウントで設定が更新されていることを確認してみます。
・TransitGatewayルートテーブルを見ると、AccountA,AccountBのVPC CIDRが伝播されてルートテーブルが更新されていることがわかります。
VPC - TransitGatewayルートテーブル - tgwaccount-tgwroute - ルート
cfn-tgw1-tgwaccount_affter1.jpg
・また、TransitGatewayアタッチメントにAccountA,AccountBのアタッチメントが追加されていることを確認できます。
VPC - TransitGateway接続
cfn-tgw1-tgwaccount_affter2.jpg
※Nameタグが元のアカウントから引き継がれないため、運用面を考慮してNameタグを付与することをおすすめします。

・これでTransitGatewayにより2つのVPCが接続されていることが確認できました!

疎通確認

それでは本当に接続が行えているかどうか、EC2間で通信をして確かめてみましょう。
・AccountAでSystemsManagerを使用してEC2へ接続します。
EC2 - インスタンス - AccountA-EC2にチェック - 接続 を押下
cfn-tgw1-connect1.jpg  
セッションマネージャー - 接続 を押下
cfn-tgw1-connect2.jpg
コンソール画面で操作可能になったら、AccountBのEC2のIPアドレスに対してpingコマンドを実行してみましょう。
cfn-tgw1-connect3.jpg
⇒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.またはその関連会社の商標です。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

お気軽にお問い合わせください。

ページのトップへ