-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAuroraServerlessV2Construct.ts
More file actions
223 lines (204 loc) · 7.56 KB
/
AuroraServerlessV2Construct.ts
File metadata and controls
223 lines (204 loc) · 7.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { IVpc } from "aws-cdk-lib/aws-ec2";
import * as rds from "aws-cdk-lib/aws-rds";
import * as secretsManager from "aws-cdk-lib/aws-secretsmanager";
import { Duration } from "aws-cdk-lib/core";
import { Construct } from "constructs";
import { StringParameter } from "aws-cdk-lib/aws-ssm";
import { CfnDBCluster } from "aws-cdk-lib/aws-rds";
/**
* Properties for configuring the AuroraServerlessV2Construct.
*/
export interface AuroraServerlessV2ConstructProps {
/**
* The application name to use as a prefix in resource names.
*/
appName: string;
/**
* The environment tag (e.g., 'dev', 'staging', 'prod').
*/
tagEnv: string;
/**
* The TCP port for the Aurora database (default: 5432 for PostgreSQL).
*/
auroraPort: number;
/**
* The VPC where the Aurora database will be deployed.
*/
vpc: IVpc;
/**
* Optional flag to create database credentials. If false, credentials will be loaded from Secrets Manager.
*/
createCredentials?: boolean;
/**
* Optional database name. Defaults to `${appName}_${tagEnv}_aurora_db`.
*/
databaseName?: string;
}
/**
* Creates an Aurora Serverless v2 PostgreSQL database instance with proper security configuration.
*
* This construct deploys a cost-effective, auto-scaling Aurora Serverless v2 instance that
* automatically scales based on workload. It handles proper networking, security groups,
* encryption, and parameter configuration.
*
* Features:
* - Serverless v2 with auto-scaling capabilities
* - Multiple Availability Zone deployment for high availability
* - Encrypted storage
* - Performance insights enabled
* - Configurable ACU range for cost optimization
* - SSM parameters for endpoint discovery
*
* @example
* const aurora = new AuroraServerlessV2Construct(this, 'Database', {
* appName: 'my-app',
* tagEnv: 'dev',
* auroraPort: 5432,
* vpc: myVpc,
* createCredentials: true
* });
*/
export class AuroraServerlessV2Construct extends Construct {
/**
* The created Aurora database cluster.
*/
public readonly dbCluster: rds.DatabaseCluster;
/**
* Secret containing database credentials.
*/
public readonly dbCredentials: secretsManager.ISecret;
/**
* Constructs a new instance of the AuroraServerlessV2Construct.
*
* @param {Construct} scope - The parent construct, typically a CDK stack.
* @param {string} id - The unique identifier for this construct.
* @param {AuroraServerlessV2ConstructProps} props - Properties for configuring the Aurora database.
*/
constructor(
scope: Construct,
id: string,
props: AuroraServerlessV2ConstructProps
) {
super(scope, id);
/* Define Security Group for RDS */
const rdsSecurityGroup = new ec2.SecurityGroup(
this,
`${props.appName}-${props.tagEnv}-aurora-sg`,
{
description: "Allow traffic to Aurora Serverless v2 instance",
securityGroupName: `${props.appName}_${props.tagEnv}_aurora_sg`,
vpc: props.vpc,
}
);
/* Allow traffic from within the VPC */
rdsSecurityGroup.addIngressRule(
ec2.Peer.ipv4(props.vpc.vpcCidrBlock),
ec2.Port.tcp(props.auroraPort),
"Allow PostgreSQL traffic from within the VPC"
);
/* Determine if production environment for appropriate scaling and retention */
const isProd = props.tagEnv === "prod";
/* Set up database credentials either from existing secret or create new ones */
if (props.createCredentials) {
/* Create new credentials if specified */
this.dbCredentials = new secretsManager.Secret(
this,
`${props.appName}-aurora-credentials`,
{
secretName: `${props.appName}-aurora-postgres-db-credentials`,
description: `Credentials for ${props.appName} Aurora PostgreSQL database`,
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: "postgres" }),
generateStringKey: "password",
excludePunctuation: true,
excludeCharacters: "\"@/\\'",
},
}
);
} else {
/* Use existing credentials from Secrets Manager */
this.dbCredentials = secretsManager.Secret.fromSecretNameV2(
this,
`${props.appName}-aurora-postgres-db-credentials`,
`${props.appName}-aurora-postgres-db-credentials`
);
}
/* Aurora PostgreSQL engine version for Serverless V2 */
const dbEngine = rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_16_8,
});
/* Create the database name */
const dbName =
props.databaseName || `${props.appName}_${props.tagEnv}_aurora_db`;
/* Create the Aurora Serverless v2 cluster */
this.dbCluster = new rds.DatabaseCluster(
this,
`${props.appName}-${props.tagEnv}-aurora-db-cluster`,
{
backup: { retention: isProd ? Duration.days(30) : Duration.days(7) },
clusterIdentifier: `${props.appName}-${props.tagEnv}-aurora-db-cluster`,
credentials: rds.Credentials.fromSecret(this.dbCredentials),
defaultDatabaseName: dbName,
enableDataApi: true,
engine: dbEngine,
readers: [
rds.ClusterInstance.serverlessV2(
`${props.appName}-${props.tagEnv}-aurora-db-reader`,
{
scaleWithWriter: true,
instanceIdentifier: `${props.appName}-${props.tagEnv}-reader`,
enablePerformanceInsights: true,
}
),
],
removalPolicy: isProd
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY,
securityGroups: [rdsSecurityGroup],
serverlessV2MaxCapacity: isProd ? 8 : 4 /* Maximum ACUs */,
serverlessV2MinCapacity: isProd
? 1
: 0 /* scale down to 0 in non-prod environments after 15 minutes of inactivity to save costs. A call to the database will wake it up but it will take take around 30 seconds to be ready.*/,
parameters: {
max_parallel_workers: "16",
max_parallel_workers_per_gather: "4",
idle_in_transaction_session_timeout: "30000",
},
storageEncrypted: true,
vpc: props.vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
writer: rds.ClusterInstance.serverlessV2(
`${props.appName}-${props.tagEnv}-aurora-db-writer`,
{
instanceIdentifier: `${props.appName}-${props.tagEnv}-writer`,
enablePerformanceInsights: true,
}
),
}
);
/* Set the auto-pause duration to 15 minutes (unless it's prod) */
if (!isProd) {
const cfnDbCluster = this.dbCluster.node.defaultChild;
if (cfnDbCluster instanceof CfnDBCluster) {
cfnDbCluster.addPropertyOverride(
"ServerlessV2ScalingConfiguration.SecondsUntilAutoPause",
Duration.minutes(15).toSeconds()
);
}
}
/* Create Aurora Writer Endpoint SSM Parameter */
new StringParameter(this, `${props.appName}-aurora-writer-endpoint-ssm`, {
parameterName: `/${props.appName}/aurora-writer-endpoint`,
description: `${props.appName} Aurora Writer Instance Endpoint`,
stringValue: this.dbCluster.clusterEndpoint.hostname,
});
/* Create Aurora Reader Endpoint SSM Parameter */
new StringParameter(this, `${props.appName}-aurora-reader-endpoint-ssm`, {
parameterName: `/${props.appName}/aurora-reader-endpoint`,
description: `${props.appName} Aurora Reader Instance Endpoint`,
stringValue: this.dbCluster.clusterReadEndpoint.hostname,
});
}
}