AWSTemplateFormatVersion: "2010-09-09"
Description: Multi-tenant Amazon Aurora MySQL with RDS Proxy and an optional load test (uksb-1rkqqff0c).
Metadata:
Version: 0.0.9
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Region configuration
Parameters:
- AvailabilityZones
- Label:
default: Main configuration
Parameters:
- DBReaderInstanceClass
- DBWriterInstanceClass
- PerformanceInsightsRetentionPeriod
- LambdaRuntimeEnv
- Environment
- EnableFlowLogs
- Label:
default: Load test configuration
Parameters:
- CreateLoadTest
- UsersToCreate
- LocustAmiId
- LocustInstanceType
- LocustVersion
- LocustSecondaryInstanceCapacity
- ApiEndpointType
- CIDRRange
ParameterLabels:
AvailabilityZones:
default: Availability Zones
DBReaderInstanceClass:
default: Database Reader Instance Class
DBWriterInstanceClass:
default: Database Writer Instance Class
PerformanceInsightsRetentionPeriod:
default: Performance Insights Retention Period
LambdaRuntimeEnv:
default: Lambda Runtime Environment
Environment:
default: Infrastructure Environment
CreateLoadTest:
default: Create Load Test Stack
UsersToCreate:
default: Number of Tenants to Create
LocustAmiId:
default: Latest Amazon Linux AMI
LocustInstanceType:
default: Locust Instance Type
LocustVersion:
default: Locust App Version
LocustSecondaryInstanceCapacity:
default: Locust Worker Instances
ApiEndpointType:
default: API Endpoint Type
CIDRRange:
default: ISP/Public IPv4
EnableFlowLogs:
default: Flow Logs
Parameters:
CreateLoadTest:
Description: If False, this creates a Proxy VPC and accompanying reosurces. If True, this additionally creates a Load Test VPC, No Proxy VPC, and accompanying reosurces, in order to run a load test and compare metrics between the Proxy and No Proxy VPCs.
Type: String
AllowedValues:
- false
- true
Default: true
ConstraintDescription: Must specify 'true' or 'false'
UsersToCreate:
Description: The number of tenants to create in the Proxy and No Proxy VPC Aurora clusters. Each tenant has their own dedicated database containing dummy data. Allowed values are 1-200.
Type: String
Default: 200
AllowedPattern: ^(20[0]|1[0-9][0-9]|[1-9]?[1-9])$
ConstraintDescription: This parameter must be in the range 1-200
AvailabilityZones:
Description: The list of Availability Zones to use for the subnets in the VPCs. Select two Availability Zones from the list.
Type: List<AWS::EC2::AvailabilityZone::Name>
DBReaderInstanceClass:
Description: The database instance class for the Proxy and No Proxy VPC Amazon Aurora Replicas, for example db.m5.large.
Type: String
Default: db.r5.large
MinLength: 6
DBWriterInstanceClass:
Description: The database instance class for the Proxy and No Proxy VPC Amazon Aurora Writer, for example db.m5.large.
Type: String
Default: db.t3.medium
MinLength: 6
PerformanceInsightsRetentionPeriod:
Description: The amount of time, in days, to retain RDS Performance Insights data. Valid values are 7 and 731 (2 years).
Type: String
AllowedValues:
- 7
- 731
Default: 7
LambdaRuntimeEnv:
Description: The runtime for the Lambda access function. Valid values are (Python, Nodejs).
Type: String
AllowedValues:
- Python
- Nodejs
Default: Nodejs
LocustAmiId:
Description: The latest Amazon Linux AMI from AWS Systems Manager Parameter Store.
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
LocustInstanceType:
Description: The Amazon EC2 instance type used in the Load Test VPC that runs Locust.
Type: String
Default: c5.large
MinLength: 6
LocustVersion:
Description: The Locust version to deploy.
Type: String
Default: latest
MinLength: 1
LocustSecondaryInstanceCapacity:
Description: The number of secondary Amazon EC2s for the Load Test Cluster. Allowed values are 2-20.
Type: String
Default: 2
AllowedPattern: ^(20|[2-9]|1[0-9])$
ConstraintDescription: This parameter must be in the range 2-20
MinLength: 1
ApiEndpointType:
Description: The Amazon API Gateway endpoint type. Valid values are (EDGE, REGIONAL, PRIVATE).
Type: String
AllowedValues:
- EDGE
- REGIONAL
- PRIVATE
Default: PRIVATE
CIDRRange:
Description: The CIDR block or your IP address that you will use to connect to the Locust Dashboard (e.g. 192.168.192.168/32). This limits the CIDR range from which the Locust dashboard can be accessed. You can use an open CIDR range (e.g. 0.0.0.0/0) to access from anywhere, but this is not recommended.
Type: String
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-32
MinLength: 1
Environment:
Description: The type of environment with which to tag your infrastructure. Valid values are DEV (development), TEST (test), or PROD (production).
Type: String
AllowedValues:
- DEV
- TEST
- PROD
Default: DEV
EnableFlowLogs:
Description: Creates an optional CloudWatch Logs group to send the VPC flow logs to. Flow Logs incur additional costs. Set to "false" to disable.
Type: String
AllowedValues:
- true
- false
Default: false
Mappings:
StackMap:
ProxyMap:
VPCCidr: 10.0.0.0/16
PublicSubnet1Cidr: 10.0.0.0/24
PublicSubnet2Cidr: 10.0.1.0/24
PrivateSubnet1Cidr: 10.0.2.0/24
PrivateSubnet2Cidr: 10.0.3.0/24
ResourceTags: rds-proxy
CreateRDSProxy: true
NoProxyMap:
VPCCidr: 10.1.0.0/16
PublicSubnet1Cidr: 10.1.0.0/24
PublicSubnet2Cidr: 10.1.1.0/24
PrivateSubnet1Cidr: 10.1.2.0/24
PrivateSubnet2Cidr: 10.1.3.0/24
ResourceTags: rds-no-proxy
CreateRDSProxy: false
LoadTestMap:
VPCCidr: 10.2.0.0/16
PublicSubnet1Cidr: 10.2.0.0/24
PublicSubnet2Cidr: 10.2.1.0/24
PrivateSubnet1Cidr: 10.2.2.0/24
PrivateSubnet2Cidr: 10.2.3.0/24
ResourceTags: rds-load-test
Conditions:
IsLoadTest: !Equals [!Ref CreateLoadTest, true]
IsPython: !Equals [!Ref LambdaRuntimeEnv, Python]
Resources:
ProxyVPCStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: vpc.template
Parameters:
AvailabilityZones: !Join [',', !Ref AvailabilityZones]
VPCCidr: !FindInMap [StackMap, ProxyMap, VPCCidr]
PublicSubnet1Cidr: !FindInMap [StackMap, ProxyMap, PublicSubnet1Cidr]
PublicSubnet2Cidr: !FindInMap [StackMap, ProxyMap, PublicSubnet2Cidr]
PrivateSubnet1Cidr: !FindInMap [StackMap, ProxyMap, PrivateSubnet1Cidr]
PrivateSubnet2Cidr: !FindInMap [StackMap, ProxyMap, PrivateSubnet2Cidr]
ResourceTags: !FindInMap [StackMap, ProxyMap, ResourceTags]
EnableFlowLogs: !Ref EnableFlowLogs
Tags:
- Key: Project
Value: !FindInMap [StackMap, ProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
NoProxyVPCStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: vpc.template
Parameters:
AvailabilityZones: !Join
- ','
- !Ref AvailabilityZones
VPCCidr: !FindInMap [StackMap, NoProxyMap, VPCCidr]
PublicSubnet1Cidr: !FindInMap [StackMap, NoProxyMap, PublicSubnet1Cidr]
PublicSubnet2Cidr: !FindInMap [StackMap, NoProxyMap, PublicSubnet2Cidr]
PrivateSubnet1Cidr: !FindInMap [StackMap, NoProxyMap, PrivateSubnet1Cidr]
PrivateSubnet2Cidr: !FindInMap [StackMap, NoProxyMap, PrivateSubnet2Cidr]
ResourceTags: !FindInMap [StackMap, NoProxyMap, ResourceTags]
EnableFlowLogs: !Ref EnableFlowLogs
Tags:
- Key: Project
Value: !FindInMap [StackMap, NoProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
LoadTestVPCStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: vpc.template
Parameters:
AvailabilityZones: !Join
- ','
- !Ref AvailabilityZones
VPCCidr: !FindInMap [StackMap, LoadTestMap, VPCCidr]
PublicSubnet1Cidr: !FindInMap [StackMap, LoadTestMap, PublicSubnet1Cidr]
PublicSubnet2Cidr: !FindInMap [StackMap, LoadTestMap, PublicSubnet2Cidr]
PrivateSubnet1Cidr: !FindInMap [StackMap, LoadTestMap, PrivateSubnet1Cidr]
PrivateSubnet2Cidr: !FindInMap [StackMap, LoadTestMap, PrivateSubnet2Cidr]
ResourceTags: !FindInMap [StackMap, LoadTestMap, ResourceTags]
EnableFlowLogs: !Ref EnableFlowLogs
Tags:
- Key: Project
Value: !FindInMap [StackMap, LoadTestMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
ProxyRDSStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: rds.template
Parameters:
AvailabilityZones: !Join
- ','
- !Ref AvailabilityZones
VpcID: !GetAtt ProxyVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet2
DBReaderInstanceClass: !Ref DBReaderInstanceClass
DBWriterInstanceClass: !Ref DBWriterInstanceClass
PerformanceInsightsRetentionPeriod: !Ref PerformanceInsightsRetentionPeriod
CreateRDSProxy: !FindInMap [StackMap, ProxyMap, CreateRDSProxy]
LayerVersionArn: !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer
Tags:
- Key: Project
Value: !FindInMap [StackMap, ProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
NoProxyRDSStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: rds.template
Parameters:
AvailabilityZones: !Join
- ','
- !Ref AvailabilityZones
VpcID: !GetAtt NoProxyVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet2
DBReaderInstanceClass: !Ref DBReaderInstanceClass
DBWriterInstanceClass: !Ref DBWriterInstanceClass
PerformanceInsightsRetentionPeriod: !Ref PerformanceInsightsRetentionPeriod
CreateRDSProxy: !FindInMap [StackMap, NoProxyMap, CreateRDSProxy]
LayerVersionArn: !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer
Tags:
- Key: Project
Value: !FindInMap [StackMap, NoProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
ProxyLayerVersionStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: lambda-layer.template
Parameters:
LambdaRuntimeEnv: !Ref LambdaRuntimeEnv
Tags:
- Key: Project
Value: !FindInMap [StackMap, ProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
NoProxyLayerVersionStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: lambda-layer.template
Parameters:
LambdaRuntimeEnv: !Ref LambdaRuntimeEnv
Tags:
- Key: Project
Value: !FindInMap [StackMap, NoProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
ProxyAccessStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: access-function.template
Parameters:
VpcID: !GetAtt ProxyVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet2
LambdaRuntimeEnv: !Ref LambdaRuntimeEnv
LayerVersionArn: !If
- IsPython
- !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer
- !GetAtt ProxyLayerVersionStack.Outputs.NodeJsLambdaLayer
Endpoint: !GetAtt ProxyRDSStack.Outputs.ProxyEndpoint
RDSSecurityGroup: !GetAtt ProxyRDSStack.Outputs.RDSSecurityGroup
ProxySecurityGroup: !GetAtt ProxyRDSStack.Outputs.ProxySecurityGroup
CreateRDSProxy: !FindInMap [StackMap, ProxyMap, CreateRDSProxy]
ClusterEndpointResourceId: !GetAtt ProxyRDSStack.Outputs.ClusterEndpointResourceId
SSLCertURL: https://www.amazontrust.com/repository/AmazonRootCA1.pem
Tags:
- Key: Project
Value: !FindInMap [StackMap, ProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
NoProxyAccessStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: access-function.template
Parameters:
VpcID: !GetAtt NoProxyVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet2
LambdaRuntimeEnv: !Ref LambdaRuntimeEnv
LayerVersionArn: !If
- IsPython
- !GetAtt NoProxyLayerVersionStack.Outputs.PythonLambdaLayer
- !GetAtt NoProxyLayerVersionStack.Outputs.NodeJsLambdaLayer
Endpoint: !GetAtt NoProxyRDSStack.Outputs.ClusterReaderEndpoint
RDSSecurityGroup: !GetAtt NoProxyRDSStack.Outputs.RDSSecurityGroup
ProxySecurityGroup: NA
CreateRDSProxy: !FindInMap [StackMap, NoProxyMap, CreateRDSProxy]
ClusterEndpointResourceId: !GetAtt NoProxyRDSStack.Outputs.ClusterEndpointResourceId
SSLCertURL: !Sub https://truststore.pki.rds.amazonaws.com/${AWS::Region}/${AWS::Region}-bundle.pem
Tags:
- Key: Project
Value: !FindInMap [StackMap, NoProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
ProxyAddUsersStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: add-users.template
Parameters:
VpcID: !GetAtt ProxyVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet2
LayerVersionArn: !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer
Endpoint: !GetAtt ProxyRDSStack.Outputs.ClusterEndpoint
RDSSecurityGroup: !GetAtt ProxyRDSStack.Outputs.RDSSecurityGroup
CreateRDSProxy: !FindInMap [StackMap, ProxyMap, CreateRDSProxy]
ProxySecurityGroup: !GetAtt ProxyRDSStack.Outputs.ProxySecurityGroup
DBSecretArn: !GetAtt ProxyRDSStack.Outputs.DBSecret
DBProxyName: !GetAtt ProxyRDSStack.Outputs.DBProxyName
DBProxyArn: !GetAtt ProxyRDSStack.Outputs.DBProxyArn
UsersToCreate: !Ref UsersToCreate
Tags:
- Key: Project
Value: !FindInMap [StackMap, ProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
NoProxyAddUsersStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: add-users.template
Parameters:
VpcID: !GetAtt NoProxyVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet2
LayerVersionArn: !GetAtt NoProxyLayerVersionStack.Outputs.PythonLambdaLayer
Endpoint: !GetAtt NoProxyRDSStack.Outputs.ClusterEndpoint
RDSSecurityGroup: !GetAtt NoProxyRDSStack.Outputs.RDSSecurityGroup
ProxySecurityGroup: NA
CreateRDSProxy: !FindInMap [StackMap, NoProxyMap, CreateRDSProxy]
DBSecretArn: !GetAtt NoProxyRDSStack.Outputs.DBSecret
DBProxyName: NA
DBProxyArn: NA
UsersToCreate: !Ref UsersToCreate
Tags:
- Key: Project
Value: !FindInMap [StackMap, NoProxyMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
DashboardStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: dashboard.template
Parameters:
ProxyAccessLambda: !If
- IsPython
- !GetAtt ProxyAccessStack.Outputs.PythonFunction
- !GetAtt ProxyAccessStack.Outputs.NodeJsFunction
NoProxyAccessLambda: !If
- IsPython
- !GetAtt NoProxyAccessStack.Outputs.PythonFunction
- !GetAtt NoProxyAccessStack.Outputs.NodeJsFunction
ProxyClusterName: !GetAtt ProxyRDSStack.Outputs.DBClusterName
NoProxyClusterName: !GetAtt NoProxyRDSStack.Outputs.DBClusterName
DBProxyName: !GetAtt ProxyRDSStack.Outputs.DBProxyName
ApiGatewayName: !GetAtt ApiStack.Outputs.ApiGatewayName
Tags:
- Key: Project
Value: !FindInMap [StackMap, LoadTestMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
LocustStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ec2.template
Parameters:
VpcCidrBlock: !GetAtt LoadTestVPCStack.Outputs.VpcCidrBlock
VpcID: !GetAtt LoadTestVPCStack.Outputs.VpcID
SubnetID: !GetAtt LoadTestVPCStack.Outputs.PublicSubnet1
InstanceAMI: !Ref LocustAmiId
InstanceType: !Ref LocustInstanceType
LocustVersion: !Ref LocustVersion
SecondaryInstanceCapacity: !Ref LocustSecondaryInstanceCapacity
CIDRRange: !Ref CIDRRange
UsersToCreate: !Ref UsersToCreate
VPCEndpointSecurityGroup: !GetAtt ApiStack.Outputs.VPCEndpointSecurityGroup
Tags:
- Key: Project
Value: !FindInMap [StackMap, LoadTestMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
ApiStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: api.template
Parameters:
VpcCidrBlock: !GetAtt LoadTestVPCStack.Outputs.VpcCidrBlock
VpcID: !GetAtt LoadTestVPCStack.Outputs.VpcID
PrivateSubnet1: !GetAtt LoadTestVPCStack.Outputs.PrivateSubnet1
PrivateSubnet2: !GetAtt LoadTestVPCStack.Outputs.PrivateSubnet2
ProxyAccessLambda: !If
- IsPython
- !GetAtt ProxyAccessStack.Outputs.PythonFunction
- !GetAtt ProxyAccessStack.Outputs.NodeJsFunction
NoProxyAccessLambda: !If
- IsPython
- !GetAtt NoProxyAccessStack.Outputs.PythonFunction
- !GetAtt NoProxyAccessStack.Outputs.NodeJsFunction
ApiEndpointType: !Ref ApiEndpointType
Tags:
- Key: Project
Value: !FindInMap [StackMap, LoadTestMap, ResourceTags]
- Key: Environment
Value: !Ref Environment
Condition: IsLoadTest
Outputs:
LocustAddress:
Description: The address of the Locust primary instance.
Value: !GetAtt LocustStack.Outputs.LocustAddress
Condition: IsLoadTest
APIGatewayURL:
Description: URL of the API Gateway.
Value: !GetAtt ApiStack.Outputs.ApiGatewayURL
Condition: IsLoadTest
ProxyLambdaName:
Description: Proxy tenant data access lambda function.
Value: !If
- IsPython
- !GetAtt ProxyAccessStack.Outputs.PythonFunction
- !GetAtt ProxyAccessStack.Outputs.NodeJsFunction
NoProxyLambdaName:
Description: NoProxy tenant data access lambda function.
Value: !If
- IsPython
- !GetAtt NoProxyAccessStack.Outputs.PythonFunction
- !GetAtt NoProxyAccessStack.Outputs.NodeJsFunction
Condition: IsLoadTest
ProxyAuroraReplica1:
Description: Proxy Aurora replica 1.
Value: !GetAtt ProxyRDSStack.Outputs.AuroraReplicaName1
NoProxyAuroraReplica1:
Description: NoProxy Aurora replica 1.
Value: !GetAtt NoProxyRDSStack.Outputs.AuroraReplicaName1
Condition: IsLoadTest
ProxyAuroraReplica2:
Description: Proxy Aurora replica 2.
Value: !GetAtt ProxyRDSStack.Outputs.AuroraReplicaName2
NoProxyAuroraReplica2:
Description: NoProxy Aurora replica 2.
Value: !GetAtt NoProxyRDSStack.Outputs.AuroraReplicaName2
Condition: IsLoadTest
DashboardName:
Description: CLoudWatch dashboard.
Value: !GetAtt DashboardStack.Outputs.LoadTestDashboard
Condition: IsLoadTest
APIGatewayLogGroupName:
Description: The CloudWatch Group for load test API Gateway.
Value: !GetAtt ApiStack.Outputs.APIGatewayLogGroupName
Condition: IsLoadTest