@infraless-cdk-constructs/aurora-cluster
v1.0.2
Published
AWS CDK L3 construct for Aurora RDS clusters with production-ready defaults
Downloads
22
Maintainers
Readme
Aurora Cluster CDK Construct
An AWS CDK L3 construct for creating production-ready Aurora RDS clusters with sensible defaults and comprehensive configuration options.
Features
- ✅ Dual Engine Support: Both Aurora PostgreSQL and MySQL
- ✅ Automatic VPC Integration: Smart subnet selection and security group management
- ✅ Secure by Default: KMS encryption, Secrets Manager integration, deletion protection
- ✅ High Availability: Multi-AZ with configurable reader instances
- ✅ Snapshot Restoration: Create clusters from existing snapshots
- ✅ IAM Authentication: Built-in support for IAM database authentication
- ✅ SSM Parameter Store: Automatic export of cluster outputs for cross-stack references
- ✅ CloudWatch Integration: Optional log exports with configurable retention
- ✅ Performance Insights: Optional performance monitoring
- ✅ Extensible: Override any resource creation via protected hook methods
- ✅ Production-Ready Defaults: 30-day backups, encryption enabled, secure passwords
Installation
npm install @cdk-constructs/aurora-clusterOr with yarn:
yarn add @cdk-constructs/aurora-clusterBasic Usage
PostgreSQL Cluster
import { AuroraCluster } from '@cdk-constructs/aurora-cluster';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
const cluster = new AuroraCluster(this, 'Database', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'production-postgres-cluster'
});
// Access cluster properties
console.log('Cluster ID:', cluster.clusterIdentifier);
console.log('Endpoint:', cluster.clusterEndpoint.hostname);
console.log('Secret ARN:', cluster.secret.secretArn);MySQL Cluster
const mysqlCluster = new AuroraCluster(this, 'MysqlDatabase', {
vpc: myVpc,
engineVersion: rds.AuroraMysqlEngineVersion.VER_3_03_0,
databaseName: 'myapp',
username: 'admin',
clusterName: 'production-mysql-cluster'
});Advanced Examples
High Availability with Multiple Readers
const haCluster = new AuroraCluster(this, 'HADatabase', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'ha-cluster',
instanceCount: 3, // 1 writer + 2 readers
instanceClass: 'MEMORY4',
instanceSize: 'LARGE'
});Snapshot Restoration
const restoredCluster = new AuroraCluster(this, 'RestoredDatabase', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'restored',
username: 'admin',
clusterName: 'restored-from-snapshot',
snapshotId: 'rds:my-cluster-2024-01-15-final-snapshot'
});Custom Security Groups
const customSg = new ec2.SecurityGroup(this, 'CustomSG', {
vpc: myVpc,
description: 'Custom security group for database'
});
customSg.addIngressRule(
ec2.Peer.ipv4('10.0.0.0/16'),
ec2.Port.tcp(5432),
'Allow from application subnets'
);
const cluster = new AuroraCluster(this, 'Database', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'custom-sg-cluster',
securityGroups: [customSg]
});CloudWatch Logs Integration
import { Duration } from 'aws-cdk-lib';
import * as logs from 'aws-cdk-lib/aws-logs';
// PostgreSQL with logs
const pgCluster = new AuroraCluster(this, 'PostgresDB', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'postgres-with-logs',
cloudwatchLogsExports: ['postgresql'],
cloudwatchLogsRetention: logs.RetentionDays.ONE_MONTH
});
// MySQL with logs
const mysqlCluster = new AuroraCluster(this, 'MysqlDB', {
vpc: myVpc,
engineVersion: rds.AuroraMysqlEngineVersion.VER_3_03_0,
databaseName: 'myapp',
username: 'admin',
clusterName: 'mysql-with-logs',
cloudwatchLogsExports: ['error', 'general', 'slowquery'],
cloudwatchLogsRetention: logs.RetentionDays.TWO_WEEKS
});IAM Database Authentication
import * as iam from 'aws-cdk-lib/aws-iam';
const cluster = new AuroraCluster(this, 'Database', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'iam-auth-cluster'
});
// Grant a Lambda function permission to connect as 'app_user'
const lambdaRole = iam.Role.fromRoleArn(
this,
'LambdaRole',
'arn:aws:iam::123456789012:role/my-lambda-role'
);
cluster.grantConnect(lambdaRole, 'app_user');
// Don't forget to create the database user with rds_iam role:
// CREATE USER app_user;
// GRANT rds_iam TO app_user;SSM Parameter Store Integration
The construct automatically stores cluster connection details in AWS Systems Manager Parameter Store for easy cross-stack references and external access without CloudFormation exports.
Default Behavior (Enabled)
By default, the construct creates four SSM parameters:
const cluster = new AuroraCluster(this, 'Database', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'production-cluster'
// SSM parameters automatically created at:
// /cdk/Database/cluster-identifier
// /cdk/Database/endpoint-hostname
// /cdk/Database/endpoint-port
// /cdk/Database/secret-arn
});Parameters Created
| Parameter Name | Value | Description |
|----------------|-------|-------------|
| ${prefix}/cluster-identifier | Cluster ID | RDS Aurora cluster identifier |
| ${prefix}/endpoint-hostname | Hostname | Cluster endpoint hostname |
| ${prefix}/endpoint-port | Port | Cluster endpoint port (5432 or 3306) |
| ${prefix}/secret-arn | ARN | Secrets Manager secret ARN |
Custom Prefix
Organize parameters with a custom prefix:
const cluster = new AuroraCluster(this, 'Database', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'production-cluster',
ssmParameterPrefix: '/myorg/databases/production'
// Parameters created at:
// /myorg/databases/production/cluster-identifier
// /myorg/databases/production/endpoint-hostname
// /myorg/databases/production/endpoint-port
// /myorg/databases/production/secret-arn
});Disable SSM Parameters
For development environments or when parameters aren't needed:
const devCluster = new AuroraCluster(this, 'DevDatabase', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'dev',
username: 'admin',
clusterName: 'dev-cluster',
enableSsmParameters: false // No SSM parameters created
});Retrieving Parameters
Access parameters from other CDK stacks:
import * as ssm from 'aws-cdk-lib/aws-ssm';
// In a different stack
const clusterEndpoint = ssm.StringParameter.valueForStringParameter(
this,
'/cdk/Database/endpoint-hostname'
);
const secretArn = ssm.StringParameter.valueForStringParameter(
this,
'/cdk/Database/secret-arn'
);
// Use in Lambda environment variables
new lambda.Function(this, 'MyFunction', {
environment: {
DB_HOST: clusterEndpoint,
DB_SECRET_ARN: secretArn
}
});Using AWS CLI:
# Get cluster endpoint
aws ssm get-parameter --name "/cdk/Database/endpoint-hostname" --query "Parameter.Value" --output text
# Get all cluster parameters
aws ssm get-parameters-by-path --path "/cdk/Database" --recursive
# Get secret ARN for credentials retrieval
aws ssm get-parameter --name "/cdk/Database/secret-arn" --query "Parameter.Value" --output textParameter Tags
All SSM parameters are tagged for management:
ManagedBy:CDKConstructType:AuroraClusterConstructId: The construct's node ID
Performance Insights and Custom Parameters
const optimizedCluster = new AuroraCluster(this, 'OptimizedDB', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'optimized-cluster',
enablePerformanceInsights: true,
parameters: {
'max_connections': '200',
'shared_buffers': '256MB',
'effective_cache_size': '1GB'
}
});Development Environment (Reduced Costs)
import { Duration } from 'aws-cdk-lib';
const devCluster = new AuroraCluster(this, 'DevDatabase', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'dev',
username: 'admin',
clusterName: 'dev-cluster',
instanceCount: 1, // Single instance
instanceClass: 'BURSTABLE3', // Cost-effective
instanceSize: 'MEDIUM',
deletionProtection: false, // Allow easy teardown
backupRetention: Duration.days(7) // Shorter retention
});Extending the Construct
The AuroraCluster construct supports customization through protected hook methods. This allows you to override specific resource creation without modifying the base module.
Why Extend?
- Cross-client reuse: Use the same base module across multiple projects with client-specific customizations
- No module forking: Customize behavior without maintaining a fork
- Type-safe: Full TypeScript support for all hook methods
- Selective overrides: Override only what you need, keep defaults for the rest
Available Hook Methods
| Hook Method | Returns | Purpose |
|-------------|---------|---------|
| resolveVpc(props) | IVpc | Custom VPC resolution |
| createSecret(props) | ISecret | Custom secret management |
| createCredentials(props) | Credentials | Custom credential configuration |
| createEncryptionKey(props) | IKey | Custom KMS key (or use existing) |
| createSecurityGroup(vpc, port, props) | ISecurityGroup | Custom security rules |
| createParameterGroup(engine, props) | IParameterGroup | Custom database parameters |
| createClusterInstances(props) | ClusterInstanceConfig | Custom instance configuration |
| createCluster(config, props) | IDatabaseCluster | Full cluster customization |
| onClusterCreated(cluster) | void | Post-creation hooks (tags, alarms) |
| onConstructComplete() | void | Final customization |
Protected Properties
All intermediate resources are available as protected properties in your subclass:
| Property | Type | Description |
|----------|------|-------------|
| _props | AuroraClusterProps | Original constructor props |
| _vpc | IVpc | Resolved VPC |
| _kmsKey | IKey | Encryption key |
| _securityGroups | ISecurityGroup[] | Security groups |
| _parameterGroup | IParameterGroup | Parameter group |
| _credentials | Credentials | Database credentials |
| _engine | IClusterEngine | Database engine |
Example: Custom Security Group
import { AuroraCluster, AuroraClusterProps } from '@cdk-constructs/aurora-cluster';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
class MyAuroraCluster extends AuroraCluster {
protected createSecurityGroup(
vpc: ec2.IVpc,
port: number,
props: AuroraClusterProps
): ec2.ISecurityGroup {
const sg = new ec2.SecurityGroup(this, 'CustomSG', {
vpc,
description: 'Custom security group with specific CIDR rules'
});
// Allow from specific CIDR ranges
sg.addIngressRule(ec2.Peer.ipv4('10.0.0.0/8'), ec2.Port.tcp(port), 'Corporate network');
sg.addIngressRule(ec2.Peer.ipv4('172.16.0.0/12'), ec2.Port.tcp(port), 'VPN network');
return sg;
}
}Example: Use Existing KMS Key
import * as kms from 'aws-cdk-lib/aws-kms';
class MyAuroraCluster extends AuroraCluster {
protected createEncryptionKey(props: AuroraClusterProps): kms.IKey {
// Use an existing KMS key from your organization
return kms.Key.fromKeyArn(this, 'ExistingKey',
'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
);
}
}Example: Custom Parameter Group
import * as rds from 'aws-cdk-lib/aws-rds';
class MyAuroraCluster extends AuroraCluster {
protected createParameterGroup(
engine: rds.IClusterEngine,
props: AuroraClusterProps
): rds.IParameterGroup {
return new rds.ParameterGroup(this, 'CustomParams', {
engine,
parameters: {
'shared_preload_libraries': 'pg_stat_statements,auto_explain',
'log_min_duration_statement': '1000',
'auto_explain.log_min_duration': '1000',
...props.parameters
}
});
}
}Example: Post-Creation Tagging and Alarms
import { Tags } from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as rds from 'aws-cdk-lib/aws-rds';
class MyAuroraCluster extends AuroraCluster {
protected onClusterCreated(cluster: rds.IDatabaseCluster): void {
// Add organization-specific tags
Tags.of(cluster).add('CostCenter', 'engineering');
Tags.of(cluster).add('Environment', 'production');
Tags.of(cluster).add('Team', 'platform');
// Add CloudWatch alarm for CPU utilization
new cloudwatch.Alarm(this, 'CPUAlarm', {
metric: cluster.metricCPUUtilization(),
threshold: 80,
evaluationPeriods: 3,
alarmDescription: 'Database CPU utilization is high'
});
// Add alarm for freeable memory
new cloudwatch.Alarm(this, 'MemoryAlarm', {
metric: cluster.metricFreeableMemory(),
threshold: 100 * 1024 * 1024, // 100MB
evaluationPeriods: 3,
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
alarmDescription: 'Database freeable memory is low'
});
}
}Example: Complete Client Extension
import { AuroraCluster, AuroraClusterProps, ClusterInstanceConfig } from '@cdk-constructs/aurora-cluster';
import { Construct } from 'constructs';
import { Tags } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as rds from 'aws-cdk-lib/aws-rds';
/**
* ACME Corporation's customized Aurora cluster with:
* - Organization KMS key
* - Corporate network security rules
* - Standard tagging
* - Monitoring alarms
*/
class AcmeAuroraCluster extends AuroraCluster {
constructor(scope: Construct, id: string, props: AuroraClusterProps) {
super(scope, id, props);
}
// Use organization's shared KMS key
protected createEncryptionKey(props: AuroraClusterProps): kms.IKey {
return kms.Key.fromKeyArn(this, 'AcmeKey',
`arn:aws:kms:${process.env.AWS_REGION}:${process.env.AWS_ACCOUNT}:alias/acme-rds-key`
);
}
// Apply corporate security rules
protected createSecurityGroup(
vpc: ec2.IVpc,
port: number,
props: AuroraClusterProps
): ec2.ISecurityGroup {
const sg = new ec2.SecurityGroup(this, 'AcmeSG', {
vpc,
description: 'ACME standard database security group'
});
// Corporate networks only
sg.addIngressRule(ec2.Peer.ipv4('10.0.0.0/8'), ec2.Port.tcp(port));
sg.addIngressRule(ec2.Peer.ipv4('192.168.0.0/16'), ec2.Port.tcp(port));
return sg;
}
// Add standard ACME tags
protected onClusterCreated(cluster: rds.IDatabaseCluster): void {
Tags.of(cluster).add('Organization', 'ACME');
Tags.of(cluster).add('ManagedBy', 'Platform-Team');
Tags.of(cluster).add('CostAllocation', this._props.clusterName);
}
}
// Usage
const cluster = new AcmeAuroraCluster(this, 'Database', {
vpc: myVpc,
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3,
databaseName: 'myapp',
username: 'admin',
clusterName: 'acme-production-db'
});API Reference
AuroraClusterProps
| Property | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
| vpc | IVpc | Either vpc or vpcId | - | The VPC to deploy the cluster in |
| vpcId | string | Either vpc or vpcId | - | The VPC ID for lookup |
| engineVersion | AuroraPostgresEngineVersion \| AuroraMysqlEngineVersion | ✅ Yes | - | The Aurora engine version |
| databaseName | string | ✅ Yes | - | The name of the default database |
| username | string | ✅ Yes | - | The master username |
| clusterName | string | ✅ Yes | - | The cluster identifier (must be unique) |
| instanceCount | number | No | 1 | Total instances (1 writer + N-1 readers) |
| instanceClass | string | No | 'BURSTABLE3' | Instance class (e.g., 'MEMORY4', 'BURSTABLE3') |
| instanceSize | string | No | 'MEDIUM' | Instance size (e.g., 'SMALL', 'MEDIUM', 'LARGE') |
| snapshotId | string | No | - | Snapshot ID to restore from |
| parameterGroupName | string | No | Auto-created | Custom parameter group name |
| parameters | { [key: string]: string } | No | {} | Custom database parameters |
| securityGroups | ISecurityGroup[] | No | Auto-created | Custom security groups |
| deletionProtection | boolean | No | true | Enable deletion protection |
| cloudwatchLogsExports | string[] | No | [] | Log types to export to CloudWatch |
| cloudwatchLogsRetention | RetentionDays | No | ONE_WEEK | CloudWatch log retention period |
| enablePerformanceInsights | boolean | No | false | Enable Performance Insights |
| backupRetention | Duration | No | Duration.days(30) | Backup retention period |
| enableSsmParameters | boolean | No | true | Create SSM parameters for cluster outputs |
| ssmParameterPrefix | string | No | /cdk/${constructId} | Custom prefix for SSM parameter paths |
AuroraCluster Properties
| Property | Type | Description |
|----------|------|-------------|
| databaseCluster | IDatabaseCluster | The underlying RDS cluster instance |
| secret | ISecret | The Secrets Manager secret with credentials |
| clusterIdentifier | string | The cluster identifier |
| clusterEndpoint | Endpoint | The cluster endpoint (hostname and port) |
AuroraCluster Methods
grantConnect(role: IRole, databaseUser: string): void
Grants an IAM role permission to connect to the database as a specific user using IAM authentication.
Parameters:
role: The IAM role to grant access todatabaseUser: The database username that the role will connect as
Example:
const lambdaRole = iam.Role.fromRoleArn(this, 'Role', roleArn);
cluster.grantConnect(lambdaRole, 'app_user');Note: You must create the database user separately with the rds_iam role:
CREATE USER app_user;
GRANT rds_iam TO app_user;Engine Versions
PostgreSQL
import * as rds from 'aws-cdk-lib/aws-rds';
engineVersion: rds.AuroraPostgresEngineVersion.VER_15_3 // PostgreSQL 15.3
engineVersion: rds.AuroraPostgresEngineVersion.VER_14_6 // PostgreSQL 14.6
engineVersion: rds.AuroraPostgresEngineVersion.VER_13_9 // PostgreSQL 13.9MySQL
engineVersion: rds.AuroraMysqlEngineVersion.VER_3_03_0 // MySQL 8.0.28
engineVersion: rds.AuroraMysqlEngineVersion.VER_3_02_0 // MySQL 8.0.23
engineVersion: rds.AuroraMysqlEngineVersion.VER_2_11_0 // MySQL 5.7.40Instance Classes and Sizes
Available Classes
BURSTABLE3(db.t3) - Cost-effective for development/testingBURSTABLE4(db.t4g) - ARM-based, cost-effectiveMEMORY4(db.r4) - Memory-optimizedMEMORY5(db.r5) - Latest memory-optimizedMEMORY6G(db.r6g) - ARM-based memory-optimized
Available Sizes
SMALL,MEDIUM,LARGEXLARGE,XLARGE2,XLARGE4,XLARGE8, etc.
Example Combinations
// Development
instanceClass: 'BURSTABLE3', instanceSize: 'SMALL' // db.t3.small
// Production - Balanced
instanceClass: 'BURSTABLE3', instanceSize: 'LARGE' // db.t3.large
// Production - Memory-intensive
instanceClass: 'MEMORY5', instanceSize: 'XLARGE2' // db.r5.2xlargeTroubleshooting
Issue: VPC not found
Error: Cannot find VPC with ID: vpc-xxxxx
Solution: Ensure the VPC exists in the target region and account. Use ec2.Vpc.fromLookup() or pass the VPC object directly.
Issue: Insufficient subnet coverage
Error: DBSubnetGroup requires subnets in at least 2 availability zones
Solution: Ensure your VPC has private subnets in at least 2 availability zones. Aurora requires multi-AZ subnet groups.
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2, // Minimum for Aurora
natGateways: 1
});Issue: Deployment timeout
Problem: Stack deployment times out waiting for cluster creation.
Solution: Aurora cluster creation typically takes 10-15 minutes. Increase CloudFormation timeout or wait patiently. Check RDS console for actual cluster status.
Issue: IAM authentication not working
Error: Database user cannot authenticate with IAM
Solution:
- Ensure the database user exists and has
rds_iamrole:CREATE USER app_user; GRANT rds_iam TO app_user; - Verify the IAM role has the correct permissions (use
grantConnect()) - Use the correct connection method with IAM token generation
Issue: Parameter group errors
Error: Invalid parameter group family
Solution: Ensure custom parameters are compatible with your engine version. Check AWS documentation for valid parameters for your Aurora version.
Issue: Snapshot not found
Error: DBSnapshotNotFound: Snapshot not found
Solution: Verify the snapshot ID exists and is in the same region. Snapshot IDs use format: rds:cluster-name-YYYY-MM-DD-HH-MM.
Issue: SSM parameter conflicts
Error: Parameter /cdk/Database/cluster-identifier already exists
Solution:
- SSM parameter names must be unique. If redeploying, either:
- Use a different construct ID
- Use a different
ssmParameterPrefix - Set
enableSsmParameters: false
- Delete old parameters manually:
aws ssm delete-parameter --name "/cdk/Database/cluster-identifier" - Or delete all parameters for a prefix:
aws ssm delete-parameters --names $(aws ssm get-parameters-by-path --path "/cdk/Database" --query "Parameters[].Name" --output text)
Issue: Cannot retrieve SSM parameters from another stack
Error: ParameterNotFound: Parameter not found
Solution:
- Verify the parameter was created. Check CloudFormation outputs or AWS Console.
- Ensure the parameter name matches exactly (case-sensitive).
- Verify the IAM role/user has
ssm:GetParameterpermission. - Parameters are region-specific - ensure you're querying the correct region.
- If the cluster stack was deleted, parameters are deleted too.
Security Best Practices
- Encryption: Always enabled by default with KMS key rotation
- Secrets: Credentials stored in Secrets Manager, never hardcoded
- Network: Deploy in private subnets with controlled security groups
- Deletion Protection: Enabled by default for production
- Backups: 30-day retention by default
- IAM Authentication: Use instead of password-based auth when possible
- Least Privilege: Grant only necessary database permissions
Cost Optimization
- Instance Sizing: Start with
BURSTABLE3.SMALLand scale up as needed - Instance Count: Use single instance for dev, 3+ for production
- Backup Retention: Adjust based on compliance requirements
- Performance Insights: Enable only when actively debugging
- CloudWatch Logs: Export only necessary log types
- Deletion Protection: Disable for temporary/dev environments
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Related Resources
Support
For issues and questions:
- Open an issue on GitHub
- Check existing issues for solutions
- Review AWS Aurora troubleshooting guides
