Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecs-patterns): add option to create cname instead of alias record #10812

Merged
merged 10 commits into from
Nov 6, 2020
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Fargate services use the default VPC Security Group unless one or more are provi

By setting `redirectHTTP` to true, CDK will automatically create a listener on port 80 that redirects HTTP traffic to the HTTPS port.

If you specify the option `recordType` you can decide if you want the construct to use CNAME or Route53-Aliases as record sets.

Additionally, if more than one application target group are needed, instantiate one of the following:

* `ApplicationMultipleTargetGroupsEc2Service`
Expand Down Expand Up @@ -153,6 +155,8 @@ The CDK will create a new Amazon ECS cluster if you specify a VPC and omit `clus

If `cluster` and `vpc` are omitted, the CDK creates a new VPC with subnets in two Availability Zones and a cluster within this VPC.

If you specify the option `recordType` you can decide if you want the construct to use CNAME or Route53-Aliases as record sets.

Additionally, if more than one network target group is needed, instantiate one of the following:

* NetworkMultipleTargetGroupsEc2Service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,29 @@ import {
IApplicationLoadBalancer, ListenerCertificate, ListenerAction,
} from '@aws-cdk/aws-elasticloadbalancingv2';
import { IRole } from '@aws-cdk/aws-iam';
import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { ARecord, IHostedZone, RecordTarget, CnameRecord } from '@aws-cdk/aws-route53';
import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';

/**
* Describes the type of DNS record the service should create
*/
export enum ApplicationLoadBalancedServiceRecordType {
/**
* Create Route53 A Alias record
*/
ALIAS,
/**
* Create a CNAME record
*/
CNAME,
/**
* Do not create any DNS records
*/
NONE
}

/**
* The properties for the base ApplicationLoadBalancedEc2Service or ApplicationLoadBalancedFargateService service.
*/
Expand Down Expand Up @@ -177,6 +195,14 @@ export interface ApplicationLoadBalancedServiceBaseProps {
* @default false
*/
readonly redirectHTTP?: boolean;

/**
* Specifies whether the Route53 record should be a CNAME, an A record using the Alias feature or no record at all.
* This is useful if you need to work with DNS systems that do not support alias records.
*
* @default ApplicationLoadBalancedServiceRecordType.ALIAS
*/
readonly recordType?: ApplicationLoadBalancedServiceRecordType;
}

export interface ApplicationLoadBalancedTaskImageOptions {
Expand Down Expand Up @@ -390,13 +416,27 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name');
}

const record = new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});

domainName = record.domainName;
switch (props.recordType ?? ApplicationLoadBalancedServiceRecordType.ALIAS) {
case ApplicationLoadBalancedServiceRecordType.ALIAS:
let aliasRecord = new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
domainName = aliasRecord.domainName;
break;
case ApplicationLoadBalancedServiceRecordType.CNAME:
let cnameRecord = new CnameRecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
domainName: loadBalancer.loadBalancerDnsName,
});
domainName = cnameRecord.domainName;
break;
case ApplicationLoadBalancedServiceRecordType.NONE:
// Do not create a DNS record
break;
}
}

if (loadBalancer instanceof ApplicationLoadBalancer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,29 @@ import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
import { INetworkLoadBalancer, NetworkListener, NetworkLoadBalancer, NetworkTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2';
import { IRole } from '@aws-cdk/aws-iam';
import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { ARecord, CnameRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';

/**
* Describes the type of DNS record the service should create
*/
export enum NetworkLoadBalancedServiceRecordType {
/**
* Create Route53 A Alias record
*/
ALIAS,
/**
* Create a CNAME record
*/
CNAME,
/**
* Do not create any DNS records
*/
NONE
}

/**
* The properties for the base NetworkLoadBalancedEc2Service or NetworkLoadBalancedFargateService service.
*/
Expand Down Expand Up @@ -136,6 +154,14 @@ export interface NetworkLoadBalancedServiceBaseProps {
* @default - AWS Cloud Map service discovery is not enabled.
*/
readonly cloudMapOptions?: CloudMapOptions;

/**
* Specifies whether the Route53 record should be a CNAME, an A record using the Alias feature or no record at all.
* This is useful if you need to work with DNS systems that do not support alias records.
*
* @default NetworkLoadBalancedServiceRecordType.ALIAS
*/
readonly recordType?: NetworkLoadBalancedServiceRecordType;
}

export interface NetworkLoadBalancedTaskImageOptions {
Expand Down Expand Up @@ -294,11 +320,25 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct {
throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name');
}

new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
switch (props.recordType ?? NetworkLoadBalancedServiceRecordType.ALIAS) {
case NetworkLoadBalancedServiceRecordType.ALIAS:
new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
break;
case NetworkLoadBalancedServiceRecordType.CNAME:
new CnameRecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
domainName: loadBalancer.loadBalancerDnsName,
});
break;
case NetworkLoadBalancedServiceRecordType.NONE:
// Do not create a DNS record
break;
}
}

if (loadBalancer instanceof NetworkLoadBalancer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,123 @@ export = {
test.done();
},

'setting ALB cname option correctly sets the recordset type'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', {
cluster,
protocol: ApplicationProtocol.HTTPS,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.CNAME,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::Route53::RecordSet', {
Name: 'test.domain.com.',
Type: 'CNAME',
}));

test.done();
},

'setting ALB record type to NONE correctly omits the recordset'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', {
cluster,
protocol: ApplicationProtocol.HTTPS,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.NONE,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).notTo(haveResource('AWS::Route53::RecordSet'));

test.done();
},


'setting NLB cname option correctly sets the recordset type'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', {
cluster,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.CNAME,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::Route53::RecordSet', {
Name: 'test.domain.com.',
Type: 'CNAME',
}));

test.done();
},

'setting NLB record type to NONE correctly omits the recordset'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', {
cluster,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.NONE,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).notTo(haveResource('AWS::Route53::RecordSet'));

test.done();
},

'setting ALB HTTP protocol to create the listener on 80'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down