in trivia-backend/infra/codedeploy-blue-green/infra-setup.ts [17:157]
constructor(parent: cdk.App, name: string, props: TriviaBackendStackProps) {
super(parent, name, props);
// Network infrastructure
const vpc = new Vpc(this, 'VPC', { maxAzs: 2 });
const serviceSG = new SecurityGroup(this, 'ServiceSecurityGroup', { vpc });
// Lookup pre-existing TLS certificate
const certificateArn = StringParameter.fromStringParameterAttributes(this, 'CertArnParameter', {
parameterName: 'CertificateArn-' + props.domainName
}).stringValue;
// Load balancer
const loadBalancer = new ApplicationLoadBalancer(this, 'ServiceLB', {
vpc,
internetFacing: true
});
serviceSG.connections.allowFrom(loadBalancer, Port.tcp(80));
const domainZone = HostedZone.fromLookup(this, 'Zone', { domainName: props.domainZone });
new ARecord(this, "DNS", {
zone: domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
// Primary traffic listener
const listener = loadBalancer.addListener('PublicListener', {
port: 443,
open: true,
certificateArns: [certificateArn]
});
// Second listener for test traffic
let testListener = loadBalancer.addListener('TestListener', {
port: 9002, // port for testing
protocol: ApplicationProtocol.HTTPS,
open: true,
certificateArns: [certificateArn]
});
// First target group for blue fleet
const tg1 = listener.addTargets('ECS', {
port: 80,
targets: [ // empty to begin with
new (class EmptyIpTarget implements IApplicationLoadBalancerTarget {
attachToApplicationTargetGroup(_: ApplicationTargetGroup): LoadBalancerTargetProps {
return { targetType: TargetType.IP };
}
})()
],
deregistrationDelay: cdk.Duration.seconds(30),
healthCheck: {
interval: cdk.Duration.seconds(5),
healthyHttpCodes: '200',
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
timeout: cdk.Duration.seconds(4)
}
});
// Second target group for green fleet
const tg2 = testListener.addTargets('ECS2', {
port: 80,
targets: [ // empty to begin with
new (class EmptyIpTarget implements IApplicationLoadBalancerTarget {
attachToApplicationTargetGroup(_: ApplicationTargetGroup): LoadBalancerTargetProps {
return { targetType: TargetType.IP };
}
})()
],
deregistrationDelay: cdk.Duration.seconds(30),
healthCheck: {
interval: cdk.Duration.seconds(5),
healthyHttpCodes: '200',
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
timeout: cdk.Duration.seconds(4)
}
});
// Alarms: monitor 500s and unhealthy hosts on target groups
const tg1UnhealthyHosts = new Alarm(this, 'TargetGroupUnhealthyHosts', {
alarmName: this.stackName + '-Unhealthy-Hosts-Blue',
metric: tg1.metricUnhealthyHostCount(),
threshold: 1,
evaluationPeriods: 2,
});
const tg1ApiFailure = new Alarm(this, 'TargetGroup5xx', {
alarmName: this.stackName + '-Http-500-Blue',
metric: tg1.metricHttpCodeTarget(HttpCodeTarget.TARGET_5XX_COUNT),
threshold: 1,
evaluationPeriods: 1,
period: cdk.Duration.minutes(1)
});
const tg2UnhealthyHosts = new Alarm(this, 'TargetGroup2UnhealthyHosts', {
alarmName: this.stackName + '-Unhealthy-Hosts-Green',
metric: tg2.metricUnhealthyHostCount(),
threshold: 1,
evaluationPeriods: 2,
});
const tg2ApiFailure = new Alarm(this, 'TargetGroup25xx', {
alarmName: this.stackName + '-Http-500-Green',
metric: tg2.metricHttpCodeTarget(HttpCodeTarget.TARGET_5XX_COUNT),
threshold: 1,
evaluationPeriods: 1,
period: cdk.Duration.minutes(1)
});
new CompositeAlarm(this, 'CompositeUnhealthyHosts', {
compositeAlarmName: this.stackName + '-Unhealthy-Hosts',
alarmRule: AlarmRule.anyOf(
AlarmRule.fromAlarm(tg1UnhealthyHosts, AlarmState.ALARM),
AlarmRule.fromAlarm(tg2UnhealthyHosts, AlarmState.ALARM))
});
new CompositeAlarm(this, 'Composite5xx', {
compositeAlarmName: this.stackName + '-Http-500',
alarmRule: AlarmRule.anyOf(
AlarmRule.fromAlarm(tg1ApiFailure, AlarmState.ALARM),
AlarmRule.fromAlarm(tg2ApiFailure, AlarmState.ALARM))
});
// Roles
new Role(this, 'ServiceTaskDefExecutionRole', {
assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
managedPolicies: [ ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy') ]
});
new Role(this, 'ServiceTaskDefTaskRole', {
assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
});
new Role(this, 'CodeDeployRole', {
assumedBy: new ServicePrincipal('codedeploy.amazonaws.com'),
managedPolicies: [ ManagedPolicy.fromAwsManagedPolicyName('AWSCodeDeployRoleForECS') ]
});
}