diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index a1072d47fe700..c7a152b7b1535 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -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 @@ -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 diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index dde07a16e114c..2593853cd0350 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -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, @@ -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 { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 949b052aebbad..5872217a43f22 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -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'; @@ -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 { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts index 2f72c6345c469..7ec268c241249 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts @@ -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'; @@ -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; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index 2915fce6a48ff..ed606bb72c907 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -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); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts index 881a346c74f8a..4bae918ca67fc 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts @@ -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); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts index 6858813dfa9cc..985a4d2390e6b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts @@ -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); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index fbb68aef84b2f..326a68529272b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -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, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts index 404d5429acfed..1f2618bbca314 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts @@ -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); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts index 6444d05f81da6..cb4b8d77a8188 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts @@ -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); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index 6f5a4aa0b3337..5b6e03a78c70b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -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(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts index 2c410f6581b1e..8eaa0fd929030 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts @@ -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. @@ -220,6 +221,10 @@ export = { DeploymentConfiguration: { MinimumHealthyPercent: 60, MaximumPercent: 150, + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, }, LaunchType: 'EC2', ServiceName: 'ecs-test-service', diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index 1feffcb2e70fd..0c25cd479925f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -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(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts index 27671ae22e70d..bfd4a36654deb 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts @@ -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. @@ -355,6 +356,10 @@ export = { DeploymentConfiguration: { MinimumHealthyPercent: 60, MaximumPercent: 150, + DeploymentCircuitBreaker: { + Enable: true, + Rollback: true, + }, }, LaunchType: 'FARGATE', ServiceName: 'fargate-test-service',