Skip to content

Commit 22fc8b9

Browse files
authoredFeb 8, 2019
feat(aws-eks): add construct library for EKS (#1655)
Construct library to set up an EKS cluster and add nodes to it. Generalizes adding AutoScalingGroup capacity and make it the same between both ECS and EKS clusters. Fixes naming inconsistencies among properties of `AutoScalingGroup`, and between EC2 AutoScaling and Application AutoScaling. This PR takes #991 but reimplements the API in the style of the ECS library. BREAKING CHANGE: For `AutoScalingGroup`, renamed `minSize` => `minCapacity`, `maxSize` => `maxCapacity`, for consistency with `desiredCapacity` and also Application AutoScaling. For ECS's `addDefaultAutoScalingGroupCapacity()`, `instanceCount` => `desiredCapacity` and the function now takes an ID (pass `"DefaultAutoScalingGroup"` to avoid interruption to your deployments).
1 parent 7b2bf13 commit 22fc8b9

25 files changed

+1934
-109
lines changed
 

‎packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts

+35-27
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,25 @@ import { BaseTargetTrackingProps, PredefinedMetric, TargetTrackingScalingPolicy
1818
const NAME_TAG: string = 'Name';
1919

2020
/**
21-
* Properties of a Fleet
21+
* Basic properties of an AutoScalingGroup, except the exact machines to run and where they should run
22+
*
23+
* Constructs that want to create AutoScalingGroups can inherit
24+
* this interface and specialize the essential parts in various ways.
2225
*/
23-
export interface AutoScalingGroupProps {
24-
/**
25-
* Type of instance to launch
26-
*/
27-
instanceType: ec2.InstanceType;
28-
26+
export interface CommonAutoScalingGroupProps {
2927
/**
3028
* Minimum number of instances in the fleet
3129
*
3230
* @default 1
3331
*/
34-
minSize?: number;
32+
minCapacity?: number;
3533

3634
/**
3735
* Maximum number of instances in the fleet
3836
*
3937
* @default desiredCapacity
4038
*/
41-
maxSize?: number;
39+
maxCapacity?: number;
4240

4341
/**
4442
* Initial amount of instances in the fleet
@@ -52,16 +50,6 @@ export interface AutoScalingGroupProps {
5250
*/
5351
keyName?: string;
5452

55-
/**
56-
* AMI to launch
57-
*/
58-
machineImage: ec2.IMachineImageSource;
59-
60-
/**
61-
* VPC to launch these instances in.
62-
*/
63-
vpc: ec2.IVpcNetwork;
64-
6553
/**
6654
* Where to place instances within the VPC
6755
*/
@@ -153,6 +141,26 @@ export interface AutoScalingGroupProps {
153141
associatePublicIpAddress?: boolean;
154142
}
155143

144+
/**
145+
* Properties of a Fleet
146+
*/
147+
export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps {
148+
/**
149+
* VPC to launch these instances in.
150+
*/
151+
vpc: ec2.IVpcNetwork;
152+
153+
/**
154+
* Type of instance to launch
155+
*/
156+
instanceType: ec2.InstanceType;
157+
158+
/**
159+
* AMI to launch
160+
*/
161+
machineImage: ec2.IMachineImageSource;
162+
}
163+
156164
/**
157165
* A Fleet represents a managed set of EC2 instances
158166
*
@@ -236,19 +244,19 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup
236244

237245
const desiredCapacity =
238246
(props.desiredCapacity !== undefined ? props.desiredCapacity :
239-
(props.minSize !== undefined ? props.minSize :
240-
(props.maxSize !== undefined ? props.maxSize : 1)));
241-
const minSize = props.minSize !== undefined ? props.minSize : 1;
242-
const maxSize = props.maxSize !== undefined ? props.maxSize : desiredCapacity;
247+
(props.minCapacity !== undefined ? props.minCapacity :
248+
(props.maxCapacity !== undefined ? props.maxCapacity : 1)));
249+
const minCapacity = props.minCapacity !== undefined ? props.minCapacity : 1;
250+
const maxCapacity = props.maxCapacity !== undefined ? props.maxCapacity : desiredCapacity;
243251

244-
if (desiredCapacity < minSize || desiredCapacity > maxSize) {
245-
throw new Error(`Should have minSize (${minSize}) <= desiredCapacity (${desiredCapacity}) <= maxSize (${maxSize})`);
252+
if (desiredCapacity < minCapacity || desiredCapacity > maxCapacity) {
253+
throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`);
246254
}
247255

248256
const asgProps: CfnAutoScalingGroupProps = {
249257
cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined,
250-
minSize: minSize.toString(),
251-
maxSize: maxSize.toString(),
258+
minSize: minCapacity.toString(),
259+
maxSize: maxCapacity.toString(),
252260
desiredCapacity: desiredCapacity.toString(),
253261
launchConfigurationName: launchConfig.ref,
254262
loadBalancerNames: new cdk.Token(() => this.loadBalancerNames.length > 0 ? this.loadBalancerNames : undefined),

‎packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,16 @@ export = {
126126
test.done();
127127
},
128128

129-
'can set minSize, maxSize, desiredCapacity to 0'(test: Test) {
129+
'can set minCapacity, maxCapacity, desiredCapacity to 0'(test: Test) {
130130
const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }});
131131
const vpc = mockVpc(stack);
132132

133133
new autoscaling.AutoScalingGroup(stack, 'MyFleet', {
134134
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro),
135135
machineImage: new ec2.AmazonLinuxImage(),
136136
vpc,
137-
minSize: 0,
138-
maxSize: 0,
137+
minCapacity: 0,
138+
maxCapacity: 0,
139139
desiredCapacity: 0
140140
});
141141

@@ -159,7 +159,7 @@ export = {
159159
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro),
160160
machineImage: new ec2.AmazonLinuxImage(),
161161
vpc,
162-
minSize: 10
162+
minCapacity: 10
163163
});
164164

165165
// THEN
@@ -183,7 +183,7 @@ export = {
183183
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro),
184184
machineImage: new ec2.AmazonLinuxImage(),
185185
vpc,
186-
maxSize: 10
186+
maxCapacity: 10
187187
});
188188

189189
// THEN
@@ -415,8 +415,8 @@ export = {
415415
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro),
416416
machineImage: new ec2.AmazonLinuxImage(),
417417
vpc,
418-
minSize: 0,
419-
maxSize: 0,
418+
minCapacity: 0,
419+
maxCapacity: 0,
420420
desiredCapacity: 0,
421421
associatePublicIpAddress: true,
422422
});
@@ -438,8 +438,8 @@ export = {
438438
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro),
439439
machineImage: new ec2.AmazonLinuxImage(),
440440
vpc,
441-
minSize: 0,
442-
maxSize: 0,
441+
minCapacity: 0,
442+
maxCapacity: 0,
443443
desiredCapacity: 0,
444444
associatePublicIpAddress: false,
445445
});
@@ -461,8 +461,8 @@ export = {
461461
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro),
462462
machineImage: new ec2.AmazonLinuxImage(),
463463
vpc,
464-
minSize: 0,
465-
maxSize: 0,
464+
minCapacity: 0,
465+
maxCapacity: 0,
466466
desiredCapacity: 0,
467467
});
468468

‎packages/@aws-cdk/aws-ec2/lib/machine-image.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class GenericLinuxImage implements IMachineImageSource {
190190
public getImage(scope: Construct): MachineImage {
191191
const stack = Stack.find(scope);
192192
const region = stack.requireRegion('AMI cannot be determined');
193-
const ami = this.amiMap[region];
193+
const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345';
194194
if (!ami) {
195195
throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`);
196196
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const cluster = new ecs.Cluster(this, 'Cluster', {
2121
});
2222

2323
// Add capacity to it
24-
cluster.addDefaultAutoScalingGroupCapacity({
24+
cluster.addDefaultAutoScalingGroupCapacity('Capacity', {
2525
instanceType: new ec2.InstanceType("t2.xlarge"),
2626
instanceCount: 3,
2727
});

‎packages/@aws-cdk/aws-ecs/lib/cluster.ts

+6-30
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,13 @@ export class Cluster extends cdk.Construct implements ICluster {
7474
*
7575
* Returns the AutoScalingGroup so you can add autoscaling settings to it.
7676
*/
77-
public addDefaultAutoScalingGroupCapacity(options: AddDefaultAutoScalingGroupOptions): autoscaling.AutoScalingGroup {
78-
const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'DefaultAutoScalingGroup', {
77+
public addDefaultAutoScalingGroupCapacity(id: string, options: AddDefaultAutoScalingGroupOptions): autoscaling.AutoScalingGroup {
78+
const autoScalingGroup = new autoscaling.AutoScalingGroup(this, id, {
79+
...options,
7980
vpc: this.vpc,
80-
instanceType: options.instanceType,
8181
machineImage: new EcsOptimizedAmi(),
82-
updateType: autoscaling.UpdateType.ReplacingUpdate,
83-
minSize: options.minCapacity,
84-
maxSize: options.maxCapacity,
85-
desiredCapacity: options.instanceCount,
82+
updateType: options.updateType || autoscaling.UpdateType.ReplacingUpdate,
83+
instanceType: options.instanceType,
8684
});
8785

8886
this.addAutoScalingGroupCapacity(autoScalingGroup, options);
@@ -375,31 +373,9 @@ export interface AddAutoScalingGroupCapacityOptions {
375373
/**
376374
* Properties for adding autoScalingGroup
377375
*/
378-
export interface AddDefaultAutoScalingGroupOptions extends AddAutoScalingGroupCapacityOptions {
379-
376+
export interface AddDefaultAutoScalingGroupOptions extends AddAutoScalingGroupCapacityOptions, autoscaling.CommonAutoScalingGroupProps {
380377
/**
381378
* The type of EC2 instance to launch into your Autoscaling Group
382379
*/
383380
instanceType: ec2.InstanceType;
384-
385-
/**
386-
* Number of container instances registered in your ECS Cluster
387-
*
388-
* @default 1
389-
*/
390-
instanceCount?: number;
391-
392-
/**
393-
* Maximum number of instances
394-
*
395-
* @default Same as instanceCount
396-
*/
397-
maxCapacity?: number;
398-
399-
/**
400-
* Minimum number of instances
401-
*
402-
* @default Same as instanceCount
403-
*/
404-
minCapacity?: number;
405381
}

‎packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class EventStack extends cdk.Stack {
1212
const vpc = new ec2.VpcNetwork(this, 'Vpc', { maxAZs: 1 });
1313

1414
const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc });
15-
cluster.addDefaultAutoScalingGroupCapacity({
15+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
1616
instanceType: new ec2.InstanceType('t2.micro')
1717
});
1818

‎packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const stack = new cdk.Stack(app, 'aws-ecs-integ');
1010
const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 });
1111

1212
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
13-
cluster.addDefaultAutoScalingGroupCapacity({
13+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
1414
instanceType: new ec2.InstanceType('t2.micro')
1515
});
1616

‎packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const stack = new cdk.Stack(app, 'aws-ecs-integ-ecs');
1010
const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 });
1111

1212
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
13-
cluster.addDefaultAutoScalingGroupCapacity({
13+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
1414
instanceType: new ec2.InstanceType('t2.micro')
1515
});
1616

@@ -44,4 +44,4 @@ listener.addTargets('ECS', {
4444

4545
new cdk.Output(stack, 'LoadBalancerDNS', { value: lb.dnsName, });
4646

47-
app.run();
47+
app.run();

‎packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export = {
1111
const stack = new cdk.Stack();
1212
const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 1 });
1313
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
14-
cluster.addDefaultAutoScalingGroupCapacity({
14+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
1515
instanceType: new ec2.InstanceType('t2.micro')
1616
});
1717

@@ -58,4 +58,4 @@ export = {
5858

5959
test.done();
6060
}
61-
};
61+
};

‎packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export = {
1313
const stack = new cdk.Stack();
1414
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
1515
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
16-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
16+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
1717
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
1818

1919
taskDefinition.addContainer("web", {
@@ -54,7 +54,7 @@ export = {
5454
const stack = new cdk.Stack();
5555
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
5656
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
57-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
57+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
5858
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
5959
taskDefinition.addContainer('BaseContainer', {
6060
image: ecs.ContainerImage.fromDockerHub('test'),
@@ -79,7 +79,7 @@ export = {
7979
const stack = new cdk.Stack();
8080
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
8181
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
82-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
82+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
8383
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
8484
taskDefinition.addContainer('BaseContainer', {
8585
image: ecs.ContainerImage.fromDockerHub('test'),
@@ -124,7 +124,7 @@ export = {
124124
const stack = new cdk.Stack();
125125
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
126126
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
127-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
127+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
128128
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
129129

130130
taskDefinition.addContainer("web", {
@@ -152,7 +152,7 @@ export = {
152152
const stack = new cdk.Stack();
153153
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
154154
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
155-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
155+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
156156
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
157157
networkMode: NetworkMode.Bridge
158158
});
@@ -184,7 +184,7 @@ export = {
184184
const stack = new cdk.Stack();
185185
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
186186
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
187-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
187+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
188188
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
189189
networkMode: NetworkMode.AwsVpc
190190
});
@@ -235,7 +235,7 @@ export = {
235235
const stack = new cdk.Stack();
236236
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
237237
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
238-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
238+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
239239
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
240240
networkMode: NetworkMode.AwsVpc
241241
});
@@ -263,7 +263,7 @@ export = {
263263
const stack = new cdk.Stack();
264264
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
265265
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
266-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
266+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
267267
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
268268

269269
taskDefinition.addContainer("web", {
@@ -292,7 +292,7 @@ export = {
292292
const stack = new cdk.Stack();
293293
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
294294
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
295-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
295+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
296296
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
297297

298298
taskDefinition.addContainer("web", {
@@ -323,7 +323,7 @@ export = {
323323
const stack = new cdk.Stack();
324324
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
325325
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
326-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
326+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
327327
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
328328

329329
taskDefinition.addContainer("web", {
@@ -354,7 +354,7 @@ export = {
354354
const stack = new cdk.Stack();
355355
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
356356
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
357-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
357+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
358358
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
359359

360360
taskDefinition.addContainer("web", {
@@ -381,7 +381,7 @@ export = {
381381
const stack = new cdk.Stack();
382382
const vpc = new ec2.VpcNetwork(stack, 'MyVpc');
383383
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
384-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
384+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
385385
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
386386

387387
taskDefinition.addContainer("web", {
@@ -411,7 +411,7 @@ export = {
411411
const stack = new cdk.Stack();
412412
const vpc = new ec2.VpcNetwork(stack, 'MyVpc');
413413
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
414-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
414+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
415415
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
416416

417417
taskDefinition.addContainer("web", {
@@ -438,7 +438,7 @@ export = {
438438
const stack = new cdk.Stack();
439439
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
440440
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
441-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
441+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
442442
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
443443

444444
taskDefinition.addContainer("web", {
@@ -469,7 +469,7 @@ export = {
469469
const stack = new cdk.Stack();
470470
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
471471
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
472-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
472+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
473473
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
474474

475475
taskDefinition.addContainer("web", {
@@ -498,7 +498,7 @@ export = {
498498
const stack = new cdk.Stack();
499499
const vpc = new ec2.VpcNetwork(stack, 'VPC');
500500
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });
501-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
501+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
502502
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TD', { networkMode: ecs.NetworkMode.Host });
503503
const container = taskDefinition.addContainer('web', {
504504
image: ecs.ContainerImage.fromDockerHub('test'),

‎packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export = {
1515
vpc,
1616
});
1717

18-
cluster.addDefaultAutoScalingGroupCapacity({
18+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
1919
instanceType: new ec2.InstanceType('t2.micro')
2020
});
2121

@@ -164,7 +164,7 @@ export = {
164164
});
165165

166166
// WHEN
167-
cluster.addDefaultAutoScalingGroupCapacity({
167+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
168168
instanceType: new ec2.InstanceType('t2.micro')
169169
});
170170

@@ -188,7 +188,7 @@ export = {
188188
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
189189

190190
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
191-
cluster.addDefaultAutoScalingGroupCapacity({
191+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
192192
instanceType: new InstanceType("m3.large")
193193
});
194194

@@ -206,9 +206,9 @@ export = {
206206
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {});
207207

208208
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
209-
cluster.addDefaultAutoScalingGroupCapacity({
209+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', {
210210
instanceType: new ec2.InstanceType('t2.micro'),
211-
instanceCount: 3
211+
desiredCapacity: 3
212212
});
213213

214214
// THEN

‎packages/@aws-cdk/aws-ecs/test/test.l3s.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export = {
1212
const stack = new cdk.Stack();
1313
const vpc = new ec2.VpcNetwork(stack, 'VPC');
1414
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });
15-
cluster.addDefaultAutoScalingGroupCapacity({ instanceType: new ec2.InstanceType('t2.micro') });
15+
cluster.addDefaultAutoScalingGroupCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
1616

1717
// WHEN
1818
new ecs.LoadBalancedEc2Service(stack, 'Service', {

‎packages/@aws-cdk/aws-eks/README.md

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,32 @@
1-
## The CDK Construct Library for AWS EKS
2-
This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.
1+
## AWS Elastic Container Service for Kubernetes (EKS) Construct Library
2+
3+
This construct library allows you to define and create [Amazon Elastic Container Service for Kubernetes (EKS)](https://aws.amazon.com/eks/) clusters programmatically.
4+
5+
### Example
6+
7+
The following example shows how to start an EKS cluster and how to
8+
add worker nodes to it:
9+
10+
[starting a cluster example](test/integ.eks-cluster.lit.ts)
11+
12+
After deploying the previous CDK app you still need to configure `kubectl`
13+
and manually add the nodes to your cluster, as described [in the EKS user
14+
guide](https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html).
15+
16+
### SSH into your nodes
17+
18+
If you want to be able to SSH into your worker nodes, you must already
19+
have an SSH key in the region you're connecting to and pass it, and you must
20+
be able to connect to the hosts (meaning they must have a public IP and you
21+
should be allowed to connect to them on port 22):
22+
23+
[ssh into nodes example](test/example.ssh-into-nodes.lit.ts)
24+
25+
If you want to SSH into nodes in a private subnet, you should set up a
26+
bastion host in a public subnet. That setup is recommended, but is
27+
unfortunately beyond the scope of this documentation.
28+
29+
### Roadmap
30+
31+
- [ ] Add ability to start tasks on clusters using CDK (similar to ECS's "`Service`" concept).
32+
- [ ] Describe how to set up AutoScaling (how to combine EC2 and Kubernetes scaling)

‎packages/@aws-cdk/aws-eks/lib/ami.ts

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import ec2 = require('@aws-cdk/aws-ec2');
2+
3+
/**
4+
* Properties for EksOptimizedAmi
5+
*/
6+
export interface EksOptimizedAmiProps {
7+
/**
8+
* What instance type to retrieve the image for (normal or GPU-optimized)
9+
*
10+
* @default Normal
11+
*/
12+
nodeType?: NodeType;
13+
14+
/**
15+
* The Kubernetes version to use
16+
*
17+
* @default The latest version
18+
*/
19+
kubernetesVersion?: string;
20+
}
21+
22+
/**
23+
* Source for EKS optimized AMIs
24+
*/
25+
export class EksOptimizedAmi extends ec2.GenericLinuxImage implements ec2.IMachineImageSource {
26+
constructor(props: EksOptimizedAmiProps = {}) {
27+
const version = props.kubernetesVersion || LATEST_KUBERNETES_VERSION;
28+
if (!(version in EKS_AMI)) {
29+
throw new Error(`We don't have an AMI for kubernetes version ${version}`);
30+
}
31+
super(EKS_AMI[version][props.nodeType || NodeType.Normal]);
32+
}
33+
}
34+
35+
const LATEST_KUBERNETES_VERSION = '1.11';
36+
37+
/**
38+
* Whether the worker nodes should support GPU or just normal instances
39+
*/
40+
export const enum NodeType {
41+
/**
42+
* Normal instances
43+
*/
44+
Normal = 'Normal',
45+
46+
/**
47+
* GPU instances
48+
*/
49+
GPU = 'GPU',
50+
}
51+
52+
export function nodeTypeForInstanceType(instanceType: ec2.InstanceType) {
53+
return instanceType.toString().startsWith('p2') || instanceType.toString().startsWith('p3') ? NodeType.GPU : NodeType.Normal;
54+
}
55+
56+
/**
57+
* Select AMI to use based on the AWS Region being deployed
58+
*
59+
* TODO: Create dynamic mappign by searching SSM Store
60+
*
61+
* @see https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html
62+
*/
63+
const EKS_AMI: {[version: string]: {[type: string]: {[region: string]: string}}} = {
64+
'1.10': parseTable(`
65+
US West (Oregon) (us-west-2) ami-09e1df3bad220af0b ami-0ebf0561e61a2be02
66+
US East (N. Virginia) (us-east-1) ami-04358410d28eaab63 ami-0131c0ca222183def
67+
US East (Ohio) (us-east-2) ami-0b779e8ab57655b4b ami-0abfb3be33c196cbf
68+
EU (Frankfurt) (eu-central-1) ami-08eb700778f03ea94 ami-000622b1016d2a5bf
69+
EU (Stockholm) (eu-north-1) ami-068b8a1efffd30eda ami-cc149ab2
70+
EU (Ireland) (eu-west-1) ami-0de10c614955da932 ami-0dafd3a1dc43781f7
71+
Asia Pacific (Tokyo) (ap-northeast-1) ami-06398bdd37d76571d ami-0afc9d14b2fe11ad9
72+
Asia Pacific (Seoul) (ap-northeast-2) ami-08a87e0a7c32fa649 ami-0d75b9ab57bfc8c9a
73+
Asia Pacific (Singapore) (ap-southeast-1) ami-0ac3510e44b5bf8ef ami-0ecce0670cb66d17b
74+
Asia Pacific (Sydney) (ap-southeast-2) ami-0d2c929ace88cfebe ami-03b048bd9d3861ce9
75+
`),
76+
'1.11': parseTable(`
77+
US West (Oregon) (us-west-2) ami-0a2abab4107669c1b ami-0c9e5e2d8caa9fb5e
78+
US East (N. Virginia) (us-east-1) ami-0c24db5df6badc35a ami-0ff0241c02b279f50
79+
US East (Ohio) (us-east-2) ami-0c2e8d28b1f854c68 ami-006a12f54eaafc2b1
80+
EU (Frankfurt) (eu-central-1) ami-010caa98bae9a09e2 ami-0d6f0554fd4743a9d
81+
EU (Stockholm) (eu-north-1) ami-06ee67302ab7cf838 ami-0b159b75
82+
EU (Ireland) (eu-west-1) ami-01e08d22b9439c15a ami-097978e7acde1fd7c
83+
Asia Pacific (Tokyo) (ap-northeast-1) ami-0f0e8066383e7a2cb ami-036b3969c5eb8d3cf
84+
Asia Pacific (Seoul) (ap-northeast-2) ami-0b7baa90de70f683f ami-0b7f163f7194396f7
85+
Asia Pacific (Singapore) (ap-southeast-1) ami-019966ed970c18502 ami-093f742654a955ee6
86+
Asia Pacific (Sydney) (ap-southeast-2) ami-06ade0abbd8eca425 ami-05e09575123ff498b
87+
`),
88+
};
89+
90+
/**
91+
* Helper function which makes it easier to copy/paste the HTML AMI table into this source.
92+
*
93+
* I can't stress enough how much of a temporary solution this should be, but until we
94+
* have a proper discovery mechanism, this is easier than converting the table into
95+
* nested dicts by hand.
96+
*/
97+
function parseTable(contents: string): {[type: string]: {[region: string]: string}} {
98+
const normalTable: {[region: string]: string} = {};
99+
const gpuTable: {[region: string]: string} = {};
100+
101+
// Last parenthesized expression that looks like a region
102+
const extractRegion = /\(([a-z]+-[a-z]+-[0-9]+)\)\s*$/;
103+
104+
for (const line of contents.split('\n')) {
105+
if (line.trim() === '') { continue; }
106+
107+
const parts = line.split('\t');
108+
if (parts.length !== 3) {
109+
throw new Error(`Line lost its TABs: "${line}"`);
110+
}
111+
112+
const m = extractRegion.exec(parts[0]);
113+
if (!m) { throw new Error(`Like doesn't seem to contain a region: "${line}"`); }
114+
const region = m[1];
115+
116+
normalTable[region] = parts[1].trim();
117+
gpuTable[region] = parts[2].trim();
118+
}
119+
120+
return {
121+
[NodeType.GPU]: gpuTable,
122+
[NodeType.Normal]: normalTable
123+
};
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import ec2 = require('@aws-cdk/aws-ec2');
2+
import cdk = require('@aws-cdk/cdk');
3+
4+
/**
5+
* An EKS cluster
6+
*/
7+
export interface ICluster extends cdk.IConstruct, ec2.IConnectable {
8+
/**
9+
* The VPC in which this Cluster was created
10+
*/
11+
readonly vpc: ec2.IVpcNetwork;
12+
13+
/**
14+
* The physical name of the Cluster
15+
*/
16+
readonly clusterName: string;
17+
18+
/**
19+
* The unique ARN assigned to the service by AWS
20+
* in the form of arn:aws:eks:
21+
*/
22+
readonly clusterArn: string;
23+
24+
/**
25+
* The API Server endpoint URL
26+
*/
27+
readonly clusterEndpoint: string;
28+
29+
/**
30+
* The certificate-authority-data for your cluster.
31+
*/
32+
readonly clusterCertificateAuthorityData: string;
33+
34+
/**
35+
* Export cluster references to use in other stacks
36+
*/
37+
export(): ClusterImportProps;
38+
}
39+
40+
/**
41+
* A SecurityGroup Reference, object not created with this template.
42+
*/
43+
export abstract class ClusterBase extends cdk.Construct implements ICluster {
44+
public abstract readonly connections: ec2.Connections;
45+
public abstract readonly vpc: ec2.IVpcNetwork;
46+
public abstract readonly clusterName: string;
47+
public abstract readonly clusterArn: string;
48+
public abstract readonly clusterEndpoint: string;
49+
public abstract readonly clusterCertificateAuthorityData: string;
50+
51+
/**
52+
* Export cluster references to use in other stacks
53+
*/
54+
public export(): ClusterImportProps {
55+
return {
56+
vpc: this.vpc.export(),
57+
clusterName: this.makeOutput('ClusterNameExport', this.clusterName),
58+
clusterArn: this.makeOutput('ClusterArn', this.clusterArn),
59+
clusterEndpoint: this.makeOutput('ClusterEndpoint', this.clusterEndpoint),
60+
clusterCertificateAuthorityData: this.makeOutput('ClusterCAData', this.clusterCertificateAuthorityData),
61+
securityGroups: this.connections.securityGroups.map(sg => sg.export()),
62+
};
63+
}
64+
65+
private makeOutput(name: string, value: any): string {
66+
return new cdk.Output(this, name, { value }).makeImportValue().toString();
67+
}
68+
}
69+
70+
export interface ClusterImportProps {
71+
/**
72+
* The VPC in which this Cluster was created
73+
*/
74+
readonly vpc: ec2.VpcNetworkImportProps;
75+
76+
/**
77+
* The physical name of the Cluster
78+
*/
79+
readonly clusterName: string;
80+
81+
/**
82+
* The unique ARN assigned to the service by AWS
83+
* in the form of arn:aws:eks:
84+
*/
85+
readonly clusterArn: string;
86+
87+
/**
88+
* The API Server endpoint URL
89+
*/
90+
readonly clusterEndpoint: string;
91+
92+
/**
93+
* The certificate-authority-data for your cluster.
94+
*/
95+
readonly clusterCertificateAuthorityData: string;
96+
97+
readonly securityGroups: ec2.SecurityGroupImportProps[];
98+
}
+342
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
import autoscaling = require('@aws-cdk/aws-autoscaling');
2+
import ec2 = require('@aws-cdk/aws-ec2');
3+
import iam = require('@aws-cdk/aws-iam');
4+
import cdk = require('@aws-cdk/cdk');
5+
import { EksOptimizedAmi, nodeTypeForInstanceType } from './ami';
6+
import { ClusterBase, ClusterImportProps, ICluster } from './cluster-base';
7+
import { CfnCluster } from './eks.generated';
8+
import { maxPodsForInstanceType } from './instance-data';
9+
10+
/**
11+
* Properties to instantiate the Cluster
12+
*/
13+
export interface ClusterProps {
14+
/**
15+
* The VPC in which to create the Cluster
16+
*/
17+
vpc: ec2.IVpcNetwork;
18+
19+
/**
20+
* Where to place EKS Control Plane ENIs
21+
*
22+
* If you want to create public load balancers, this must include public subnets.
23+
*
24+
* For example, to only select private subnets, supply the following:
25+
*
26+
* ```
27+
* vpcPlacements: [
28+
* { subnetsToUse: ec2.SubnetType.Private }
29+
* ]
30+
* ```
31+
*
32+
* @default All public and private subnets
33+
*/
34+
vpcPlacements?: ec2.VpcPlacementStrategy[];
35+
36+
/**
37+
* Role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf.
38+
*
39+
* @default A role is automatically created for you
40+
*/
41+
role?: iam.IRole;
42+
43+
/**
44+
* Name for the cluster.
45+
*
46+
* @default Automatically generated name
47+
*/
48+
clusterName?: string;
49+
50+
/**
51+
* Security Group to use for Control Plane ENIs
52+
*
53+
* @default A security group is automatically created
54+
*/
55+
securityGroup?: ec2.ISecurityGroup;
56+
57+
/**
58+
* The Kubernetes version to run in the cluster
59+
*
60+
* @default If not supplied, will use Amazon default version
61+
*/
62+
version?: string;
63+
}
64+
65+
/**
66+
* A Cluster represents a managed Kubernetes Service (EKS)
67+
*
68+
* This is a fully managed cluster of API Servers (control-plane)
69+
* The user is still required to create the worker nodes.
70+
*/
71+
export class Cluster extends ClusterBase {
72+
/**
73+
* Import an existing cluster
74+
*
75+
* @param scope the construct scope, in most cases 'this'
76+
* @param id the id or name to import as
77+
* @param props the cluster properties to use for importing information
78+
*/
79+
public static import(scope: cdk.Construct, id: string, props: ClusterImportProps): ICluster {
80+
return new ImportedCluster(scope, id, props);
81+
}
82+
83+
/**
84+
* The VPC in which this Cluster was created
85+
*/
86+
public readonly vpc: ec2.IVpcNetwork;
87+
88+
/**
89+
* The Name of the created EKS Cluster
90+
*/
91+
public readonly clusterName: string;
92+
93+
/**
94+
* The AWS generated ARN for the Cluster resource
95+
*
96+
* @example arn:aws:eks:us-west-2:666666666666:cluster/prod
97+
*/
98+
public readonly clusterArn: string;
99+
100+
/**
101+
* The endpoint URL for the Cluster
102+
*
103+
* This is the URL inside the kubeconfig file to use with kubectl
104+
*
105+
* @example https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com
106+
*/
107+
public readonly clusterEndpoint: string;
108+
109+
/**
110+
* The certificate-authority-data for your cluster.
111+
*/
112+
public readonly clusterCertificateAuthorityData: string;
113+
114+
/**
115+
* Manages connection rules (Security Group Rules) for the cluster
116+
*
117+
* @type {ec2.Connections}
118+
* @memberof Cluster
119+
*/
120+
public readonly connections: ec2.Connections;
121+
122+
/**
123+
* IAM role assumed by the EKS Control Plane
124+
*/
125+
public readonly role: iam.IRole;
126+
127+
private readonly version: string | undefined;
128+
129+
/**
130+
* Initiates an EKS Cluster with the supplied arguments
131+
*
132+
* @param scope a Construct, most likely a cdk.Stack created
133+
* @param name the name of the Construct to create
134+
* @param props properties in the IClusterProps interface
135+
*/
136+
constructor(scope: cdk.Construct, id: string, props: ClusterProps) {
137+
super(scope, id);
138+
139+
this.vpc = props.vpc;
140+
this.version = props.version;
141+
142+
this.tagSubnets();
143+
144+
this.role = props.role || new iam.Role(this, 'ClusterRole', {
145+
assumedBy: new iam.ServicePrincipal('eks.amazonaws.com'),
146+
managedPolicyArns: [
147+
new iam.AwsManagedPolicy('AmazonEKSClusterPolicy', this).policyArn,
148+
new iam.AwsManagedPolicy('AmazonEKSServicePolicy', this).policyArn,
149+
],
150+
});
151+
152+
const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'ControlPlaneSecurityGroup', {
153+
vpc: props.vpc,
154+
description: 'EKS Control Plane Security Group',
155+
});
156+
157+
this.connections = new ec2.Connections({
158+
securityGroups: [securityGroup],
159+
defaultPortRange: new ec2.TcpPort(443), // Control Plane has an HTTPS API
160+
});
161+
162+
// Get subnetIds for all selected subnets
163+
const placements = props.vpcPlacements || [{ subnetsToUse: ec2.SubnetType.Public }, { subnetsToUse: ec2.SubnetType.Private }];
164+
const subnetIds = flatMap(placements, p => this.vpc.subnets(p)).map(s => s.subnetId);
165+
166+
const resource = new CfnCluster(this, 'Resource', {
167+
name: props.clusterName,
168+
roleArn: this.role.roleArn,
169+
version: props.version,
170+
resourcesVpcConfig: {
171+
securityGroupIds: [securityGroup.securityGroupId],
172+
subnetIds
173+
}
174+
});
175+
176+
this.clusterName = resource.clusterName;
177+
this.clusterArn = resource.clusterArn;
178+
this.clusterEndpoint = resource.clusterEndpoint;
179+
this.clusterCertificateAuthorityData = resource.clusterCertificateAuthorityData;
180+
181+
new cdk.Output(this, 'ClusterName', { value: this.clusterName, disableExport: true });
182+
}
183+
184+
/**
185+
* Add nodes to this EKS cluster
186+
*
187+
* The nodes will automatically be configured with the right VPC and AMI
188+
* for the instance type and Kubernetes version.
189+
*/
190+
public addCapacity(id: string, options: AddWorkerNodesOptions): autoscaling.AutoScalingGroup {
191+
const asg = new autoscaling.AutoScalingGroup(this, id, {
192+
...options,
193+
vpc: this.vpc,
194+
machineImage: new EksOptimizedAmi({
195+
nodeType: nodeTypeForInstanceType(options.instanceType),
196+
kubernetesVersion: this.version,
197+
}),
198+
updateType: options.updateType || autoscaling.UpdateType.RollingUpdate,
199+
instanceType: options.instanceType,
200+
});
201+
202+
this.addAutoScalingGroup(asg, {
203+
maxPods: maxPodsForInstanceType(options.instanceType),
204+
});
205+
206+
return asg;
207+
}
208+
209+
/**
210+
* Add compute capacity to this EKS cluster in the form of an AutoScalingGroup
211+
*
212+
* The AutoScalingGroup must be running an EKS-optimized AMI containing the
213+
* /etc/eks/bootstrap.sh script. This method will configure Security Groups,
214+
* add the right policies to the instance role, apply the right tags, and add
215+
* the required user data to the instance's launch configuration.
216+
*
217+
* Prefer to use `addCapacity` if possible, it will automatically configure
218+
* the right AMI and the `maxPods` number based on your instance type.
219+
*
220+
* @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html
221+
*/
222+
public addAutoScalingGroup(autoScalingGroup: autoscaling.AutoScalingGroup, options: AddAutoScalingGroupOptions) {
223+
// self rules
224+
autoScalingGroup.connections.allowInternally(new ec2.AllTraffic());
225+
226+
// Cluster to:nodes rules
227+
autoScalingGroup.connections.allowFrom(this, new ec2.TcpPort(443));
228+
autoScalingGroup.connections.allowFrom(this, new ec2.TcpPortRange(1025, 65535));
229+
230+
// Allow HTTPS from Nodes to Cluster
231+
autoScalingGroup.connections.allowTo(this, new ec2.TcpPort(443));
232+
233+
// Allow all node outbound traffic
234+
autoScalingGroup.connections.allowToAnyIPv4(new ec2.TcpAllPorts());
235+
autoScalingGroup.connections.allowToAnyIPv4(new ec2.UdpAllPorts());
236+
autoScalingGroup.connections.allowToAnyIPv4(new ec2.IcmpAllTypesAndCodes());
237+
238+
autoScalingGroup.addUserData(
239+
'set -o xtrace',
240+
`/etc/eks/bootstrap.sh ${this.clusterName} --use-max-pods ${options.maxPods}`,
241+
);
242+
// FIXME: Add a cfn-signal call once we've sorted out UserData and can write reliable
243+
// signaling scripts: https://github.com/awslabs/aws-cdk/issues/623
244+
245+
autoScalingGroup.role.attachManagedPolicy(new iam.AwsManagedPolicy('AmazonEKSWorkerNodePolicy', this).policyArn);
246+
autoScalingGroup.role.attachManagedPolicy(new iam.AwsManagedPolicy('AmazonEKS_CNI_Policy', this).policyArn);
247+
autoScalingGroup.role.attachManagedPolicy(new iam.AwsManagedPolicy('AmazonEC2ContainerRegistryReadOnly', this).policyArn);
248+
249+
// EKS Required Tags
250+
autoScalingGroup.apply(new cdk.Tag(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { applyToLaunchedInstances: true }));
251+
252+
// Create an Output for the Instance Role ARN (need to paste it into aws-auth-cm.yaml)
253+
new cdk.Output(autoScalingGroup, 'InstanceRoleARN', {
254+
disableExport: true,
255+
value: autoScalingGroup.role.roleArn
256+
});
257+
}
258+
259+
/**
260+
* Opportunistically tag subnets with the required tags.
261+
*
262+
* If no subnets could be found (because this is an imported VPC), add a warning.
263+
*
264+
* @see https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html
265+
*/
266+
private tagSubnets() {
267+
const privates = this.vpc.subnets({ subnetsToUse: ec2.SubnetType.Private });
268+
269+
for (const subnet of privates) {
270+
if (!isRealSubnetConstruct(subnet)) {
271+
// Just give up, all of them will be the same.
272+
this.node.addWarning('Could not auto-tag private subnets with "kubernetes.io/role/internal-elb=1", please remember to do this manually');
273+
return;
274+
}
275+
276+
subnet.apply(new cdk.Tag("kubernetes.io/role/internal-elb", "1"));
277+
}
278+
}
279+
}
280+
281+
function isRealSubnetConstruct(subnet: ec2.IVpcSubnet): subnet is ec2.VpcSubnet {
282+
return (subnet as any).addDefaultRouteToIGW !== undefined;
283+
}
284+
285+
/**
286+
* Options for adding worker nodes
287+
*/
288+
export interface AddWorkerNodesOptions extends autoscaling.CommonAutoScalingGroupProps {
289+
/**
290+
* Instance type of the instances to start
291+
*/
292+
instanceType: ec2.InstanceType;
293+
}
294+
295+
/**
296+
* Options for adding an AutoScalingGroup as capacity
297+
*/
298+
export interface AddAutoScalingGroupOptions {
299+
/**
300+
* How many pods to allow on this instance.
301+
*
302+
* Should be at most equal to the maximum number of IP addresses available to
303+
* the instance type less one.
304+
*/
305+
maxPods: number;
306+
}
307+
308+
/**
309+
* Import a cluster to use in another stack
310+
*/
311+
class ImportedCluster extends ClusterBase {
312+
public readonly vpc: ec2.IVpcNetwork;
313+
public readonly clusterCertificateAuthorityData: string;
314+
public readonly clusterName: string;
315+
public readonly clusterArn: string;
316+
public readonly clusterEndpoint: string;
317+
public readonly connections = new ec2.Connections();
318+
319+
constructor(scope: cdk.Construct, id: string, props: ClusterImportProps) {
320+
super(scope, id);
321+
322+
this.vpc = ec2.VpcNetwork.import(this, "VPC", props.vpc);
323+
this.clusterName = props.clusterName;
324+
this.clusterEndpoint = props.clusterEndpoint;
325+
this.clusterArn = props.clusterArn;
326+
this.clusterCertificateAuthorityData = props.clusterCertificateAuthorityData;
327+
328+
let i = 1;
329+
for (const sgProps of props.securityGroups) {
330+
this.connections.addSecurityGroup(ec2.SecurityGroup.import(this, `SecurityGroup${i}`, sgProps));
331+
i++;
332+
}
333+
}
334+
}
335+
336+
function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
337+
const ret = new Array<U>();
338+
for (const x of xs) {
339+
ret.push(...f(x));
340+
}
341+
return ret;
342+
}
+5-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
export * from "./cluster-base";
2+
export * from "./cluster";
3+
export * from "./ami";
4+
15
// AWS::EKS CloudFormation Resources:
2-
export * from './eks.generated';
6+
export * from "./eks.generated";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import ec2 = require('@aws-cdk/aws-ec2');
2+
3+
/**
4+
* Used internally to bootstrap the worker nodes
5+
* This sets the max pods based on the instanceType created
6+
* ref: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI
7+
*/
8+
const MAX_PODS = Object.freeze(
9+
new Map([
10+
['c4.large', 29],
11+
['c4.xlarge', 58],
12+
['c4.2xlarge', 58],
13+
['c4.4xlarge', 234],
14+
['c4.8xlarge', 234],
15+
['c5.large', 29],
16+
['c5.xlarge', 58],
17+
['c5.2xlarge', 58],
18+
['c5.4xlarge', 234],
19+
['c5.9xlarge', 234],
20+
['c5.18xlarge', 737],
21+
['i3.large', 29],
22+
['i3.xlarge', 58],
23+
['i3.2xlarge', 58],
24+
['i3.4xlarge', 234],
25+
['i3.8xlarge', 234],
26+
['i3.16xlarge', 737],
27+
['m3.medium', 12],
28+
['m3.large', 29],
29+
['m3.xlarge', 58],
30+
['m3.2xlarge', 118],
31+
['m4.large', 20],
32+
['m4.xlarge', 58],
33+
['m4.2xlarge', 58],
34+
['m4.4xlarge', 234],
35+
['m4.10xlarge', 234],
36+
['m5.large', 29],
37+
['m5.xlarge', 58],
38+
['m5.2xlarge', 58],
39+
['m5.4xlarge', 234],
40+
['m5.12xlarge', 234],
41+
['m5.24xlarge', 737],
42+
['p2.xlarge', 58],
43+
['p2.8xlarge', 234],
44+
['p2.16xlarge', 234],
45+
['p3.2xlarge', 58],
46+
['p3.8xlarge', 234],
47+
['p3.16xlarge', 234],
48+
['r3.xlarge', 58],
49+
['r3.2xlarge', 58],
50+
['r3.4xlarge', 234],
51+
['r3.8xlarge', 234],
52+
['r4.large', 29],
53+
['r4.xlarge', 58],
54+
['r4.2xlarge', 58],
55+
['r4.4xlarge', 234],
56+
['r4.8xlarge', 234],
57+
['r4.16xlarge', 735],
58+
['t2.small', 8],
59+
['t2.medium', 17],
60+
['t2.large', 35],
61+
['t2.xlarge', 44],
62+
['t2.2xlarge', 44],
63+
['x1.16xlarge', 234],
64+
['x1.32xlarge', 234],
65+
]),
66+
);
67+
68+
export function maxPodsForInstanceType(instanceType: ec2.InstanceType) {
69+
const num = MAX_PODS.get(instanceType.toString());
70+
if (num === undefined) {
71+
throw new Error(`Instance type not supported for EKS: ${instanceType.toString()}. Please pick a different instance type.`);
72+
}
73+
return num;
74+
}

‎packages/@aws-cdk/aws-eks/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,21 @@
5656
"devDependencies": {
5757
"@aws-cdk/assert": "^0.24.1",
5858
"cdk-build-tools": "^0.24.1",
59+
"cdk-integ-tools": "^0.24.1",
5960
"cfn2ts": "^0.24.1",
6061
"pkglint": "^0.24.1"
6162
},
6263
"dependencies": {
64+
"@aws-cdk/aws-ec2": "^0.24.1",
65+
"@aws-cdk/aws-iam": "^0.24.1",
66+
"@aws-cdk/aws-autoscaling": "^0.24.1",
6367
"@aws-cdk/cdk": "^0.24.1"
6468
},
6569
"homepage": "https://github.com/awslabs/aws-cdk",
6670
"peerDependencies": {
71+
"@aws-cdk/aws-ec2": "^0.24.1",
72+
"@aws-cdk/aws-iam": "^0.24.1",
73+
"@aws-cdk/aws-autoscaling": "^0.24.1",
6774
"@aws-cdk/cdk": "^0.24.1"
6875
},
6976
"engines": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Manual verification
2+
3+
Following https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html
4+
5+
After starting the cluster and installing `kubectl` and `aws-iam-authenticator`:
6+
7+
```
8+
apiVersion: v1
9+
kind: ConfigMap
10+
metadata:
11+
name: aws-auth
12+
namespace: kube-system
13+
data:
14+
mapRoles: |
15+
- rolearn: <ROLE ARN>
16+
username: system:node:{{EC2PrivateDNSName}}
17+
groups:
18+
- system:bootstrappers
19+
- system:nodes
20+
```
21+
22+
```
23+
aws eks update-kubeconfig --name {{ClusterName}}
24+
25+
# File above, with substitutions
26+
kubectl apply -f aws-auth-cm.yaml
27+
28+
# Check that nodes joined (may take a while)
29+
kubectl get nodes
30+
31+
# Start services (will autocreate a load balancer)
32+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-controller.json
33+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-service.json
34+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-controller.json
35+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-service.json
36+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-controller.json
37+
kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-service.json
38+
39+
# Check up on service status
40+
kubectl get services -o wide
41+
```
42+
43+
Visit the website that appears under LoadBalancer on port 3000. The Amazon corporate network will block this
44+
port, in which case you add this:
45+
46+
```
47+
ssh -L 3000:<ELB ADDRESS>:3000 ssh-box-somewhere.example.com
48+
49+
# Visit http://localhost:3000/
50+
```
51+
52+
Clean the services before you stop the cluster to get rid of the load balancer
53+
(otherwise you won't be able to delet the stack):
54+
55+
```
56+
kubectl delete --all services
57+
58+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import ec2 = require('@aws-cdk/aws-ec2');
2+
import cdk = require('@aws-cdk/cdk');
3+
import eks = require('../lib');
4+
5+
class EksClusterStack extends cdk.Stack {
6+
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
7+
super(scope, id, props);
8+
9+
const vpc = new ec2.VpcNetwork(this, 'VPC');
10+
11+
const cluster = new eks.Cluster(this, 'EKSCluster', {
12+
vpc
13+
});
14+
15+
/// !show
16+
const asg = cluster.addCapacity('Nodes', {
17+
instanceType: new ec2.InstanceType('t2.medium'),
18+
vpcPlacement: { subnetsToUse: ec2.SubnetType.Public },
19+
keyName: 'my-key-name',
20+
});
21+
22+
// Replace with desired IP
23+
asg.connections.allowFrom(new ec2.CidrIPv4('1.2.3.4/32'), new ec2.TcpPort(22));
24+
/// !hide
25+
}
26+
}
27+
28+
const app = new cdk.App();
29+
30+
new EksClusterStack(app, 'eks-integ-test');
31+
32+
app.run();

‎packages/@aws-cdk/aws-eks/test/integ.eks-cluster.lit.expected.json

+919
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import ec2 = require('@aws-cdk/aws-ec2');
2+
import cdk = require('@aws-cdk/cdk');
3+
import eks = require('../lib');
4+
5+
class EksClusterStack extends cdk.Stack {
6+
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
7+
super(scope, id, props);
8+
9+
/// !show
10+
const vpc = new ec2.VpcNetwork(this, 'VPC');
11+
12+
const cluster = new eks.Cluster(this, 'EKSCluster', {
13+
vpc
14+
});
15+
16+
cluster.addCapacity('Nodes', {
17+
instanceType: new ec2.InstanceType('t2.medium'),
18+
desiredCapacity: 1, // Raise this number to add more nodes
19+
});
20+
/// !hide
21+
}
22+
}
23+
24+
const app = new cdk.App();
25+
26+
new EksClusterStack(app, 'eks-integ-test');
27+
28+
app.run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert';
2+
import ec2 = require('@aws-cdk/aws-ec2');
3+
import cdk = require('@aws-cdk/cdk');
4+
import { Test } from 'nodeunit';
5+
import eks = require('../lib');
6+
7+
export = {
8+
'a default cluster spans all subnets'(test: Test) {
9+
// GIVEN
10+
const [stack, vpc] = testFixture();
11+
12+
// WHEN
13+
new eks.Cluster(stack, 'Cluster', { vpc });
14+
15+
// THEN
16+
expect(stack).to(haveResourceLike('AWS::EKS::Cluster', {
17+
ResourcesVpcConfig: {
18+
SubnetIds: [
19+
{ Ref: "VPCPublicSubnet1SubnetB4246D30" },
20+
{ Ref: "VPCPublicSubnet2Subnet74179F39" },
21+
{ Ref: "VPCPublicSubnet3Subnet631C5E25" },
22+
{ Ref: "VPCPrivateSubnet1Subnet8BCA10E0" },
23+
{ Ref: "VPCPrivateSubnet2SubnetCFCDAA7A" },
24+
{ Ref: "VPCPrivateSubnet3Subnet3EDCD457" }
25+
]
26+
}
27+
}));
28+
29+
test.done();
30+
},
31+
32+
'creating a cluster tags the private VPC subnets'(test: Test) {
33+
// GIVEN
34+
const [stack, vpc] = testFixture();
35+
36+
// WHEN
37+
new eks.Cluster(stack, 'Cluster', { vpc });
38+
39+
// THEN
40+
expect(stack).to(haveResource('AWS::EC2::Subnet', {
41+
Tags: [
42+
{ Key: "Name", Value: "VPC/PrivateSubnet1" },
43+
{ Key: "aws-cdk:subnet-name", Value: "Private" },
44+
{ Key: "aws-cdk:subnet-type", Value: "Private" },
45+
{ Key: "kubernetes.io/role/internal-elb", Value: "1" }
46+
]
47+
}));
48+
49+
test.done();
50+
},
51+
52+
'adding capacity creates an ASG with tags'(test: Test) {
53+
// GIVEN
54+
const [stack, vpc] = testFixture();
55+
const cluster = new eks.Cluster(stack, 'Cluster', { vpc });
56+
57+
// WHEN
58+
cluster.addCapacity('Default', {
59+
instanceType: new ec2.InstanceType('t2.medium'),
60+
});
61+
62+
// THEN
63+
expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', {
64+
Tags: [
65+
{
66+
Key: "Name",
67+
PropagateAtLaunch: true,
68+
Value: "Cluster/Default"
69+
},
70+
{
71+
Key: { "Fn::Join": [ "", [ "kubernetes.io/cluster/", { Ref: "ClusterEB0386A7" } ] ] },
72+
PropagateAtLaunch: true,
73+
Value: "owned"
74+
}
75+
]
76+
}));
77+
78+
test.done();
79+
},
80+
81+
'adding capacity correctly deduces maxPods and adds userdata'(test: Test) {
82+
// GIVEN
83+
const [stack, vpc] = testFixture();
84+
const cluster = new eks.Cluster(stack, 'Cluster', { vpc });
85+
86+
// WHEN
87+
cluster.addCapacity('Default', {
88+
instanceType: new ec2.InstanceType('t2.medium'),
89+
});
90+
91+
// THEN
92+
expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', {
93+
UserData: {
94+
"Fn::Base64": {
95+
"Fn::Join": [
96+
"",
97+
[
98+
"#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ",
99+
{ Ref: "ClusterEB0386A7" },
100+
" --use-max-pods 17"
101+
]
102+
]
103+
}
104+
}
105+
}));
106+
107+
test.done();
108+
},
109+
110+
'exercise export/import'(test: Test) {
111+
// GIVEN
112+
const [stack1, vpc] = testFixture();
113+
const stack2 = new cdk.Stack();
114+
const cluster = new eks.Cluster(stack1, 'Cluster', { vpc });
115+
116+
// WHEN
117+
const imported = eks.Cluster.import(stack2, 'Imported', cluster.export());
118+
119+
// THEN
120+
test.deepEqual(stack2.node.resolve(imported.clusterArn), {
121+
'Fn::ImportValue': 'Stack:ClusterClusterArn00DCA0E0'
122+
});
123+
124+
test.done();
125+
},
126+
};
127+
128+
function testFixture(): [cdk.Stack, ec2.VpcNetwork] {
129+
const stack = new cdk.Stack(undefined, 'Stack', { env: { region: 'us-east-1' }});
130+
const vpc = new ec2.VpcNetwork(stack, 'VPC');
131+
132+
return [stack, vpc];
133+
}

‎packages/@aws-cdk/aws-eks/test/test.eks.ts

-8
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.