Skip to content

Commit 979f6fd

Browse files
hoegertnElad Ben-Israel
authored andcommitted
feat(ecs): make cluster and vpc optional for higher level constructs (#2773)
* feat(ecs): make cluster and vpc optional for higher level constructs * chore(ecs): review feedback * chore(ecs): improve commenst after review * fix(ecs-patterns): refactor tests after merge * fix(ecs-patterns): whitespace * fix(ecs-patterns): memory in MB * fix(ecs): merge conflicts * feat(ecs-patterns): use default cluster per stack * fix(ecs-patterns): tests * feat(ecs-patterns): add magic id * chore: PR review * chore: add README
1 parent bc233fa commit 979f6fd

13 files changed

+3056
-12
lines changed

packages/@aws-cdk/aws-ecs-patterns/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ const loadBalancedFargateService = new ecsPatterns.LoadBalancedFargateService(st
5151
});
5252
```
5353

54+
Instead of providing a cluster you can specify a VPC and CDK will create a new ECS cluster.
55+
If you deploy multiple services CDK will only create on cluster per VPC.
56+
57+
You can omit `cluster` and `vpc` to let CDK create a new VPC with two AZs and create a cluster inside this VPC.
58+
5459
## Queue Processing Services
5560

5661
To define a service that creates a queue and reads from that queue, instantiate one of the following:

packages/@aws-cdk/aws-ecs-patterns/lib/base/load-balanced-service-base.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ICertificate } from '@aws-cdk/aws-certificatemanager';
2+
import ec2 = require('@aws-cdk/aws-ec2');
23
import ecs = require('@aws-cdk/aws-ecs');
34
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
45
import { AddressRecordTarget, ARecord, IHostedZone } from '@aws-cdk/aws-route53';
@@ -16,8 +17,19 @@ export enum LoadBalancerType {
1617
export interface LoadBalancedServiceBaseProps {
1718
/**
1819
* The cluster where your service will be deployed
20+
* You can only specify either vpc or cluster. Alternatively, you can leave both blank
21+
*
22+
* @default - create a new cluster; if you do not specify a cluster nor a vpc, a new VPC will be created for you as well
23+
*/
24+
readonly cluster?: ecs.ICluster;
25+
26+
/**
27+
* VPC that the cluster instances or tasks are running in
28+
* You can only specify either vpc or cluster. Alternatively, you can leave both blank
29+
*
30+
* @default - use vpc of cluster or create a new one
1931
*/
20-
readonly cluster: ecs.ICluster;
32+
readonly vpc?: ec2.IVpc;
2133

2234
/**
2335
* The image to start.
@@ -115,11 +127,18 @@ export abstract class LoadBalancedServiceBase extends cdk.Construct {
115127

116128
public readonly targetGroup: elbv2.ApplicationTargetGroup | elbv2.NetworkTargetGroup;
117129

130+
public readonly cluster: ecs.ICluster;
131+
118132
public readonly logDriver?: ecs.LogDriver;
119133

120134
constructor(scope: cdk.Construct, id: string, props: LoadBalancedServiceBaseProps) {
121135
super(scope, id);
122136

137+
if (props.cluster && props.vpc) {
138+
throw new Error(`You can only specify either vpc or cluster. Alternatively, you can leave both blank`);
139+
}
140+
this.cluster = props.cluster || this.getDefaultCluster(this, props.vpc);
141+
123142
// Create log driver if logging is enabled
124143
const enableLogging = props.enableLogging !== undefined ? props.enableLogging : true;
125144
this.logDriver = enableLogging ? this.createAWSLogDriver(this.node.id) : undefined;
@@ -134,7 +153,7 @@ export abstract class LoadBalancedServiceBase extends cdk.Construct {
134153
const internetFacing = props.publicLoadBalancer !== undefined ? props.publicLoadBalancer : true;
135154

136155
const lbProps = {
137-
vpc: props.cluster.vpc,
156+
vpc: this.cluster.vpc,
138157
internetFacing
139158
};
140159

@@ -183,6 +202,13 @@ export abstract class LoadBalancedServiceBase extends cdk.Construct {
183202
new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName });
184203
}
185204

205+
protected getDefaultCluster(scope: cdk.Construct, vpc?: ec2.IVpc): ecs.Cluster {
206+
// magic string to avoid collision with user-defined constructs
207+
const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`;
208+
const stack = cdk.Stack.of(scope);
209+
return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as ecs.Cluster || new ecs.Cluster(stack, DEFAULT_CLUSTER_ID, { vpc });
210+
}
211+
186212
protected addServiceAsTarget(service: ecs.BaseService) {
187213
if (this.loadBalancerType === LoadBalancerType.APPLICATION) {
188214
(this.targetGroup as elbv2.ApplicationTargetGroup).addTarget(service);

packages/@aws-cdk/aws-ecs-patterns/lib/ecs/load-balanced-ecs-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class LoadBalancedEc2Service extends LoadBalancedServiceBase {
6363

6464
const assignPublicIp = props.publicTasks !== undefined ? props.publicTasks : false;
6565
const service = new ecs.Ec2Service(this, "Service", {
66-
cluster: props.cluster,
66+
cluster: this.cluster,
6767
desiredCount: props.desiredCount || 1,
6868
taskDefinition,
6969
assignPublicIp

packages/@aws-cdk/aws-ecs-patterns/lib/fargate/load-balanced-fargate-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export class LoadBalancedFargateService extends LoadBalancedServiceBase {
107107
});
108108
const assignPublicIp = props.publicTasks !== undefined ? props.publicTasks : false;
109109
const service = new ecs.FargateService(this, "Service", {
110-
cluster: props.cluster,
110+
cluster: this.cluster,
111111
desiredCount: props.desiredCount || 1,
112112
taskDefinition,
113113
assignPublicIp,

packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,48 @@ export = {
5656
test.done();
5757
},
5858

59+
'set vpc instead of cluster'(test: Test) {
60+
// GIVEN
61+
const stack = new cdk.Stack();
62+
const vpc = new ec2.Vpc(stack, 'VPC');
63+
64+
// WHEN
65+
new ecsPatterns.LoadBalancedEc2Service(stack, 'Service', {
66+
vpc,
67+
memoryLimitMiB: 1024,
68+
image: ecs.ContainerImage.fromRegistry('test'),
69+
desiredCount: 2,
70+
environment: {
71+
TEST_ENVIRONMENT_VARIABLE1: "test environment variable 1 value",
72+
TEST_ENVIRONMENT_VARIABLE2: "test environment variable 2 value"
73+
}
74+
});
75+
76+
// THEN - stack does not contain a LaunchConfiguration
77+
expect(stack, true).notTo(haveResource("AWS::AutoScaling::LaunchConfiguration"));
78+
79+
test.throws(() => expect(stack));
80+
81+
test.done();
82+
},
83+
84+
'setting vpc and cluster throws error'(test: Test) {
85+
// GIVEN
86+
const stack = new cdk.Stack();
87+
const vpc = new ec2.Vpc(stack, 'VPC');
88+
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });
89+
90+
// WHEN
91+
test.throws(() => new ecsPatterns.LoadBalancedEc2Service(stack, 'Service', {
92+
cluster,
93+
vpc,
94+
loadBalancerType: ecsPatterns.LoadBalancerType.NETWORK,
95+
image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app")
96+
}));
97+
98+
test.done();
99+
},
100+
59101
'test ECS loadbalanced construct with memoryReservationMiB'(test: Test) {
60102
// GIVEN
61103
const stack = new cdk.Stack();

0 commit comments

Comments
 (0)