Fargate with Load Balancer
Quickly set up an ALB usinc AWS CDK.
const { service, targetGroup } =
new ecsPatterns.ApplicationLoadBalancedFargateService(
this,
"CoolAppLoadBalancer",
{
serviceName: `CoolApp${capitalize(props.environment)}`,
cpu: 1024,
taskDefinition,
securityGroups: [this.securityGroup],
desiredCount: props.instanceCount || 1,
publicLoadBalancer: true,
cluster,
deploymentController: { type: ecs.DeploymentControllerType.ECS },
certificate,
domainName,
domainZone: hostedZone,
redirectHTTP: true,
loadBalancerName: `CoolAppALB${capitalize(props.environment)}`,
}
);
new cdk.CfnOutput(this, "service", { value: service.serviceName });
targetGroup.setAttribute("deregistration_delay.timeout_seconds", "5");
targetGroup.configureHealthCheck({
interval: Duration.seconds(10),
timeout: Duration.seconds(5),
healthyThresholdCount: 5,
});
new cdk.CfnOutput(this, "targetgrouparn", {
value: targetGroup.targetGroupArn,
});
Usage
In order to set up the ALB, you are going to need references to the following resources:
- Environment in use
- Task definition (or at least an image)
- Security group
- Certificate
- Domain name
- Domain zone
1
Get references to domain & certificate
const props = {
appName: "cool-app",
domain: "awesomedomain.com",
};
const domainName = `${props.appName}.${props.domain}`;
const hostedZone = route53.HostedZone.fromLookup(this, "DnsZone", {
domainName: props.domain,
});
const certificate = new acm.Certificate(this, `CoolAppCert`, {
domainName,
validation: acm.CertificateValidation.fromDns(hostedZone),
});
2
Retrieve references to the VPC, Security group & Cluster
const vpc = ec2.Vpc.fromLookup(this, "Vpc", { vpcId: props.vpcId });
this.securityGroup = new ec2.SecurityGroup(this, "CoolAppSecurityGroup", {
vpc,
securityGroupName: `CoolApp${capitalize(props.environment)}SecurityGroup`,
});
// Manipulate the securitygroup to suit your application here if needed
const cluster = ecs.Cluster.fromClusterAttributes(this, "ECSCluster", {
vpc,
securityGroups: [this.securityGroup],
clusterName: props.environment,
});
// This outputs the cluster name to the configured output file (output.json)
new cdk.CfnOutput(this, "cluster", { value: cluster.clusterName });
3
Fetch secrets (database)
// Use the environment prop to fetch the right credentials for your environment
const verySecretSecrets = secretsmanager.Secret.fromSecretNameV2(
this,
`SecretSecrets`,
`secret-database-${props.environment}`
);
// (Optional): Ouput the secret ARN for use by CI
new cdk.CfnOutput(this, "secretarn", {
value: postgresSecrets.secretArn,
});
// This utility enables easy fetching of secrets by key
const getSecret = (key: string) => ecs.Secret.fromSecretsManager(postgresSecrets, key) // prettier-ignore
4
Create Log group & Task definition
// The App will use this log group when storing its messages.
// You can set the amount of time you want the logs to be stored,
// as well as what name they should be showing up under in CloudWatch
const logGroup = new logs.LogGroup(this, "CoolAppLogGroup", {
logGroupName: `cool-app-${props.environment}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logs.RetentionDays.FIVE_DAYS,
});
// Create a task definition and assign a famility to it.
// This makes it easier to differentiate the created resources in the console.
const taskDefinition = new ecs.FargateTaskDefinition(this,'CoolAppTask',{family: 'weather-api'}) // prettier-ignore
taskDefinition.addContainer("CoolAppContainer", {
image: ecs.ContainerImage.fromEcrRepository(props.ecrRepo, "latest"),
logging: ecs.LogDriver.awsLogs({
logGroup,
streamPrefix: "/ecs/CoolApp",
}),
secrets: {
DB_USER: getSecret("username"),
DB_PASSWORD: getSecret("password"),
DB_HOST: getSecret("host"),
DB_PORT: getSecret("port"),
},
portMappings: [
{
containerPort: 3000,
protocol: ecs.Protocol.TCP,
},
],
});
5
Create ALB using ecs-patterns
const { service, targetGroup } =
new ecsPatterns.ApplicationLoadBalancedFargateService(
this,
"CoolAppLoadBalancer",
{
serviceName: `CoolApp${capitalize(props.environment)}`,
cpu: 1024,
taskDefinition,
securityGroups: [this.securityGroup],
desiredCount: props.instanceCount || 1,
publicLoadBalancer: true,
cluster,
deploymentController: { type: ecs.DeploymentControllerType.ECS },
certificate,
domainName,
domainZone: hostedZone,
redirectHTTP: true,
loadBalancerName: `CoolAppALB${capitalize(props.environment)}`,
}
);
// Write service name to output for use in CI (to force redeployment for example)
new cdk.CfnOutput(this, "service", { value: service.serviceName });
// (Optional) Reduce the default deregistration delay from 300 seconds to a mere 5.
// This makes the deployment process quicker, since the time that the container
// waits before going into a DRAINING state is greatly reduced.
targetGroup.setAttribute("deregistration_delay.timeout_seconds", "5");
// (Optional) Reduce the interval, timeout and successful
//attempts needed to perform health checks
targetGroup.configureHealthCheck({
interval: Duration.seconds(10),
timeout: Duration.seconds(5),
healthyThresholdCount: 5,
});
// Write target-group to output in order for the CI to
//be able to listen for successful deployments
new cdk.CfnOutput(this, "targetgrouparn", {
value: targetGroup.targetGroupArn,
});
Final file
stacks/cool-app-stack.ts
import * as cdk from '@aws-cdk/core'
import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns'
import * as secretsmanager from '@aws-cdk/aws-secretsmanager'
import * as ecs from '@aws-cdk/aws-ecs'
import * as ecr from '@aws-cdk/aws-ecr'
import * as acm from '@aws-cdk/aws-certificatemanager'
import * as route53 from '@aws-cdk/aws-route53'
import * as ec2 from '@aws-cdk/aws-ec2'
import * as logs from '@aws-cdk/aws-logs'
import { Duration } from '@aws-cdk/core'
export interface CoolAppStackProps extends cdk.StackProps {
domain: string
/** Used as part of the domain ({appname}.{domainName}) */
appName: string
environment: 'development' | 'staging' | 'production'
ecrRepo: ecr.Repository
vpcId: string
/** Maximum amount of instances created by the ALB. @default 1 */
instanceCount?: number
}
export class CoolAppStack extends cdk.Stack {
securityGroup: ec2.SecurityGroup
constructor(scope: cdk.Construct, id: string, props: CoolAppStackProps) {
super(scope, id, props)
const domainName = `${props.appName}.${props.domain}`
const hostedZone = route53.HostedZone.fromLookup(this, 'DnsZone', {
domainName: props.domain
})
const certificate = new acm.Certificate(this, `CoolAppCert`, {
domainName,
validation: acm.CertificateValidation.fromDns(hostedZone)
})
const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId })
this.securityGroup = new ec2.SecurityGroup(this, 'CoolAppSecurityGroup', {
vpc,
securityGroupName: `CoolApp${capitalize(props.environment)}SecurityGroup`
})
// Manipulate the securitygroup to suit your application here if needed
const cluster = ecs.Cluster.fromClusterAttributes(this, 'ECSCluster', {
vpc,
securityGroups: [this.securityGroup],
clusterName: props.environment
})
// This outputs the cluster name to the configured output file (output.json)
new cdk.CfnOutput(this, 'cluster', { value: cluster.clusterName })
// Use the environment prop to fetch the right credentials for your environment
const verySecretSecrets = secretsmanager.Secret.fromSecretNameV2(
this,
`SecretSecrets`,
`secret-database-${props.environment}`
)
// (Optional): Ouput the secret ARN for use by CI
new cdk.CfnOutput(this, 'secretarn', {
value: postgresSecrets.secretArn
})
// This utility enables easy fetching of secrets by key
const getSecret = (key: string) => ecs.Secret.fromSecretsManager(postgresSecrets, key) // prettier-ignore
// The App will use this log group when storing its messages.
// You can set the amount of time you want the logs to be stored,
// as well as what name they should be showing up under in CloudWatch
const logGroup = new logs.LogGroup(this, 'CoolAppLogGroup', {
logGroupName: `cool-app-${props.environment}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logs.RetentionDays.FIVE_DAYS
})
// Create a task definition and assign a famility to it.
// This makes it easier to differentiate the created resources in the console.
const taskDefinition = new ecs.FargateTaskDefinition(this,'CoolAppTask',{family: 'weather-api'}) // prettier-ignore
taskDefinition.addContainer('CoolAppContainer', {
image: ecs.ContainerImage.fromEcrRepository(props.ecrRepo, 'latest'),
logging: ecs.LogDriver.awsLogs({
logGroup,
streamPrefix: '/ecs/CoolApp'
}),
secrets: {
DB_USER: getSecret('username'),
DB_PASSWORD: getSecret('password'),
DB_HOST: getSecret('host'),
DB_PORT: getSecret('port')
},
portMappings: [
{
containerPort: 3000,
protocol: ecs.Protocol.TCP
}
]
})
const { service, targetGroup } = new ecsPatterns.ApplicationLoadBalancedFargateService(
this,
'CoolAppLoadBalancer',
{
serviceName: `CoolApp${capitalize(props.environment)}`,
cpu: 1024,
taskDefinition,
securityGroups: [this.securityGroup],
desiredCount: props.instanceCount || 1,
publicLoadBalancer: true,
cluster,
deploymentController: { type: ecs.DeploymentControllerType.ECS },
certificate,
domainName,
domainZone: hostedZone,
redirectHTTP: true,
loadBalancerName: `CoolAppALB${capitalize(props.environment)}`
}
)
// Write service name to output for use in CI (to force redeployment for example)
new cdk.CfnOutput(this, 'service', { value: service.serviceName })
// (Optional) Reduce the default deregistration delay from 300 seconds to a mere 5.
// This makes the deployment process quicker, since the time that the container
// waits before going into a DRAINING state is greatly reduced.
targetGroup.setAttribute('deregistration_delay.timeout_seconds', '5')
// (Optional) Reduce the interval, timeout and successful
//attempts needed to perform health checks
targetGroup.configureHealthCheck({
interval: Duration.seconds(10),
timeout: Duration.seconds(5),
healthyThresholdCount: 5
})
// Write target-group to output in order for the CI to
//be able to listen for successful deployments
new cdk.CfnOutput(this, 'targetgrouparn', {
value: targetGroup.targetGroupArn
})
}
}
Fore completeness, here's a cdk-app
file to tie it together and make it deployable.
cdk-app.ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from '@aws-cdk/core'
import { CoolAppStack } from './stacks/cool-app-stack'
import { EcrRepoStack } from './stacks/repo-stack'
new CoolAppStack(app, 'CoolAppStackDevelopment', {
appName: 'cool-app',
domain: 'coolcompany.com',
env: {
region: 'eu-west-1',
account: '12345678987',
},
vpcId: 'vpc-12ab34cd',
environment: 'development',
ecrRepo: ecrCoolAppRepo.repo,
tags: {
application: 'cool-app',
realm: 'snippet-showoffs',
environment: 'development',
}
})