Skip to content

Commit

Permalink
Add ECS deployment circuit breaker support to higher-level constructs
Browse files Browse the repository at this point in the history
  • Loading branch information
Abdul Kader Maliyakkal committed Feb 26, 2021
1 parent c4a239c commit a335496
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 4 deletions.
24 changes: 23 additions & 1 deletion packages/@aws-cdk/aws-ecs-patterns/README.md
Expand Up @@ -394,6 +394,28 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta
});
```

### Deployment circuit breaker and rollback

Amazon ECS [deployment circuit breaker](https://aws.amazon.com/tw/blogs/containers/announcing-amazon-ecs-deployment-circuit-breaker/)
automatically rolls back unhealthy service deployments without the need for manual intervention. Use `circuitBreaker` to enable
deployment circuit breaker and optionally enable `rollback` for automatic rollback. See [Using the deployment circuit breaker](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html)
for more details.

```ts
const service = new ApplicationLoadBalancedFargateService(stack, 'Service', {
cluster,
memoryLimitMiB: 1024,
desiredCount: 1,
cpu: 512,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
deploymentController: {
type: ecs.DeploymentControllerType.ECS,
},
circuitBreaker: { rollback: true },
});
```

### Set deployment configuration on QueueProcessingService

Expand Down Expand Up @@ -469,7 +491,7 @@ const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTa

### Use the REMOVE_DEFAULT_DESIRED_COUNT feature flag

The REMOVE_DEFAULT_DESIRED_COUNT feature flag is used to override the default desiredCount that is autogenerated by the CDK. This will set the desiredCount of any service created by any of the following constructs to be undefined.
The REMOVE_DEFAULT_DESIRED_COUNT feature flag is used to override the default desiredCount that is autogenerated by the CDK. This will set the desiredCount of any service created by any of the following constructs to be undefined.

* ApplicationLoadBalancedEc2Service
* ApplicationLoadBalancedFargateService
Expand Down
@@ -1,6 +1,9 @@
import { Certificate, CertificateValidation, ICertificate } from '@aws-cdk/aws-certificatemanager';
import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
import {
AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, DeploymentCircuitBreaker,
ICluster, LogDriver, PropagatedTagSource, Secret,
} from '@aws-cdk/aws-ecs';
import {
ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup,
IApplicationLoadBalancer, ListenerCertificate, ListenerAction, AddApplicationTargetsProps,
Expand Down Expand Up @@ -226,6 +229,14 @@ export interface ApplicationLoadBalancedServiceBaseProps {
* @default - Rolling update (ECS)
*/
readonly deploymentController?: DeploymentController;

/**
* Whether to enable the deployment circuit breaker. If this property is defined, circuit breaker will be implicitly
* enabled.
* @default - disabled
*/
readonly circuitBreaker?: DeploymentCircuitBreaker;

}

export interface ApplicationLoadBalancedTaskImageOptions {
Expand Down
@@ -1,5 +1,8 @@
import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
import {
AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, DeploymentController, DeploymentCircuitBreaker,
ICluster, LogDriver, PropagatedTagSource, Secret,
} from '@aws-cdk/aws-ecs';
import { INetworkLoadBalancer, NetworkListener, NetworkLoadBalancer, NetworkTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2';
import { IRole } from '@aws-cdk/aws-iam';
import { ARecord, CnameRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
Expand Down Expand Up @@ -176,6 +179,13 @@ export interface NetworkLoadBalancedServiceBaseProps {
* @default - Rolling update (ECS)
*/
readonly deploymentController?: DeploymentController;

/**
* Whether to enable the deployment circuit breaker. If this property is defined, circuit breaker will be implicitly
* enabled.
* @default - disabled
*/
readonly circuitBreaker?: DeploymentCircuitBreaker;
}

export interface NetworkLoadBalancedTaskImageOptions {
Expand Down
@@ -1,6 +1,9 @@
import { ScalingInterval } from '@aws-cdk/aws-applicationautoscaling';
import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, Cluster, ContainerImage, DeploymentController, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
import {
AwsLogDriver, BaseService, Cluster, ContainerImage, DeploymentController, DeploymentCircuitBreaker,
ICluster, LogDriver, PropagatedTagSource, Secret,
} from '@aws-cdk/aws-ecs';
import { IQueue, Queue } from '@aws-cdk/aws-sqs';
import { CfnOutput, Duration, Stack } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
Expand Down Expand Up @@ -189,6 +192,13 @@ export interface QueueProcessingServiceBaseProps {
* @default - Rolling update (ECS)
*/
readonly deploymentController?: DeploymentController;

/**
* Whether to enable the deployment circuit breaker. If this property is defined, circuit breaker will be implicitly
* enabled.
* @default - disabled
*/
readonly circuitBreaker?: DeploymentCircuitBreaker;
}

/**
Expand Down
Expand Up @@ -133,6 +133,7 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe
enableECSManagedTags: props.enableECSManagedTags,
cloudMapOptions: props.cloudMapOptions,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
});
this.addServiceAsTarget(this.service);
}
Expand Down
Expand Up @@ -131,6 +131,7 @@ export class NetworkLoadBalancedEc2Service extends NetworkLoadBalancedServiceBas
enableECSManagedTags: props.enableECSManagedTags,
cloudMapOptions: props.cloudMapOptions,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
});
this.addServiceAsTarget(this.service);
}
Expand Down
Expand Up @@ -114,6 +114,7 @@ export class QueueProcessingEc2Service extends QueueProcessingServiceBase {
propagateTags: props.propagateTags,
enableECSManagedTags: props.enableECSManagedTags,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
});

this.configureAutoscalingForService(this.service);
Expand Down
Expand Up @@ -170,6 +170,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc
cloudMapOptions: props.cloudMapOptions,
platformVersion: props.platformVersion,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
securityGroups: props.securityGroups,
vpcSubnets: props.taskSubnets,
});
Expand Down
Expand Up @@ -157,6 +157,7 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic
cloudMapOptions: props.cloudMapOptions,
platformVersion: props.platformVersion,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
vpcSubnets: props.taskSubnets,
});
this.addServiceAsTarget(this.service);
Expand Down
Expand Up @@ -148,6 +148,7 @@ export class QueueProcessingFargateService extends QueueProcessingServiceBase {
securityGroups: props.securityGroups,
vpcSubnets: props.taskSubnets,
assignPublicIp: props.assignPublicIp,
circuitBreaker: props.circuitBreaker,
});

this.configureAutoscalingForService(this.service);
Expand Down
66 changes: 66 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts
Expand Up @@ -1098,6 +1098,72 @@ export = {
test.done();
},

'ALB with circuit breaker'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });

// WHEN
new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', {
cluster,
memoryLimitMiB: 1024,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
deploymentController: {
type: ecs.DeploymentControllerType.ECS,
},
circuitBreaker: { rollback: true },
});

// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
DeploymentConfiguration: {
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
}));

test.done();
},

'NLB with circuit breaker'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });

// WHEN
new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', {
cluster,
memoryLimitMiB: 1024,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
deploymentController: {
type: ecs.DeploymentControllerType.ECS,
},
circuitBreaker: { rollback: true },
});

// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
DeploymentConfiguration: {
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
}));

test.done();
},

'NetworkLoadbalancedEC2Service accepts previously created load balancer'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down
Expand Up @@ -212,6 +212,7 @@ export = {
deploymentController: {
type: ecs.DeploymentControllerType.CODE_DEPLOY,
},
circuitBreaker: { rollback: true },
});

// THEN - QueueWorker is of EC2 launch type, an SQS queue is created and all optional properties are set.
Expand All @@ -220,6 +221,10 @@ export = {
DeploymentConfiguration: {
MinimumHealthyPercent: 60,
MaximumPercent: 150,
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
LaunchType: 'EC2',
ServiceName: 'ecs-test-service',
Expand Down
Expand Up @@ -427,6 +427,58 @@ export = {
test.done();
},

'setting ALB circuitBreaker works'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

// WHEN
new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', {
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'),
},
deploymentController: {
type: ecs.DeploymentControllerType.ECS,
},
circuitBreaker: { rollback: true },
});
// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
DeploymentConfiguration: {
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
}));
test.done();
},

'setting NLB circuitBreaker works'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

// WHEN
new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', {
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'),
},
deploymentController: {
type: ecs.DeploymentControllerType.ECS,
},
circuitBreaker: { rollback: true },
});
// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
DeploymentConfiguration: {
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
}));
test.done();
},

'setting NLB special listener port to create the listener'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down
Expand Up @@ -347,6 +347,7 @@ export = {
deploymentController: {
type: ecs.DeploymentControllerType.CODE_DEPLOY,
},
circuitBreaker: { rollback: true },
});

// THEN - QueueWorker is of FARGATE launch type, an SQS queue is created and all optional properties are set.
Expand All @@ -355,6 +356,10 @@ export = {
DeploymentConfiguration: {
MinimumHealthyPercent: 60,
MaximumPercent: 150,
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
LaunchType: 'FARGATE',
ServiceName: 'fargate-test-service',
Expand Down

0 comments on commit a335496

Please sign in to comment.