VPCピアリングを使って別アカウントにあるVPC内のRDSに接続できるようにする設定をCloudFormationで作成してみた

2023.02.02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

VPCピアリングを使って同じアカウント内のVPCを繋げたことはあったのですが、別アカウントにあるVPCは繋げたことが無かったのでやってみました。

構成

構成図は以下になります。

アカウントAのプライベートサブネットに配置されているEC2からアカウントBのVPCへの通信 (RDS Aurora MySQLへのアクセスなど) をVPCピアリング経由で行う構成となっています。
それ以外の通信はNATゲートウェイを経由して通信を行います。

設定してみた

アカウントAでVPCピアリング以外のリソース作成

まずはアカウントAでVPCピアリング以外のリソースを作成します。
リソース作成には以下のCloudFormationテンプレートを使用しました。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: Network and EC2 Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for VPC
        Parameters:
          - VPCCIDR
      - Label: 
          default: Parameters for Subnet
        Parameters:
          - PublicSubnet01CIDR
          - PrivateSubnet01CIDR
      - Label: 
          default: Parameters for ec2
        Parameters:
          - EC2VolumeSize
          - EC2VolumeIOPS
          - EC2AMI
          - EC2InstanceType

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  VPCCIDR:
    Default: 172.30.0.0/16
    Type: String

  PublicSubnet01CIDR:
    Default: 172.30.1.0/24
    Type: String

  PrivateSubnet01CIDR:
    Default: 172.30.3.0/24
    Type: String

  EC2VolumeSize:
    Default: 32
    Type: Number

  EC2VolumeIOPS:
    Default: 3000
    Type: Number

  EC2AMI:
    Default: ami-06ee4e2261a4dc5c3
    Type: AWS::EC2::Image::Id

  EC2InstanceType:
    Default: t3.micro
    Type: String

Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  EC2IAMRole:
    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: iam-role-ec2
      Tags:
        - Key: Name
          Value: iam-role-ec2

  EC2IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: iam-instanceprofile-ec2
      Roles: 
        - !Ref EC2IAMRole

# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: account-a-vpc

# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------# 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: account-a-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet01CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: !Sub account-a-public-subnet-01
      VpcId: !Ref VPC

  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet01CIDR
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: !Sub account-a-private-subnet-01
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# NatGateWay
# ------------------------------------------------------------# 
  NatGateWayEIP:
    Type: AWS::EC2::EIP
    Properties: 
      Domain: vpc
      Tags:
        - Key: Name
          Value: eip-natgw

  NatGateWay:
    Type: AWS::EC2::NatGateway
    Properties: 
      AllocationId: !GetAtt NatGateWayEIP.AllocationId
      ConnectivityType: public
      SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: account-a-natgw-1a

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: account-a-public-rtb

  PublicRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicRouteTable

  PublicRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: account-a-private-rtb

  PrivateRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateWay
      RouteTableId: !Ref PrivateRouteTable

  PrivateRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet01

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ec2
      GroupName: account-a-sg-ec2
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      Tags: 
        - Key: Name
          Value: account-a-sg-ec2
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------# 
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: !Ref EC2VolumeIOPS
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp3
      DisableApiTermination: false
      IamInstanceProfile: !Ref EC2IAMInstanceProfile
      ImageId: !Ref EC2AMI
      InstanceType: !Ref EC2InstanceType
      NetworkInterfaces: 
        - DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref EC2SG
          SubnetId: !Ref PrivateSubnet01
      Tags:
        - Key: Name
          Value: test-ec2
      UserData: !Base64 |
        #!/bin/bash
        yum remove mariadb-libs
        rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
        yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-6.noarch.rpm -y
        yum install -y mysql-community-client

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  VPCID:
    Value: !Ref VPC
    Export: 
      Name: VPC

  PublicSubnet01ID:
    Value: !Ref PublicSubnet01
    Export: 
      Name: PublicSubnet01ID

  PrivateSubnet01ID:
    Value: !Ref PrivateSubnet01
    Export: 
      Name: PrivateSubnet01ID

  PrivateRouteTableID:
    Value: !Ref PrivateRouteTable
    Export:
      Name: PrivateRouteTableID

EC2はMySQLクライアントをインストールするために「UserData」でインストールコマンドを記載しています。

デプロイは以下のコマンドを実行します。
VPCのCIDRなどを変更したい場合はコマンドのオプションで「--parameters」を入れてください。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM

アカウントBでリソースの作成

アカウントBではRDS Aurora MySQLをシングルAZで起動します。
まずはネットワーク部分とクロスアカウント用のIAMロールを作成してVPCピアリングをできるようにしていきます。
IAMロールは以下の公式ドキュメントと同じものになります。
チュートリアル: 別の AWS アカウント の VPC とピア接続する

リソース作成には以下のCloudFormationテンプレートを使用しました。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: Network Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for IAM
        Parameters:
          - PeerRequesterAccountId
      - Label: 
          default: Parameters for VPC
        Parameters:
          - VPCCIDR
      - Label: 
          default: Parameters for Subnet
        Parameters:
          - PrivateSubnet01CIDR
          - PrivateSubnet02CIDR

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  PeerRequesterAccountId:
    Type: Number
    Description: AWS Account ID

  VPCCIDR:
    Default: 172.17.0.0/16
    Type: String

  PrivateSubnet01CIDR:
    Default: 172.17.1.0/24
    Type: String

  PrivateSubnet02CIDR:
    Default: 172.17.2.0/24
    Type: String

Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  peerRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Principal:
              AWS: !Ref PeerRequesterAccountId
            Action:
              - 'sts:AssumeRole'
            Effect: Allow
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: 'ec2:AcceptVpcPeeringConnection'
                Resource: '*'
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: account-b-vpc

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet01CIDR
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: !Sub account-b-private-subnet-01
      VpcId: !Ref VPC

  PrivateSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet02CIDR
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: !Sub account-b-private-subnet-02
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: account-b-private-rtb

  PrivateRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet01

  PrivateRtAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet02

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  RoleARN:
    Value: !GetAtt peerRole.Arn
    Export: 
      Name: RoleARN

  VPCID:
    Value: !Ref VPC
    Export: 
      Name: VPC

  PrivateSubnet01ID:
    Value: !Ref PrivateSubnet01
    Export: 
      Name: PrivateSubnet01ID

  PrivateSubnet02ID:
    Value: !Ref PrivateSubnet02
    Export: 
      Name: PrivateSubnet02ID

  PrivateRouteTableID:
    Value: !Ref PrivateRouteTable
    Export:
      Name: PrivateRouteTableID

こちらのCloudFormationテンプレートではプライベートサブネットを2つ作成しています。

デプロイは以下のコマンドを実行します。
こちらのCloudFormationはアカウントBで実行するものなので実行場所 (クレデンシャル) などに気を付けてください。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=PeerRequesterAccountId,ParameterValue=AWSアカウントAのID --capabilities CAPABILITY_NAMED_IAM

アカウントAでVPCピアリングの作成

ここからはアカウントAでVPCピアリングを作成していきます。
リソース作成には以下のCloudFormationテンプレートを使用しました。

AWSTemplateFormatVersion: "2010-09-09"

Description: Network Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for VPC Peering
        Parameters:
          - PeerOwnerId
          - PeerRegion
          - PeerRoleArn
          - PeerVpcId

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  PeerOwnerId:
    Type: Number
    Description: AWS Account-b ID

  PeerRegion:
    Default: ap-northeast-1
    Type: String

  PeerRoleArn:
    Type: String
    Description: Account-b Role ARN

  PeerVpcId: 
    Type: String
    Description: Account-b VPC ID

Resources:
# ------------------------------------------------------------#
# VPC Peering
# ------------------------------------------------------------# 
  VPCPeering:
    Type: AWS::EC2::VPCPeeringConnection
    Properties: 
      PeerOwnerId: !Ref PeerOwnerId
      PeerRegion: !Ref PeerRegion
      PeerRoleArn: !Ref PeerRoleArn
      PeerVpcId: !Ref PeerVpcId
      VpcId: !ImportValue VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PrivateRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 172.17.0.0/16
      VpcPeeringConnectionId: !Ref VPCPeering
      RouteTableId: !ImportValue PrivateRouteTableID

こちらのCloudFormationテンプレートではVPCピアリングの作成とアカウントBのVPC向けの経路をルートテーブルに登録しています。
「PeerRoleArn」はクロスアカウントの場合は必要ですが、同一アカウント内であればIAMロールは不要になります。

デプロイは以下のコマンドを実行します。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=PeerOwnerId,ParameterValue=AWSアカウントBのID ParameterKey=PeerRoleArn,ParameterValue=AWSアカウントBで作成したIAMロールのARN ParameterKey=PeerVpcId,ParameterValue=AWSアカウントBのVPC ID

アカウントBでRDS Aurora MySQLの作成

ここではアカウントBでRDS Aurora MySQLの作成を行います。
リソース作成には以下のCloudFormationテンプレートを使用しました。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: RDS Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for Security Group
        Parameters:
          - EC2SG
          - SourceSecurityGroupOwnerId
      - Label: 
          default: Parameters for RDS Aurora MySQL
        Parameters:
          - InstanceClass
          - AuroraMySQLVersion

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  EC2SG:
    Type: String
    Description: SecurityGroup ID
  
  SourceSecurityGroupOwnerId:
    Type: Number
    Description: AWS Account-b ID

  VpcPeeringConnectionId:
    Type: String
    Description: VPC Peering ID

  InstanceClass:
    Default: db.t3.medium
    Type: String

  AuroraMySQLVersion:
    Default: 8.0.mysql_aurora.3.02.2
    Type: String

Resources:
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  RDSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for aurora mysql
      GroupName: account-b-sg-aurora-mysql
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref EC2SG
          ToPort: 3306
      Tags: 
        - Key: Name
          Value: account-b-sg-aurora-mysql
      VpcId: !ImportValue VPC
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PrivateRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 172.16.0.0/16
      VpcPeeringConnectionId: !Ref VpcPeeringConnectionId
      RouteTableId: !ImportValue PrivateRouteTableID

# ------------------------------------------------------------#
# RDS Password
# ------------------------------------------------------------# 
  Password:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Description: RDS Password
      GenerateSecretString:
        GenerateStringKey: password
        SecretStringTemplate: '{"username": "root"}'
        ExcludeCharacters: '"@/\'
      Name: password-aurora-mysql
      Tags: 
        - Key: Name
          Value: password-aurora-mysql

# ------------------------------------------------------------#
# RDS Subnet Group
# ------------------------------------------------------------# 
  SubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: for aurora mysql subnet group
      DBSubnetGroupName: subng-aurora-mysql
      SubnetIds: 
        - !ImportValue PrivateSubnet01ID
        - !ImportValue PrivateSubnet02ID
      Tags:
        - Key: Name
          Value: subng-aurora-mysql

# ------------------------------------------------------------#
# RDS Cluster
# ------------------------------------------------------------# 
  Cluster:
    Type: AWS::RDS::DBCluster
    Properties: 
      BackupRetentionPeriod: 1
      CopyTagsToSnapshot: true
      DBClusterIdentifier: account-b-aurora-mysql
      DBSubnetGroupName: !Ref SubnetGroup
      DeletionProtection: false
      Engine: aurora-mysql
      EngineVersion: !Ref AuroraMySQLVersion
      MasterUsername: !Sub '{{resolve:secretsmanager:${Password}::username}}'
      MasterUserPassword: !Sub '{{resolve:secretsmanager:${Password}::password}}'
      Port: 3306
      StorageEncrypted: true
      Tags: 
        - Key: Name
          Value: account-b-aurora-mysql
      VpcSecurityGroupIds:
        - !Ref RDSSG

# ------------------------------------------------------------#
# RDS Instance
# ------------------------------------------------------------# 
  Instance:
    Type: AWS::RDS::DBInstance
    Properties: 
      AutoMinorVersionUpgrade: true
      AvailabilityZone: ap-northeast-1a
      DBClusterIdentifier: !Ref Cluster
      DBInstanceClass: !Ref InstanceClass
      DBInstanceIdentifier: account-b-aurora-mysql-1
      Engine: aurora-mysql
      PubliclyAccessible: false
      Tags: 
        - Key: Name
          Value: account-b-aurora-mysql-1

セキュリティグループではアカウントAのEC2からポート番号3306でアクセスしてこれるようにアカウントAのEC2が使用しているセキュリティグループを送信元として設定します。
クロスアカウントのセキュリティグループ参照については以下の公式ドキュメントをご確認ください。
セキュリティグループの更新とピアセキュリティグループの参照
RDS Aurora MySQLのマスターユーザー、パスワードはSecrets Managerに格納されるようにしています。

デプロイは以下のコマンドを実行します。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=EC2SG,ParameterValue=アカウントAにあるEC2のセキュリティグループID ParameterKey=SourceSecurityGroupOwnerId,ParameterValue=アカウントAのID

デプロイが完了したら以下のコマンドを実行してRDS Aurora MySQLのマスターユーザーのパスワードを確認します。

get-secret-value --secret-id Secrets ManagerのARN --query SecretString

実行すると以下のような結果が返ってきます。

"{\"password\":\"RDS Aurora MySQLのパスワード\",\"username\":\"root\"}"

動作確認

アカウントAのEC2に接続して以下のコマンドを実行してください
パスワードは上記手順で確認したものになります。

mysql -h RDS Aurora MySQLのライターエンドポイント -u root -P 3306 -p

接続ができれば成功です。

さいごに

クロスアカウントでのVPCピアリング設定は初めてやりましたが詰まる部分も無くサクサク進められました。
アプリケーションを乗せたVPCと管理用のVPCで分けたりするときに使えるので手順は覚えておいて損はないといった印象です。