Skip to content

Instantly share code, notes, and snippets.

@billykong
Created May 28, 2020 12:53
Show Gist options
  • Save billykong/a778613eb0ecf1ab59ff82b76bbf985b to your computer and use it in GitHub Desktop.
Save billykong/a778613eb0ecf1ab59ff82b76bbf985b to your computer and use it in GitHub Desktop.
aws-cdk stack for a load-balanced ECS Fargate service with RDS
import { Vpc, Subnet, SubnetType, SecurityGroup, Peer, Port } from '@aws-cdk/aws-ec2';
import ecs = require('@aws-cdk/aws-ecs');
import ecs_patterns = require('@aws-cdk/aws-ecs-patterns');
import { CfnDBCluster, CfnDBSubnetGroup } from '@aws-cdk/aws-rds';
import secretsManager = require('@aws-cdk/aws-secretsmanager');
import ssm = require('@aws-cdk/aws-ssm');
import * as cdk from '@aws-cdk/core';
export class AwsCdkFargateRdsStackStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const serviceName = 'my-service';
const databaseName = 'my_database'
const databaseUsername = 'deployer'
const stage = 'dev';
const vpc = new Vpc(this, 'MyVPC', {
cidr: '10.0.0.0/16',
subnetConfiguration: [
{ name: 'elb_public_', subnetType: SubnetType.PUBLIC },
{ name: 'ecs_private_', subnetType: SubnetType.PRIVATE },
{ name: 'aurora_isolated_', subnetType: SubnetType.ISOLATED }
]
});
const subnetIds: string[] = [];
vpc.isolatedSubnets.forEach((subnet, index) => {
subnetIds.push(subnet.subnetId);
});
const dbSubnetGroup: CfnDBSubnetGroup = new CfnDBSubnetGroup(this, 'AuroraSubnetGroup', {
dbSubnetGroupDescription: 'Subnet group to access aurora',
dbSubnetGroupName: 'aurora-serverless-subnet-group',
subnetIds
});
const databaseCredentialsSecret = new secretsManager.Secret(this, 'DBCredentialsSecret', {
secretName: `${serviceName}-${stage}-credentials`,
generateSecretString: {
secretStringTemplate: JSON.stringify({
username: databaseUsername,
}),
excludePunctuation: true,
includeSpace: false,
generateStringKey: 'password'
}
});
new ssm.StringParameter(this, 'DBCredentialsArn', {
parameterName: `${serviceName}-${stage}-credentials-arn`,
stringValue: databaseCredentialsSecret.secretArn,
});
const dbClusterSecurityGroup = new SecurityGroup(this, 'DBClusterSecurityGroup', { vpc });
// A better security approach would be allow ingress from private subnet only
// but I haven't been able to get the ipv4 cidr block of subnets in aws-cwk
dbClusterSecurityGroup.addIngressRule(Peer.ipv4('10.0.0.0/16'), Port.tcp(5432));
const dbConfig = {
dbClusterIdentifier: `${serviceName}-${stage}-cluster`,
engineMode: 'serverless',
engine: 'aurora-postgresql',
engineVersion: '10.7',
databaseName: databaseName,
masterUsername: databaseCredentialsSecret.secretValueFromJson('username').toString(),
masterUserPassword: databaseCredentialsSecret.secretValueFromJson('password').toString(),
// Note: aurora serverless cluster can be accessed within its VPC only
// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html
dbSubnetGroupName: dbSubnetGroup.dbSubnetGroupName,
scalingConfiguration: {
autoPause: true,
maxCapacity: 2,
minCapacity: 2,
secondsUntilAutoPause: 3600,
},
vpcSecurityGroupIds: [
dbClusterSecurityGroup.securityGroupId
]
};
const rdsCluster = new CfnDBCluster(this, 'DBCluster', dbConfig);
rdsCluster.addDependsOn(dbSubnetGroup)
const cluster = new ecs.Cluster(this, 'Cluster', { vpc });
const loadBalancedService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, "FargateService", {
cluster,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("billykong/express-database-checker"),
environment: {
DATABASE_HOST: rdsCluster.attrEndpointAddress,
DATABASE_NAME: databaseName,
// TODO: use secret instead of environment
DATABASE_USERNAME: databaseCredentialsSecret.secretValueFromJson('username').toString(),
DATABASE_PASSWORD: databaseCredentialsSecret.secretValueFromJson('password').toString(),
}
},
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment