Amazon Aurora Global Database + ヘッドレスクラスター を一つのCloudFormationテンプレートでまとめて作る。
はじめに
皆様こんにちは、あかいけです。
「Aurora Global Databaseってマネジメントコンソールからポチポチ作るのめんどくさいなぁ...。」
「CloudFormationで一発で作れたらいいのに…。」
そんなことを思ったことはありませんか?私はあります。
特にセカンダリリージョンでヘッドレスクラスターを作る場合、
手動だとインスタンスを作った後わざわざ削除する必要があったりして、正直面倒ですよね。
そんな悩みを解決すべく、
今回は 複数リージョンに対応した一つのCloudFormationテンプレートで、Aurora Global Database + ヘッドレスクラスター を作ってみました。
モチベーション
1. 手動で作成する場合
まず手動でAmazon Aurora Global Database + ヘッドレスクラスターを作ろうとすると、以下の手順が必要です。
- メインリージョン
- Aurora クラスター作成
- グローバルデータベース作成
- セカンダリリージョン
- Aurora クラスター&インスタンス 作成 (グローバルデータベース作成と同時)
- Aurora インスタンス削除
冒頭で触れた通り手動でクラスターを作成した上で、セカンダリのインスタンスをわざわざ削除しないといけないのがめんどくさいポイントです。
この一連の作業だけでおそらく30分以上かかるでしょう…。
2. CloudFormationで作成する場合
次に CloudFormation で作成する場合です。
手動で作るときに比べて セカンダリのAurora インスタンス削除の手間が減っていて、その分時間が削減できます。
- メインリージョン
- Aurora クラスター作成
- グローバルデータベース作成
- セカンダリリージョン
- Aurora クラスター作成
そしてマルチリージョンでリソースを作成する場合、
以下のようにリージョン別にCloudFormationテンプレートを分けることが一般的かな?と思います。
├── README.md
├── region1/
│ └── aurora.yaml
└── region2/
└── aurora.yaml
ただリージョンごとに分けると、ファイル数が2倍になりまた重複した記載が多くなるので、正直色々疲れます…。
なので今回はリージョン間で同いテンプレートファイルを使えるように作ってみます。
今回の構成
構成は以下の通り。
プライマリクラスター×1、セカンダリクラスター×1のシンプルな構成です。
またプライマリクラスターにはライターとリーダーでインスタンスが2つ、
セカンダリクラスターはヘッドレスクラスターのためインスタンスは存在しない状況です。
スタック / コード構成
以下の理由からネストスタック構成にしてみました。
- モジュール化(子スタック)による再利用性の向上
- クロススタック参照を使わずにスタック間の値参照が可能
またディレクトリ構成は以下の通りです。
親スタック/子スタックはリージョン共通で使えるようにしています。
.
└── cloudformation/
├── 01-parent/ # 親スタック
│ └── parent-cfn.yaml
└── 02-child/ # 子スタック
├── vpc-child-cfn.yaml
├── sg-child-cfn.yaml
└── aurora-child-cfn.yaml
親スタック
AWSTemplateFormatVersion: "2010-09-09"
Description: "Production Parent Stack"
Parameters:
# Common Parameters
ProjectName:
Type: String
Default: globaldb-test-cfn
Description: Project name prefix for resources
Environment:
Type: String
Default: dev
Description: Environment name (prod, dev, etc.)
# Aurora Parameters
AuroraInstanceClass:
Type: String
Default: db.r7g.large
Description: Aurora PostgreSQL Instance Class
AuroraDeletionProtection:
Type: String
Default: false
Description: Deletion Protection Status for Aurora
MasterUsername:
Type: String
Description: Aurora master username
MasterUserPassword:
Type: String
NoEcho: true
Description: Aurora master user password
Mappings:
RegionToShortname:
ap-northeast-1:
Shortname: "apne1"
ap-northeast-3:
Shortname: "apne3"
Resources:
# VPC Infrastructure
ChildStackVPC:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub
- "https://s3.amazonaws.com/${ProjectName}-${Environment}-${RegionShortname}-cfn-s3/vpc-child-cfn.yaml"
- RegionShortname:
!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
Parameters:
ProjectName: !Ref ProjectName
Environment: !Ref Environment
RegionToShortname:
!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
# Security Groups
ChildStackSG:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub
- "https://s3.amazonaws.com/${ProjectName}-${Environment}-${RegionShortname}-cfn-s3/sg-child-cfn.yaml"
- RegionShortname:
!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
Parameters:
ProjectName: !Ref ProjectName
Environment: !Ref Environment
RegionToShortname:
!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
VpcId: !GetAtt ChildStackVPC.Outputs.VpcId
# Aurora PostgreSQL (only for Tokyo initially)
ChildStackAurora:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub
- "https://s3.amazonaws.com/${ProjectName}-${Environment}-${RegionShortname}-cfn-s3/aurora-child-cfn.yaml"
- RegionShortname:
!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
Parameters:
ProjectName: !Ref ProjectName
Environment: !Ref Environment
RegionToShortname:
!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
PrivateSubnet1Id: !GetAtt ChildStackVPC.Outputs.PrivateSubnet1Id
PrivateSubnet2Id: !GetAtt ChildStackVPC.Outputs.PrivateSubnet2Id
AuroraSecurityGroupId: !GetAtt ChildStackSG.Outputs.AuroraSecurityGroupId
InstanceClass: !Ref AuroraInstanceClass
DeletionProtection: !Ref AuroraDeletionProtection
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
子スタック
AWSTemplateFormatVersion: "2010-09-09"
Description: "VPC Infrastructure Child Stack"
Parameters:
ProjectName:
Type: String
Environment:
Type: String
RegionToShortname:
Type: String
Mappings:
AvailabilityZoneToShortname:
ap-northeast-1:
AZ1: "1a"
AZ2: "1c"
ap-northeast-3:
AZ1: "3a"
AZ2: "3b"
RegionToAvailabilityZones:
ap-northeast-1:
AZ1: "ap-northeast-1a"
AZ2: "ap-northeast-1c"
ap-northeast-3:
AZ1: "ap-northeast-3a"
AZ2: "ap-northeast-3b"
RegionToCIDRs:
ap-northeast-1:
VpcCidr: 10.0.0.0/16
PrivateSubnet1Cidr: 10.0.0.0/24
PrivateSubnet2Cidr: 10.0.1.0/24
ap-northeast-3:
VpcCidr: 10.1.0.0/16
PrivateSubnet1Cidr: 10.1.0.0/24
PrivateSubnet2Cidr: 10.1.1.0/24
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !FindInMap [RegionToCIDRs, !Ref "AWS::Region", VpcCidr]
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-vpc"
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-igw"
# Attach Internet Gateway to VPC
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !FindInMap [RegionToAvailabilityZones, !Ref "AWS::Region", AZ1]
CidrBlock: !FindInMap [RegionToCIDRs, !Ref "AWS::Region", PrivateSubnet1Cidr]
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Join
- "-"
- - !Ref ProjectName
- !Ref Environment
- !Ref RegionToShortname
- "private-subnet"
- !FindInMap [
AvailabilityZoneToShortname,
!Ref "AWS::Region",
AZ1,
]
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !FindInMap [RegionToAvailabilityZones, !Ref "AWS::Region", AZ2]
CidrBlock: !FindInMap [RegionToCIDRs, !Ref "AWS::Region", PrivateSubnet2Cidr]
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Join
- "-"
- - !Ref ProjectName
- !Ref Environment
- !Ref RegionToShortname
- "private-subnet"
- !FindInMap [
AvailabilityZoneToShortname,
!Ref "AWS::Region",
AZ2,
]
# Route Tables
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Join
- "-"
- - !Ref ProjectName
- !Ref Environment
- !Ref RegionToShortname
- "private-rtb"
- !FindInMap [
AvailabilityZoneToShortname,
!Ref "AWS::Region",
AZ1,
]
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet2
# Network ACLs
PrivateNetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-private-nacl"
PrivateInboundRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateNetworkAcl
RuleNumber: 100
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PrivateOutboundRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateNetworkAcl
RuleNumber: 100
Protocol: -1
Egress: true
RuleAction: allow
CidrBlock: 0.0.0.0/0
PrivateSubnetNetworkAclAssociation1:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
NetworkAclId: !Ref PrivateNetworkAcl
PrivateSubnetNetworkAclAssociation2:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
NetworkAclId: !Ref PrivateNetworkAcl
Outputs:
VpcId:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VpcId"
PrivateSubnet1Id:
Description: Private Subnet 1 ID
Value: !Ref PrivateSubnet1
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnet1Id"
PrivateSubnet2Id:
Description: Private Subnet 2 ID
Value: !Ref PrivateSubnet2
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnet2Id"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Security Groups Child Stack"
Parameters:
ProjectName:
Type: String
Environment:
Type: String
RegionToShortname:
Type: String
VpcId:
Type: String
Description: VPC ID where security groups will be created
Resources:
# Aurora Security Group
AuroraSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-sg"
GroupDescription: Security group for Aurora PostgreSQL
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-sg"
Outputs:
AuroraSecurityGroupId:
Description: Aurora Security Group ID
Value: !Ref AuroraSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-AuroraSecurityGroupId"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Aurora PostgreSQL Child Stack"
Parameters:
ProjectName:
Type: String
Environment:
Type: String
RegionToShortname:
Type: String
PrivateSubnet1Id:
Type: String
Description: Private Subnet 1 ID
PrivateSubnet2Id:
Type: String
Description: Private Subnet 2 ID
AuroraSecurityGroupId:
Type: String
Description: Aurora Security Group ID
InstanceClass:
Type: String
DeletionProtection:
Type: String
MasterUsername:
Type: String
MasterUserPassword:
Type: String
NoEcho: true
Conditions:
IsTokyoRegion: !Equals [!Ref "AWS::Region", "ap-northeast-1"]
IsOsakaRegion: !Equals [!Ref "AWS::Region", "ap-northeast-3"]
Resources:
# Aurora Subnet Group
AuroraSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-private-sbntgroup"
DBSubnetGroupDescription: Subnet group for Aurora cluster
SubnetIds:
- !Ref PrivateSubnet1Id
- !Ref PrivateSubnet2Id
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-private-sbntgroup"
# Aurora Cluster Parameter Group
AuroraClusterParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
DBClusterParameterGroupName: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cpgp"
Description: Aurora PostgreSQL cluster parameter group
Family: aurora-postgresql16
Parameters:
timezone: Asia/Tokyo
client_encoding: UTF8
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cpgp"
# Aurora DB Parameter Group
AuroraParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
DBParameterGroupName: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-pgp"
Description: Aurora PostgreSQL parameter group
Family: aurora-postgresql16
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-pgp"
# Aurora Cluster
AuroraCluster:
Type: AWS::RDS::DBCluster
Condition: IsTokyoRegion
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
Engine: aurora-postgresql
EngineVersion: "16.6"
Port: 5432
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
DBSubnetGroupName: !Ref AuroraSubnetGroup
VpcSecurityGroupIds:
- !Ref AuroraSecurityGroupId
DBClusterParameterGroupName: !Ref AuroraClusterParameterGroup
BackupRetentionPeriod: 7
PreferredBackupWindow: "19:00-19:30"
PreferredMaintenanceWindow: "sat:20:00-sat:20:30"
DeletionProtection: !Ref DeletionProtection
StorageEncrypted: true
AutoMinorVersionUpgrade: false
EnableCloudwatchLogsExports:
- postgresql
- instance
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
- Key: aws-backup
Value: enable
# Aurora Global Cluster (only for Tokyo, created after DB Cluster)
AuroraGlobalCluster:
Type: AWS::RDS::GlobalCluster
Condition: IsTokyoRegion
Properties:
GlobalClusterIdentifier: !Sub "${ProjectName}-${Environment}-aurora-global-cluster"
SourceDBClusterIdentifier: !Ref AuroraCluster
DependsOn: AuroraCluster
# Aurora DB Instance - Writer (Primary)
AuroraDBInstanceWriter:
Type: AWS::RDS::DBInstance
Condition: IsTokyoRegion
Properties:
DBInstanceIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-instance-1"
DBInstanceClass: !Ref InstanceClass
Engine: aurora-postgresql
EngineVersion: "16.6"
DBClusterIdentifier: !Ref AuroraCluster
DBParameterGroupName: !Ref AuroraParameterGroup
PreferredMaintenanceWindow: "sat:20:30-sat:21:00"
AvailabilityZone: ap-northeast-1a
MonitoringInterval: 0
EnablePerformanceInsights: false
AutoMinorVersionUpgrade: false
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-instance-1"
- Key: Role
Value: "Writer"
# Aurora DB Instance - Reader (Replica in different AZ)
AuroraDBInstanceReader:
Type: AWS::RDS::DBInstance
Condition: IsTokyoRegion
Properties:
DBInstanceIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-instance-2"
DBInstanceClass: !Ref InstanceClass
Engine: aurora-postgresql
EngineVersion: "16.6"
DBClusterIdentifier: !Ref AuroraCluster
DBParameterGroupName: !Ref AuroraParameterGroup
PreferredMaintenanceWindow: "sat:20:30-sat:21:00"
AvailabilityZone: ap-northeast-1c
MonitoringInterval: 0
EnablePerformanceInsights: false
AutoMinorVersionUpgrade: false
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-instance-2"
- Key: Role
Value: "Reader"
# Aurora Secondary Cluster (headless - only for Osaka region)
AuroraSecondaryCluster:
Type: AWS::RDS::DBCluster
Condition: IsOsakaRegion
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
GlobalClusterIdentifier: !Sub "${ProjectName}-${Environment}-aurora-global-cluster"
Engine: aurora-postgresql
EngineVersion: "16.6"
Port: 5432
DBSubnetGroupName: !Ref AuroraSubnetGroup
VpcSecurityGroupIds:
- !Ref AuroraSecurityGroupId
DBClusterParameterGroupName: !Ref AuroraClusterParameterGroup
BackupRetentionPeriod: 7
PreferredBackupWindow: "19:00-19:30"
PreferredMaintenanceWindow: "sat:20:00-sat:20:30"
DeletionProtection: !Ref DeletionProtection
StorageEncrypted: true
KmsKeyId: alias/aws/rds
AutoMinorVersionUpgrade: false
EnableCloudwatchLogsExports:
- postgresql
- instance
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
- Key: Type
Value: "Secondary-Headless"
Outputs:
AuroraClusterIdentifier:
Description: Aurora Cluster Identifier
Value: !If
- IsTokyoRegion
- !Ref AuroraCluster
- !Ref AuroraSecondaryCluster
Export:
Name: !Sub "${AWS::StackName}-AuroraClusterIdentifier"
AuroraClusterEndpoint:
Condition: IsTokyoRegion
Description: Aurora Cluster Writer Endpoint (Tokyo only)
Value: !GetAtt AuroraCluster.Endpoint.Address
Export:
Name: !Sub "${AWS::StackName}-AuroraClusterEndpoint"
AuroraClusterReaderEndpoint:
Condition: IsTokyoRegion
Description: Aurora Cluster Reader Endpoint (Tokyo only)
Value: !GetAtt AuroraCluster.ReadEndpoint.Address
Export:
Name: !Sub "${AWS::StackName}-AuroraClusterReaderEndpoint"
AuroraSecondaryClusterIdentifier:
Condition: IsOsakaRegion
Description: Aurora Secondary Cluster Identifier (Osaka headless)
Value: !Ref AuroraSecondaryCluster
Export:
Name: !Sub "${AWS::StackName}-AuroraSecondaryClusterIdentifier"
AuroraWriterInstanceIdentifier:
Condition: IsTokyoRegion
Description: Aurora Writer Instance Identifier
Value: !Ref AuroraDBInstanceWriter
Export:
Name: !Sub "${AWS::StackName}-AuroraWriterInstanceIdentifier"
AuroraReaderInstanceIdentifier:
Condition: IsTokyoRegion
Description: Aurora Reader Instance Identifier
Value: !Ref AuroraDBInstanceReader
Export:
Name: !Sub "${AWS::StackName}-AuroraReaderInstanceIdentifier"
AuroraGlobalClusterIdentifier:
Condition: IsTokyoRegion
Description: Aurora Global Cluster Identifier
Value: !Ref AuroraGlobalCluster
Export:
Name: !Sub "${AWS::StackName}-AuroraGlobalClusterIdentifier"
デプロイ
子スタック用のテンプレートファイル格納用のS3を作成します。
バケット名はテンプレートファイル側でParametersとして定義している、
「ProjectName-Environment-RegionShortname-cfn-s3」になるようにします。
aws s3api create-bucket \
--bucket globaldb-test-cfn-dev-apne1-cfn-s3 \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1;
aws s3api create-bucket \
--bucket globaldb-test-cfn-dev-apne3-cfn-s3 \
--region ap-northeast-3 \
--create-bucket-configuration LocationConstraint=ap-northeast-3;
子スタック用のテンプレートファイルを格納します。
aws s3 cp --recursive cloudformation/02-child/ s3://globaldb-test-cfn-dev-apne1-cfn-s3;
aws s3 cp --recursive cloudformation/02-child/ s3://globaldb-test-cfn-dev-apne3-cfn-s3;
以下コマンドでスタックを作成します。
DBで利用するユーザー名、パスワードを環境変数で定義しておきます。
MasterUsername='postgres'
MasterUserPassword=$(openssl rand -base64 12)
まずはプライマリ側(東京リージョン)のスタックを作成します。
aws cloudformation create-stack \
--stack-name parent-stack-apne1 \
--template-body file://cloudformation/01-parent/parent-cfn.yaml \
--region ap-northeast-1 \
--parameters \
ParameterKey=MasterUsername,ParameterValue=$MasterUsername \
ParameterKey=MasterUserPassword,ParameterValue=$MasterUserPassword
プライマリ側の作成が完了してから、
セカンダリ側(大阪リージョン)のスタックを作成します。
aws cloudformation create-stack \
--stack-name parent-stack-apne3 \
--template-body file://cloudformation/01-parent/parent-cfn.yaml \
--region ap-northeast-3 \
--parameters \
ParameterKey=MasterUsername,ParameterValue=$MasterUsername \
ParameterKey=MasterUserPassword,ParameterValue=$MasterUserPassword
セカンダリ側のスタック作成が完了すれば、冒頭の構成が完成します。
削除方法
リソースを削除する際は、
作成時とは逆にセカンダリスタックから削除すればOKです。
aws cloudformation delete-stack \
--stack-name parent-stack-apne3 \
--region ap-northeast-3
aws cloudformation delete-stack \
--stack-name parent-stack-apne1 \
--region ap-northeast-1
コードの解説
このままだと内容が薄すぎるので、重要な箇所を解説します。
1. リージョンでの分岐
まず一番のポイントとなるのが、
同じテンプレートファイルでリージョンによって異なるリソースを作り分ける部分です。
まずCloudFormationのConditions
を使って、現在のリージョンを判定しています。
Conditions:
IsTokyoRegion: !Equals [!Ref "AWS::Region", "ap-northeast-1"]
IsOsakaRegion: !Equals [!Ref "AWS::Region", "ap-northeast-3"]
リソース側ではConditionでConditionsを参照することで、
東京リージョンではプライマリクラスター、大阪リージョンではセカンダリクラスターを作成するように制御できます。
# 東京リージョンでのみ作成されるプライマリクラスター
AuroraCluster:
Type: AWS::RDS::DBCluster
Condition: IsTokyoRegion
Properties: # プライマリクラスターの設定
# 大阪リージョンでのみ作成されるセカンダリクラスター
AuroraSecondaryCluster:
Type: AWS::RDS::DBCluster
Condition: IsOsakaRegion
Properties: # セカンダリクラスター(ヘッドレス)の設定
2. グローバルデータベースの記述
グローバルデータベースの作成では、既存のクラスターを指定する必要があります。
# まず通常のクラスターを作成
AuroraCluster:
Type: AWS::RDS::DBCluster
Condition: IsTokyoRegion
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
Engine: aurora-postgresql
# その他の設定...
# 既存クラスターをグローバル化
AuroraGlobalCluster:
Type: AWS::RDS::GlobalCluster
Condition: IsTokyoRegion
Properties:
GlobalClusterIdentifier: !Sub "${ProjectName}-${Environment}-aurora-global-cluster"
SourceDBClusterIdentifier: !Ref AuroraCluster # 上記で作成したクラスターを参照
DependsOn: AuroraCluster # 依存関係を明示
ポイントはSourceDBClusterIdentifier
で既存クラスターを指定し、
またDependsOn
でしっかりと依存関係を制御することです。
3. セカンダリクラスターでのヘッドレス構成
大阪リージョンのセカンダリクラスターでは、インスタンスを作成せずクラスターのみを作成します。
AuroraSecondaryCluster:
Type: AWS::RDS::DBCluster
Condition: IsOsakaRegion
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
GlobalClusterIdentifier: !Sub "${ProjectName}-${Environment}-aurora-global-cluster" # 既存のグローバルクラスターに参加
Engine: aurora-postgresql
# インスタンスは作成しない = ヘッドレスクラスター
今後のアップデートでマネジメントコンソールでも、
インスタンスを作成するかどうか選べるようになったら嬉しいですね…。
4. リージョン間でのリソース名の統一
各リージョンで適切にリソース名を分けるために、Mappingsを活用しています。
Mappings:
RegionToShortname:
ap-northeast-1:
Shortname: "apne1"
ap-northeast-3:
Shortname: "apne3"
これにより!FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
でリージョンの短縮名を取得し、リソース名に含めています。
さいごに
以上、Aurora グローバルデータベース + ヘッドレスクラスター を一つのCloudFormationでまとめて作ってみました。
同じテンプレートファイルでリージョンによって作成するリソースを分岐させるテクニックは、Aurora Global Database以外にも様々な場面で応用できます。
例えば、プライマリ・セカンダリ構成のアプリケーションや、リージョン別に異なるリソース構成が必要な場合など、マルチリージョン展開でお困りの際にはぜひ活用してみてください。
正直Aurora Global Databaseを触る機会はそんなに多くないかもしれませんが、
本記事が迷えるCloudFormationユーザーのお役に立てれば嬉しいです。