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 Jan 27, 2021
1 parent 126a693 commit 619fad5
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 3 deletions.
23 changes: 23 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/README.md
Expand Up @@ -393,6 +393,29 @@ 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

```ts
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 @@ -224,6 +227,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 @@ -174,6 +177,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 { Construct } from 'constructs';
Expand Down Expand Up @@ -178,6 +181,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 @@ -132,6 +132,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 @@ -130,6 +130,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 @@ -110,6 +110,7 @@ export class QueueProcessingEc2Service extends QueueProcessingServiceBase {
propagateTags: props.propagateTags,
enableECSManagedTags: props.enableECSManagedTags,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
});
this.configureAutoscalingForService(this.service);
this.grantPermissionsToService(this.service);
Expand Down
Expand Up @@ -167,6 +167,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 @@ -156,6 +156,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 @@ -117,6 +117,7 @@ export class QueueProcessingFargateService extends QueueProcessingServiceBase {
enableECSManagedTags: props.enableECSManagedTags,
platformVersion: props.platformVersion,
deploymentController: props.deploymentController,
circuitBreaker: props.circuitBreaker,
});
this.configureAutoscalingForService(this.service);
this.grantPermissionsToService(this.service);
Expand Down
66 changes: 66 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts
Expand Up @@ -1001,6 +1001,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 @@ -186,6 +186,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 @@ -194,6 +195,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 @@ -231,6 +231,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 @@ -239,6 +240,10 @@ export = {
DeploymentConfiguration: {
MinimumHealthyPercent: 60,
MaximumPercent: 150,
DeploymentCircuitBreaker: {
Enable: true,
Rollback: true,
},
},
LaunchType: 'FARGATE',
ServiceName: 'fargate-test-service',
Expand Down

0 comments on commit 619fad5

Please sign in to comment.