Skip to content

Commit

Permalink
Update per reviewer feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
otterley committed May 11, 2021
1 parent 53062e8 commit 7f6597b
Show file tree
Hide file tree
Showing 7 changed files with 859 additions and 168 deletions.
106 changes: 58 additions & 48 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,39 +493,6 @@ scaling.scaleOnRequestCount('RequestScaling', {
Task auto-scaling is powered by *Application Auto-Scaling*.
See that section for details.

## Managed Scaling for Amazon EC2 Capacity

Amazon ECS can manage EC2 capacity on which you can place your tasks. You can
enable this by using an EC2 Capacity Provider and associating it with you ECS
tasks and services. If enabled, ECS will scale out the Auto Scaling Group(s) as
necessary to fit your tasks.

```ts
const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'Compute', {
vpc,
instanceType: new ec2.InstanceType('t2.xlarge'),
minCapacity: 0,
maxCapacity: 100
});
const capacity = new ecs.CapacityProvider(this, 'EC2Capacity', {
autoScalingGroup
});
cluster.addCapacityProvider(capacity);

const service = new ecs.Ec2Service(this, 'Service', {
cluster,
taskDefinition,
desiredCount: 10,
capacityProviderStrategies: [
{
base: 0,
weight: 1,
capacityProvider: capacity.capacityProviderName
}
]
});
```

## Integration with CloudWatch Events

To start an Amazon ECS task on an Amazon EC2-backed Cluster, instantiate an
Expand Down Expand Up @@ -758,25 +725,20 @@ ecsService.associateCloudMapService({

## Capacity Providers

Currently, only `FARGATE` and `FARGATE_SPOT` capacity providers are supported.
There are two major families of Capacity Providers: Fargate and EC2 (via Auto
Scaling Groups. Both are supported.

To enable capacity providers on your cluster, set the `capacityProviders` field
to [`FARGATE`, `FARGATE_SPOT`]. Then, specify capacity provider strategies on
the `capacityProviderStrategies` field for your Fargate Service.
### Fargate Capacity Providers

```ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '../../lib';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'aws-ecs-integ-capacity-provider');

const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 });
To enable Fargate capacity providers, you can either set
`enableFargateCapacityProviders` to `true` when creating your cluster, or by
invoking the `enableFargateCapacityProviders()` method after creating your
cluster.

```ts
const cluster = new ecs.Cluster(stack, 'FargateCPCluster', {
vpc,
capacityProviders: ['FARGATE', 'FARGATE_SPOT'],
enableFargateCapacityProviders: true,
});

const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef');
Expand All @@ -799,8 +761,56 @@ new ecs.FargateService(stack, 'FargateService', {
}
],
});
```

### EC2 Capacity Providers

To add an EC2 Capacity Provider, first create an EC2 Auto Scaling Group. Then,
create an `EC2CapacityProvider` and pass the Auto Scaling Group to it in the
constructor. Then add the Capacity Provider to the cluster. Finally, you can
refer to the Provider by its name in your service's or task's Capacity Provider
strategy.

By default, an EC2 Capacity Provider will manage the Auto Scaling Group's size
for you. It will also enable managed termination protection, in order to prevent
EC2 Auto Scaling from terminating EC2 instances that have tasks running on
them. If you want to disable this behavior, set both `enableManagedScaling` to
and `enableManagedTerminationProtection` to `false`.

```ts
const cluster = new ecs.Cluster(stack, 'Cluster', {
vpc,
});

const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'ASG', {
vpc,
instanceType: new ec2.InstanceType('t2.micro'),
machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
minCapacity: 0,
maxCapacity: 100,
});

const capacityProvider = new ecs.EC2CapacityProvider(stack, 'EC2CapacityProvider', {
autoScalingGroup,
});
cluster.addEC2CapacityProvider(capacityProvider);

app.synth();
const taskDefinition = new ecs.EC2TaskDefinition(stack, 'TaskDef');

taskDefinition.addContainer('web', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryReservationMiB: 256,
});

new ecs.Ec2Service(stack, 'EC2Service', {
cluster,
taskDefinition,
capacityProviderStrategies: [
{
capacityProvider: capacityProvider.capacityProviderName,
weight: 1,
}
],
});
```

## Elastic Inference Accelerators
Expand Down
161 changes: 56 additions & 105 deletions packages/@aws-cdk/aws-ecs/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,16 @@ export interface ClusterProps {
* The capacity providers to add to the cluster
*
* @default - None. Currently only FARGATE and FARGATE_SPOT are supported.
* @deprecated Use {@link ClusterProps.enableFargateCapacityProvider} and {@link ClusterProps.enableFargateSpotCapacityProvider} instead.
* @deprecated Use {@link ClusterProps.enableFargateCapacityProviders} instead.
*/
readonly capacityProviders?: string[];

/**
* Whether to enable Fargate Capacity Provider
* Whether to enable Fargate Capacity Providers
*
* @default false
*/
readonly enableFargateCapacityProvider?: boolean;

/**
* Whether to enable Fargate Spot Capacity Provider
*
* @default false
*/
readonly enableFargateSpotCapacityProvider?: boolean;
readonly enableFargateCapacityProviders?: boolean;

/**
* If true CloudWatch Container Insights will be enabled for the cluster
Expand Down Expand Up @@ -124,9 +117,14 @@ export class Cluster extends Resource implements ICluster {
public readonly clusterName: string;

/**
* The capacity providers associated with the cluster.
* The cluster-level (FARGATE, FARGATE_SPOT) capacity providers.
*/
private _clusterCapacityProviders: string[] = [];

/**
* The EC2 Auto Scaling Group capacity providers associated with the cluster.
*/
private _capacityProviders: ICapacityProvider[] = [];
private _ec2CapacityProviders: EC2CapacityProvider[] = [];

/**
* The AWS Cloud Map namespace to associate with the cluster.
Expand Down Expand Up @@ -161,22 +159,15 @@ export class Cluster extends Resource implements ICluster {
clusterSettings = [{ name: 'containerInsights', value: props.containerInsights ? ContainerInsights.ENABLED : ContainerInsights.DISABLED }];
}

if (props.enableFargateCapacityProvider || props.capacityProviders?.includes('FARGATE')) {
this.enableFargateCapacityProvider();
}
if (props.enableFargateSpotCapacityProvider || props.capacityProviders?.includes('FARGATE_SPOT')) {
this.enableFargateSpotCapacityProvider();
this._clusterCapacityProviders = props.capacityProviders ?? [];
if (props.enableFargateCapacityProviders) {
this.enableFargateCapacityProviders();
}

const cluster = new CfnCluster(this, 'Resource', {
clusterName: this.physicalName,
clusterSettings,
capacityProviders: Lazy.list(
{
produce: () => this._capacityProviders.map(p => p.capacityProviderName).filter(p => p === 'FARGATE' || p === 'FARGATE_SPOT'),
},
{ omitEmpty: true },
),
capacityProviders: Lazy.list({ produce: () => this._clusterCapacityProviders }, { omitEmpty: true }),
});

this.clusterArn = this.getResourceArnAttribute(cluster.attrArn, {
Expand All @@ -202,24 +193,17 @@ export class Cluster extends Resource implements ICluster {
// since it's harmless, but we'd prefer not to add unexpected new
// resources to the stack which could surprise users working with
// brown-field CDK apps and stacks.
Aspects.of(this).add(new MaybeCreateCapacityProviderAssociations(this, id, this._capacityProviders));
Aspects.of(this).add(new MaybeCreateCapacityProviderAssociations(this, id, this._ec2CapacityProviders));
}

/**
* Enable the Fargate capacity provider for this cluster.
* Enable the Fargate capacity providers for this cluster.
*/
public enableFargateCapacityProvider() {
if (!this._capacityProviders.includes(FargateCapacityProvider)) {
this._capacityProviders.push(FargateCapacityProvider);
}
}

/**
* Enable the Fargate Spot capacity provider for this cluster.
*/
public enableFargateSpotCapacityProvider() {
if (!this._capacityProviders.includes(FargateSpotCapacityProvider)) {
this._capacityProviders.push(FargateSpotCapacityProvider);
public enableFargateCapacityProviders() {
for (const provider of ['FARGATE', 'FARGATE_SPOT']) {
if (!this._clusterCapacityProviders.includes(provider)) {
this._clusterCapacityProviders.push(provider);
}
}
}

Expand Down Expand Up @@ -293,35 +277,36 @@ export class Cluster extends Resource implements ICluster {
*
* @param provider the capacity provider to add to this cluster.
*/
public addCapacityProvider(provider: ICapacityProvider | string, options: AddAutoScalingGroupCapacityOptions = {}) {
// Backward compatibility
if (typeof(provider) === 'string') {
switch (provider) {
case 'FARGATE':
this.addCapacityProvider(FargateCapacityProvider);
return;
case 'FARGATE_SPOT':
this.addCapacityProvider(FargateSpotCapacityProvider);
return;
default:
throw new Error('CapacityProvider not supported');
}
public addCapacityProvider(provider: string) {
if (!(provider === 'FARGATE' || provider === 'FARGATE_SPOT')) {
throw new Error('CapacityProvider not supported');
}
// Don't add the same provider twice
if (this._capacityProviders.includes(provider)) {
return;

if (!this._clusterCapacityProviders.includes(provider)) {
this._clusterCapacityProviders.push(provider);
}
}

if (provider instanceof EC2CapacityProvider) {
this._hasEc2Capacity = true;
this.configureAutoScalingGroup(provider.autoScalingGroup, {
...options,
// Don't enable the instance-draining lifecycle hook if managed termination protection is enabled
taskDrainTime: provider.enableManagedTerminationProtection ? Duration.seconds(0) : options.taskDrainTime,
});
/**
* This method adds an Auto Scaling Group Capacity Provider to a cluster using
* the specified EC2 Capacity Provider.
*
* @param provider the capacity provider to add to this cluster.
*/
public addEC2CapacityProvider(provider: EC2CapacityProvider, options: AddAutoScalingGroupCapacityOptions = {}) {
// Don't add the same capacity provider more than once.
if (this._ec2CapacityProviders.includes(provider)) {
return;
}

this._capacityProviders.push(provider);
this._hasEc2Capacity = true;
this.configureAutoScalingGroup(provider.autoScalingGroup, {
...options,
// Don't enable the instance-draining lifecycle hook if managed termination protection is enabled
taskDrainTime: provider.enableManagedTerminationProtection ? Duration.seconds(0) : options.taskDrainTime,
});

this._ec2CapacityProviders.push(provider);
}

/**
Expand Down Expand Up @@ -1074,38 +1059,6 @@ capacity provider. The weight value is taken into consideration after the base v
readonly weight?: number;
}

/**
* An ECS Capacity Provider
*/
export interface ICapacityProvider {
/**
* The name for the capacity provider.
*
* @default CloudFormation-generated name
*/
readonly capacityProviderName: string;
}

/**
* A class used solely for generating the singleton objects that follow
*/
class NamedCapacityProvider implements ICapacityProvider {
public capacityProviderName: string;
constructor(name: string) {
this.capacityProviderName = name;
}
}

/**
* Fargate capacity provider
*/
export const FargateCapacityProvider = new NamedCapacityProvider('FARGATE');

/**
* Fargate Spot capacity provider
*/
export const FargateSpotCapacityProvider = new NamedCapacityProvider('FARGATE_SPOT');

/**
* The options for creating an EC2 Capacity Provider.
*/
Expand All @@ -1119,10 +1072,8 @@ export interface EC2CapacityProviderProps extends AddAutoScalingGroupCapacityOpt

/**
* The autoscaling group to add as a Capacity Provider.
* [disable-awslint:ref-via-interface] is needed to enable managed termination
* protection.
*/
readonly autoScalingGroup: autoscaling.AutoScalingGroup;
readonly autoScalingGroup: autoscaling.IAutoScalingGroup;

/**
* Whether to enable managed scaling
Expand Down Expand Up @@ -1166,10 +1117,8 @@ export interface EC2CapacityProviderProps extends AddAutoScalingGroupCapacityOpt
* ECS can manage the number of instances in the ASG to fit the tasks, and can
* ensure that instances are not prematurely terminated while there are still tasks
* running on them.
* [disable-awslint:ref-via-interface] is needed to enable managed termination
* protection.
*/
export class EC2CapacityProvider extends CoreConstruct implements ICapacityProvider {
export class EC2CapacityProvider extends CoreConstruct {
/**
* Capacity provider name
* @default Chosen by CloudFormation
Expand All @@ -1189,8 +1138,10 @@ export class EC2CapacityProvider extends CoreConstruct implements ICapacityProvi
constructor(scope: Construct, id: string, props: EC2CapacityProviderProps) {
super(scope, id);

this.autoScalingGroup = props.autoScalingGroup;
this.enableManagedTerminationProtection = props.enableManagedTerminationProtection;
this.autoScalingGroup = props.autoScalingGroup as autoscaling.AutoScalingGroup;

this.enableManagedTerminationProtection =
props.enableManagedTerminationProtection === undefined ? true : props.enableManagedTerminationProtection;

if (this.enableManagedTerminationProtection) {
this.autoScalingGroup.protectNewInstancesFromScaleIn();
Expand All @@ -1201,12 +1152,12 @@ export class EC2CapacityProvider extends CoreConstruct implements ICapacityProvi
autoScalingGroupProvider: {
autoScalingGroupArn: this.autoScalingGroup.autoScalingGroupName,
managedScaling: {
status: (props.enableManagedScaling === undefined || props.enableManagedScaling) ? 'ENABLED' : 'DISABLED',
status: props.enableManagedScaling === false ? 'DISABLED' : 'ENABLED',
targetCapacity: props.targetCapacityPercent || 100,
maximumScalingStepSize: props.maximumScalingStepSize,
minimumScalingStepSize: props.minimumScalingStepSize,
},
managedTerminationProtection: (props.enableManagedTerminationProtection === undefined || props.enableManagedTerminationProtection) ? 'ENABLED' : 'DISABLED',
managedTerminationProtection: this.enableManagedTerminationProtection ? 'ENABLED' : 'DISABLED',
},
});

Expand All @@ -1221,9 +1172,9 @@ export class EC2CapacityProvider extends CoreConstruct implements ICapacityProvi
class MaybeCreateCapacityProviderAssociations implements IAspect {
private scope: CoreConstruct;
private id: string;
private capacityProviders: ICapacityProvider[]
private capacityProviders: EC2CapacityProvider[]

constructor(scope: CoreConstruct, id: string, capacityProviders: ICapacityProvider[]) {
constructor(scope: CoreConstruct, id: string, capacityProviders: EC2CapacityProvider[]) {
this.scope = scope;
this.id = id;
this.capacityProviders = capacityProviders;
Expand Down
Loading

0 comments on commit 7f6597b

Please sign in to comment.