Skip to content

Commit

Permalink
docs(ecs): improve docs for managedScaling and `managedTerminationP…
Browse files Browse the repository at this point in the history
…rotection` options (#23729)

## Summary

This PR…
- Improves the documentation for `AsgCapacityProvider`'s `managedScaling` and `managedTerminationProtection` options.
- Throws a new error when `managedTerminationProtection` is `true`/`undefined` and `managedScaling` is explicitly `false`.

Related to but does not fix: #18179

Signed-off-by: Ryan Parker <parkerzr@amazon.com>

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
ryparker authored Jan 18, 2023
1 parent 3d0f4ba commit 23634fd
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 26 deletions.
36 changes: 25 additions & 11 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const cluster = new ecs.Cluster(this, 'Cluster', {
});
```

The following code imports an existing cluster using the ARN which can be used to
The following code imports an existing cluster using the ARN which can be used to
import an Amazon ECS service either EC2 or Fargate.

```ts
Expand Down Expand Up @@ -547,7 +547,7 @@ taskDefinition.addContainer('windowsservercore', {
});
```

### Using Graviton2 with Fargate
### Using Graviton2 with Fargate

AWS Graviton2 supports AWS Fargate. For more details, please see this [blog post](https://aws.amazon.com/blogs/aws/announcing-aws-graviton2-support-for-aws-fargate-get-up-to-40-better-price-performance-for-your-serverless-containers/)

Expand Down Expand Up @@ -729,7 +729,7 @@ There are two higher-level constructs available which include a load balancer fo
`Ec2Service` and `FargateService` provide methods to import existing EC2/Fargate services.
The ARN of the existing service has to be specified to import the service.

Since AWS has changed the [ARN format for ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-account-settings.html#ecs-resource-ids),
Since AWS has changed the [ARN format for ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-account-settings.html#ecs-resource-ids),
feature flag `@aws-cdk/aws-ecs:arnFormatIncludesClusterName` must be enabled to use the new ARN format.
The feature flag changes behavior for the entire CDK project. Therefore it is not possible to mix the old and the new format in one CDK project.

Expand Down Expand Up @@ -1094,11 +1094,25 @@ 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 Auto Scaling Group 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`.
By default, Auto Scaling Group Capacity Providers will manage the scale-in and
scale-out behavior of the auto scaling group based on the load your tasks put on
the cluster, this is called [Managed Scaling](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/asg-capacity-providers.html#asg-capacity-providers-managed-scaling). If you'd
rather manage scaling behavior yourself set `enableManagedScaling` to `false`.

Additionally [Managed Termination Protection](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-auto-scaling.html#managed-termination-protection) is enabled by default to
prevent scale-in behavior from terminating instances that have non-daemon tasks
running on them. This is ideal for tasks that should be ran to completion. If your
tasks are safe to interrupt then this protection can be disabled by setting
`enableManagedTerminationProtection` to `false`. Managed Scaling must be enabled for
Managed Termination Protection to work.

> Currently there is a known [CloudFormation issue](https://github.com/aws/containers-roadmap/issues/631)
> that prevents CloudFormation from automatically deleting Auto Scaling Groups that
> have Managed Termination Protection enabled. To work around this issue you could set
> `enableManagedTerminationProtection` to `false` on the Auto Scaling Group Capacity
> Provider. If you'd rather not disable Managed Termination Protection, you can [manually
> delete the Auto Scaling Group](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-process-shutdown.html).
> For other workarounds, see [this GitHub issue](https://github.com/aws/aws-cdk/issues/18179).
```ts
declare const vpc: ec2.Vpc;
Expand Down Expand Up @@ -1236,11 +1250,11 @@ const cluster = new ecs.Cluster(this, 'Cluster', {

## Amazon ECS Service Connect

Service Connect is a managed AWS mesh network offering. It simplifies DNS queries and inter-service communication for
Service Connect is a managed AWS mesh network offering. It simplifies DNS queries and inter-service communication for
ECS Services by allowing customers to set up simple DNS aliases for their services, which are accessible to all
services that have enabled Service Connect.

To enable Service Connect, you must have created a CloudMap namespace. The CDK can infer your cluster's default CloudMap namespace,
To enable Service Connect, you must have created a CloudMap namespace. The CDK can infer your cluster's default CloudMap namespace,
or you can specify a custom namespace. You must also have created a named port mapping on at least one container in your Task Definition.

```ts
Expand Down Expand Up @@ -1274,7 +1288,7 @@ const service = new ecs.FargateService(this, 'Service', {
});
```

Service Connect-enabled services may now reach this service at `http-api:80`. Traffic to this endpoint will
Service Connect-enabled services may now reach this service at `http-api:80`. Traffic to this endpoint will
be routed to the container's port 8080.

To opt a service into using service connect without advertising a port, simply call the 'enableServiceConnect' method on an initialized service.
Expand Down
34 changes: 22 additions & 12 deletions packages/@aws-cdk/aws-ecs/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export class Cluster extends Resource implements ICluster {
}
}

private renderExecuteCommandConfiguration() : CfnCluster.ClusterConfigurationProperty {
private renderExecuteCommandConfiguration(): CfnCluster.ClusterConfigurationProperty {
return {
executeCommandConfiguration: {
kmsKeyId: this._executeCommandConfiguration?.kmsKey?.keyArn,
Expand Down Expand Up @@ -377,7 +377,7 @@ export class Cluster extends Resource implements ICluster {
*
* @param provider the capacity provider to add to this cluster.
*/
public addAsgCapacityProvider(provider: AsgCapacityProvider, options: AddAutoScalingGroupCapacityOptions= {}) {
public addAsgCapacityProvider(provider: AsgCapacityProvider, options: AddAutoScalingGroupCapacityOptions = {}) {
// Don't add the same capacity provider more than once.
if (this._capacityProviderNames.includes(provider.capacityProviderName)) {
return;
Expand Down Expand Up @@ -1072,14 +1072,25 @@ export interface AsgCapacityProviderProps extends AddAutoScalingGroupCapacityOpt
readonly autoScalingGroup: autoscaling.IAutoScalingGroup;

/**
* Whether to enable managed scaling
* When enabled the scale-in and scale-out actions of the cluster's Auto Scaling Group will be managed for you.
* This means your cluster will automatically scale instances based on the load your tasks put on the cluster.
* For more information, see [Using Managed Scaling](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/asg-capacity-providers.html#asg-capacity-providers-managed-scaling) in the ECS Developer Guide.
*
* @default true
*/
readonly enableManagedScaling?: boolean;

/**
* Whether to enable managed termination protection
* When enabled the Auto Scaling Group will only terminate EC2 instances that no longer have running non-daemon
* tasks.
*
* Scale-in protection will be automatically enabled on instances. When all non-daemon tasks are
* stopped on an instance, ECS initiates the scale-in process and turns off scale-in protection for the
* instance. The Auto Scaling Group can then terminate the instance. For more information see [Managed termination
* protection](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-auto-scaling.html#managed-termination-protection)
* in the ECS Developer Guide.
*
* Managed scaling must also be enabled.
*
* @default true
*/
Expand Down Expand Up @@ -1132,7 +1143,7 @@ export class AsgCapacityProvider extends Construct {
readonly machineImageType: MachineImageType;

/**
* Whether managed termination protection is enabled
* Whether managed termination protection is enabled.
*/
readonly enableManagedTerminationProtection?: boolean;

Expand All @@ -1145,19 +1156,18 @@ export class AsgCapacityProvider extends Construct {

constructor(scope: Construct, id: string, props: AsgCapacityProviderProps) {
super(scope, id);

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

this.machineImageType = props.machineImageType ?? MachineImageType.AMAZON_LINUX_2;

this.canContainersAccessInstanceRole = props.canContainersAccessInstanceRole;
this.enableManagedTerminationProtection = props.enableManagedTerminationProtection ?? true;

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

if (this.enableManagedTerminationProtection && props.enableManagedScaling === false) {
throw new Error('Cannot enable Managed Termination Protection on a Capacity Provider when Managed Scaling is disabled. Either enable Managed Scaling or disable Managed Termination Protection.');
}
if (this.enableManagedTerminationProtection) {
this.autoScalingGroup.protectNewInstancesFromScaleIn();
}

if (props.capacityProviderName) {
if (!(/^(?!aws|ecs|fargate).+/gm.test(props.capacityProviderName))) {
throw new Error(`Invalid Capacity Provider Name: ${props.capacityProviderName}, If a name is specified, it cannot start with aws, ecs, or fargate.`);
Expand Down Expand Up @@ -1191,7 +1201,7 @@ class MaybeCreateCapacityProviderAssociations implements IAspect {
private capacityProviders: string[]
private resource?: CfnClusterCapacityProviderAssociations

constructor(scope: Construct, id: string, capacityProviders: string[] ) {
constructor(scope: Construct, id: string, capacityProviders: string[]) {
this.scope = scope;
this.id = id;
this.capacityProviders = capacityProviders;
Expand Down
78 changes: 75 additions & 3 deletions packages/@aws-cdk/aws-ecs/test/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2037,7 +2037,7 @@ describe('cluster', () => {

});

test('can disable managed scaling for ASG capacity provider', () => {
test('can disable Managed Scaling and Managed Termination Protection for ASG capacity provider', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'test');
Expand All @@ -2052,6 +2052,7 @@ describe('cluster', () => {
new ecs.AsgCapacityProvider(stack, 'provider', {
autoScalingGroup,
enableManagedScaling: false,
enableManagedTerminationProtection: false,
});

// THEN
Expand All @@ -2061,10 +2062,82 @@ describe('cluster', () => {
Ref: 'asgASG4D014670',
},
ManagedScaling: Match.absent(),
ManagedTerminationProtection: 'ENABLED',
ManagedTerminationProtection: 'DISABLED',
},
});
});

test('can disable Managed Termination Protection for ASG capacity provider', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'test');
const vpc = new ec2.Vpc(stack, 'Vpc');
const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'asg', {
vpc,
instanceType: new ec2.InstanceType('bogus'),
machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
});

// WHEN
new ecs.AsgCapacityProvider(stack, 'provider', {
autoScalingGroup,
enableManagedTerminationProtection: false,
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ECS::CapacityProvider', {
AutoScalingGroupProvider: {
AutoScalingGroupArn: {
Ref: 'asgASG4D014670',
},
ManagedScaling: {
Status: 'ENABLED',
TargetCapacity: 100,
},
ManagedTerminationProtection: 'DISABLED',
},
});
});

test('throws error, when ASG capacity provider has Managed Scaling disabled and Managed Termination Protection is undefined (defaults to true)', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'test');
const vpc = new ec2.Vpc(stack, 'Vpc');
const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'asg', {
vpc,
instanceType: new ec2.InstanceType('bogus'),
machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
});

// THEN
expect(() => {
new ecs.AsgCapacityProvider(stack, 'provider', {
autoScalingGroup,
enableManagedScaling: false,
});
}).toThrowError('Cannot enable Managed Termination Protection on a Capacity Provider when Managed Scaling is disabled. Either enable Managed Scaling or disable Managed Termination Protection.');
});

test('throws error, when Managed Scaling is disabled and Managed Termination Protection is enabled.', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'test');
const vpc = new ec2.Vpc(stack, 'Vpc');
const autoScalingGroup = new autoscaling.AutoScalingGroup(stack, 'asg', {
vpc,
instanceType: new ec2.InstanceType('bogus'),
machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
});

// THEN
expect(() => {
new ecs.AsgCapacityProvider(stack, 'provider', {
autoScalingGroup,
enableManagedScaling: false,
enableManagedTerminationProtection: true,
});
}).toThrowError('Cannot enable Managed Termination Protection on a Capacity Provider when Managed Scaling is disabled. Either enable Managed Scaling or disable Managed Termination Protection.');
});

test('capacity provider enables ASG new instance scale-in protection by default', () => {
Expand Down Expand Up @@ -2599,4 +2672,3 @@ describe('Accessing container instance role', function () {
expect(autoScalingGroup.addUserData).not.toHaveBeenCalledWith('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config');
});
});

0 comments on commit 23634fd

Please sign in to comment.