Creating an Amazon Aurora Global Database + headless cluster together in a single CloudFormation template.
Introduction
Hello everyone, I'm Akaike.
"Creating an Aurora Global Database through the management console is a bit of a hassle..."
"If only I could create it all at once with CloudFormation..."
Have you ever thought something like this? I have.
Especially when creating a headless cluster in a secondary region,
manually, you need to create instances and then deliberately delete them afterward, which is honestly troublesome.
To solve these issues,
I've created a single CloudFormation template that supports multiple regions to create an Aurora Global Database + headless cluster.
Motivation
1. Manual Creation Process
When trying to manually create an Amazon Aurora Global Database + headless cluster, the following steps are required:
- Main region
- Create Aurora cluster
- Create global database
- Secondary region
- Create Aurora cluster & instance (simultaneously with global database creation)
- Delete Aurora instance
As mentioned earlier, the annoying part is having to manually create the cluster and then deliberately delete the secondary instance.
This series of operations would probably take more than 30 minutes...
2. Creating with CloudFormation
Next, when creating with CloudFormation.
Compared to manual creation, it reduces the effort of deleting the secondary Aurora instance, saving time.
- Main region
- Create Aurora cluster
- Create global database
- Secondary region
- Create Aurora cluster
And when creating resources across multiple regions,
I think it's common to separate CloudFormation templates by region as follows:
├── README.md
├── region1/
│ └── aurora.yaml
└── region2/
└── aurora.yaml
However, dividing by region doubles the number of files and results in many duplicate entries, which is honestly tiring...
So this time, I'll create it so that the same template file can be used across regions.
Current Architecture
The architecture is as follows:
A simple configuration with one primary cluster and one secondary cluster.
The primary cluster has two instances, one writer and one reader,
while the secondary cluster is a headless cluster, so there are no instances.
Stack / Code Structure
I decided to use a nested stack structure for the following reasons:
- Improved reusability through modularization (child stacks)
- Ability to reference values between stacks without using cross-stack references
The directory structure is as follows.
Parent stacks/child stacks are designed to be usable across regions.
.
└── cloudformation/
├── 01-parent/ # Parent stack
│ └── parent-cfn.yaml
└── 02-child/ # Child stack
├── vpc-child-cfn.yaml
├── sg-child-cfn.yaml
└── aurora-child-cfn.yaml
```### Parent Stack
```yaml:parent-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
```### Child Stack
```yaml:vpc-child-cfn.yaml
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"
``````yaml:sg-child-cfn.yaml
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"
```### Deployment
Create an S3 bucket to store template files for child stacks.
The bucket name should follow the format defined as Parameters in the template file:
"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;
Store the template files for child stacks.
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;
Create stacks with the following commands.
Define the username and password for DB use as environment variables.
```txt:Environment variable setup
MasterUsername='postgres'
MasterUserPassword=$(openssl rand -base64 12)
First, create the primary stack (Tokyo region).
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
After the primary stack creation is complete,
create the secondary stack (Osaka region).
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
Once the secondary stack creation is complete, the structure described at the beginning will be complete.
Deletion Method
When deleting resources,
delete the secondary stack first, opposite to the creation order.
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
```## Code Explanation
Since the content would be too thin as is, I will explain the important parts.
### 1. Region-based Branching
First, the most important point is
how to create different resources in the same template file depending on the region.
We use CloudFormation's `Conditions` to determine the current region.
```yaml
Conditions:
IsTokyoRegion: !Equals [!Ref "AWS::Region", "ap-northeast-1"]
IsOsakaRegion: !Equals [!Ref "AWS::Region", "ap-northeast-3"]
On the resource side, by referencing these Conditions with the Condition property,
we can control the creation of a primary cluster in the Tokyo region and a secondary cluster in the Osaka region.
# Primary cluster created only in the Tokyo region
AuroraCluster:
Type: AWS::RDS::DBCluster
Condition: IsTokyoRegion
Properties: # Primary cluster configuration
# Secondary cluster created only in the Osaka region
AuroraSecondaryCluster:
Type: AWS::RDS::DBCluster
Condition: IsOsakaRegion
Properties: # Secondary cluster (headless) configuration
2. Global Database Description
When creating a global database, you need to specify an existing cluster.
# First create a normal cluster
AuroraCluster:
Type: AWS::RDS::DBCluster
Condition: IsTokyoRegion
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
Engine: aurora-postgresql
# Other settings...
# Make the existing cluster global
AuroraGlobalCluster:
Type: AWS::RDS::GlobalCluster
Condition: IsTokyoRegion
Properties:
GlobalClusterIdentifier: !Sub "${ProjectName}-${Environment}-aurora-global-cluster"
SourceDBClusterIdentifier: !Ref AuroraCluster # Reference the cluster created above
DependsOn: AuroraCluster # Explicitly specify dependency
The key points are to specify an existing cluster with SourceDBClusterIdentifier
and
to properly control dependencies with DependsOn
.
3. Headless Configuration for Secondary Cluster
In the Osaka region's secondary cluster, we create only a cluster without instances.
AuroraSecondaryCluster:
Type: AWS::RDS::DBCluster
Condition: IsOsakaRegion
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-${Environment}-${RegionToShortname}-aurora-cluster"
GlobalClusterIdentifier: !Sub "${ProjectName}-${Environment}-aurora-global-cluster" # Join existing global cluster
Engine: aurora-postgresql
# No instances created = headless cluster
It would be nice if future updates to the management console would allow us
to choose whether to create instances or not...### 4. Unifying resource names across regions
To properly separate resource names in each region, we utilize Mappings.
Mappings:
RegionToShortname:
ap-northeast-1:
Shortname: "apne1"
ap-northeast-3:
Shortname: "apne3"
This allows us to obtain the abbreviated region name with !FindInMap [RegionToShortname, !Ref "AWS::Region", Shortname]
and include it in resource names.
Conclusion
I've shown how to create Aurora Global Database + Headless Cluster together in one CloudFormation template.
The technique of branching resources created by region within the same template file can be applied to various scenarios beyond Aurora Global Database.
For example, when configuring primary-secondary applications or when different resource configurations are needed by region, please consider using this approach when dealing with multi-region deployments.
Honestly, you may not have many opportunities to work with Aurora Global Database, but
I hope this article will be helpful to CloudFormation users who are struggling with similar challenges.