in dotcom-rendering/cdk/lib/renderingStack.ts [176:292]
constructor(scope: CDKApp, id: string, props: RenderingCDKStackProps) {
super(scope, id, {
...props,
// Any version of this app should run in the eu-west-1 region
env: { region: 'eu-west-1' },
// Set the stack within the constructor as this won't vary between apps
stack: 'frontend',
});
const { stack: guStack, region, account } = this;
const { guApp, stage, instanceType, scaling, domainName } = props;
const artifactsBucket =
GuDistributionBucketParameter.getInstance(this).valueAsString;
const monitoringConfiguration =
stage === 'PROD'
? ({
snsTopicName: `Frontend-${stage}-CriticalAlerts`,
// TODO – how does this overlap with the DevX debug dashboard?
http5xxAlarm: {
tolerated5xxPercentage: 0.5, // Monitor and increase if too noisy
numberOfMinutesAboveThresholdBeforeAlarm: 1,
},
unhealthyInstancesAlarm: true,
} satisfies Alarms)
: ({ noMonitoring: true } satisfies NoMonitoring);
const ec2App = new GuEc2App(this, {
app: guApp,
access: {
// Restrict access to this range within the VPC
cidrRanges: [Peer.ipv4('10.0.0.0/8')],
scope: AccessScope.INTERNAL,
},
accessLogging: {
enabled: true,
prefix: `ELBLogs/${guStack}/${guApp}/${stage}`,
},
applicationLogging: {
enabled: true,
systemdUnitName: guApp,
},
// TODO - should we change to 3000?
applicationPort: 9000,
// Certificate is necessary for the creation of a listener on port 443,
// instead of the default 8080 which is unreachable.
certificateProps: { domainName },
healthcheck: { path: '/_healthcheck' },
instanceType,
monitoringConfiguration,
roleConfiguration: {
additionalPolicies: [
new GuAllowPolicy(this, 'AllowPolicyCloudwatchLogs', {
actions: ['cloudwatch:*', 'logs:*'],
resources: ['*'],
}),
new GuAllowPolicy(this, 'AllowPolicyDescribeDecryptKms', {
actions: ['kms:Decrypt', 'kms:DescribeKey'],
resources: [
`arn:aws:kms:${region}:${account}:FrontendConfigKey`,
],
}),
new GuAllowPolicy(this, 'AllowPolicyGetSsmParamsByPath', {
actions: [
'ssm:GetParametersByPath',
'ssm:GetParameter',
],
resources: [
// This is for backwards compatibility reasons with frontend apps and an old SSM naming system
// TODO - ideally we should convert these params to use the newer naming style for consistency
`arn:aws:ssm:${region}:${this.account}:parameter/frontend/*`,
`arn:aws:ssm:${region}:${this.account}:parameter/dotcom/*`,
],
}),
],
},
scaling,
userData: getUserData({
guApp,
guStack,
stage,
artifactsBucket,
}),
});
/**
* The default Node server keep alive timeout is 5 seconds
* @see https://nodejs.org/api/http.html#serverkeepalivetimeout
*
* This ensures that the load balancer idle timeout is less than the Node server keep alive timeout
* so that the Node app does not prematurely close the connection before the load balancer can accept the response.
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#connection-idle-timeout
*/
ec2App.loadBalancer.setAttribute('idle_timeout.timeout_seconds', '4');
// Maps the certificate domain name to the load balancer DNS name
new GuCname(this, 'LoadBalancerDNS', {
domainName,
app: guApp,
resourceRecord: ec2App.loadBalancer.loadBalancerDnsName,
ttl: Duration.hours(1),
});
/** Add CPU utilisation based STEP scaling policy for PROD only if a policy is defined */
addCPUStepScalingPolicy(this, ec2App, props, stage);
/** Add latency-based STEP scaling policy for PROD only if a policy is defined */
addLatencyStepScalingPolicy(this, ec2App, props, stage);
// Saves the value of the rendering base URL to SSM for frontend apps to use
new StringParameter(this, 'RenderingBaseURLParam', {
parameterName: `/${guStack}/${stage.toLowerCase()}/${guApp}.baseURL`,
stringValue: `https://${domainName}`,
description: `The rendering base URL for frontend to call the ${guApp} app in the ${stage} environment`,
});
}