Created
May 28, 2020 12:53
-
-
Save billykong/a778613eb0ecf1ab59ff82b76bbf985b to your computer and use it in GitHub Desktop.
aws-cdk stack for a load-balanced ECS Fargate service with RDS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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