Skip to content

Commit

Permalink
feat(ecs): warning when creating a service with the default minHealth…
Browse files Browse the repository at this point in the history
…yPercent (#31738)

### Issue

Closes #31705

### Reason for this change

CDK overrides the default value of 100% used by CloudFormation's `AWS::ECS::Service` and the `CreateService` API. This allows the number of running tasks to drop by up to 50% during deployments and [Fargate maintenance](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-maintenance.html) etc.

This is an unsafe default for services which must support a consistent load e.g. handle web traffic via an ALB.

### Description of changes

A warning appears when the default value is implicitly used. CloudFormation's default is overridden in three different places so multiple warnings are added:
* `BaseService` emits `@aws-cdk/aws-ecs:minHealthyPercent` when `minHealthyPercent` is `undefined`
* `Ec2Service` emits `@aws-cdk/aws-ecs:minHealthyPercentDaemon` when `daemon` is `true` and `minHealthyPercent` is `undefined` 
* `ExternalService` emits `@aws-cdk/aws-ecs:minHealthyPercentExternal` when `minHealthyPercent` is `undefined`

At most one warning appears due to the way the CloudFormation's default is overidden.

`README.md` has been updated for `aws_ecs` and `aws_ecs_patterns` to ensure these examples don't trigger this warning.

### Description of how you validated changes
Unit tests have been added to ensure the warnings appear are there when they should be and not when they shouldn't.

### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
MarrickLip authored Jan 7, 2025
1 parent c57debb commit 3606deb
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 9 deletions.
34 changes: 34 additions & 0 deletions packages/aws-cdk-lib/aws-ecs-patterns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const loadBalancedEcsService = new ecsPatterns.ApplicationLoadBalancedEc2Service
entryPoint: ['entry', 'point'],
},
desiredCount: 2,
minHealthyPercent: 100,
});
```

Expand All @@ -46,6 +47,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
command: ['command'],
entryPoint: ['entry', 'point'],
},
minHealthyPercent: 100,
});

loadBalancedFargateService.targetGroup.configureHealthCheck({
Expand Down Expand Up @@ -142,6 +144,7 @@ const loadBalancedEcsService = new ecsPatterns.NetworkLoadBalancedEc2Service(thi
},
},
desiredCount: 2,
minHealthyPercent: 100,
});
```

Expand All @@ -156,6 +159,7 @@ const loadBalancedFargateService = new ecsPatterns.NetworkLoadBalancedFargateSer
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
});
```

Expand Down Expand Up @@ -272,6 +276,7 @@ const queueProcessingEc2Service = new ecsPatterns.QueueProcessingEc2Service(this
},
maxScalingCapacity: 5,
containerName: 'test',
minHealthyPercent: 100,
});
```

Expand All @@ -292,6 +297,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
},
maxScalingCapacity: 5,
containerName: 'test',
minHealthyPercent: 100,
});
```

Expand All @@ -314,6 +320,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
},
maxScalingCapacity: 5,
containerName: 'test',
minHealthyPercent: 100,
disableCpuBasedScaling: true,
});
```
Expand All @@ -332,6 +339,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
environment: {},
maxScalingCapacity: 5,
containerName: 'test',
minHealthyPercent: 100,
cpuTargetUtilizationPercent: 90,
});
```
Expand Down Expand Up @@ -392,6 +400,7 @@ declare const cluster: ecs.Cluster;
const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
vpc,
cluster,
minHealthyPercent: 100,
certificate,
sslPolicy: SslPolicy.RECOMMENDED,
domainName: 'api.example.com',
Expand All @@ -414,6 +423,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
capacityProviderStrategies: [
{
capacityProvider: 'FARGATE_SPOT',
Expand Down Expand Up @@ -441,6 +451,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
});

const scalableTarget = loadBalancedFargateService.service.autoScaleTaskCount({
Expand Down Expand Up @@ -471,6 +482,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
});

const scalableTarget = loadBalancedFargateService.service.autoScaleTaskCount({
Expand Down Expand Up @@ -499,6 +511,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
deploymentController: {
type: ecs.DeploymentControllerType.CODE_DEPLOY,
},
Expand All @@ -522,6 +535,7 @@ const service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Ser
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
circuitBreaker: { rollback: true },
});
```
Expand Down Expand Up @@ -553,6 +567,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
vpc,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
securityGroups: [securityGroup],
taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
});
Expand All @@ -566,6 +581,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
vpc,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
assignPublicIp: true,
});
```
Expand All @@ -578,6 +594,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
vpc,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
maxReceiveCount: 42,
retentionPeriod: Duration.days(7),
visibilityTimeout: Duration.minutes(5),
Expand All @@ -595,6 +612,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
vpc,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
assignPublicIp: true,
cooldown: Duration.seconds(500),
});
Expand All @@ -610,6 +628,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
cluster,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
capacityProviderStrategies: [
{
capacityProvider: 'FARGATE_SPOT',
Expand All @@ -632,6 +651,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ
vpc,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
healthCheck: {
command: [ "CMD-SHELL", "curl -f http://localhost/ || exit 1" ],
// the properties below are optional
Expand Down Expand Up @@ -664,6 +684,7 @@ const queueProcessingEc2Service = new ecsPatterns.QueueProcessingEc2Service(this
cluster,
memoryLimitMiB: 512,
image: ecs.ContainerImage.fromRegistry('test'),
minHealthyPercent: 100,
capacityProviderStrategies: [
{
capacityProvider: capacityProvider.capacityProviderName,
Expand All @@ -684,6 +705,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
taskSubnets: {
subnets: [ec2.Subnet.fromSubnetId(this, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')],
},
Expand All @@ -702,6 +724,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
idleTimeout: Duration.seconds(120),
});
```
Expand Down Expand Up @@ -881,6 +904,7 @@ const applicationLoadBalancedFargateService = new ecsPatterns.ApplicationLoadBal
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
runtimePlatform: {
cpuArchitecture: ecs.CpuArchitecture.ARM64,
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
Expand Down Expand Up @@ -966,6 +990,7 @@ const service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Ser
cluster,
vpc,
desiredCount: 1,
minHealthyPercent: 100,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
dockerLabels: {
Expand All @@ -992,6 +1017,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
taskSubnets: {
subnets: [ec2.Subnet.fromSubnetId(this, 'subnet', 'VpcISOLATEDSubnet1Subnet80F07FA0')],
},
Expand Down Expand Up @@ -1020,6 +1046,7 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
},
minHealthyPercent: 100,
enableExecuteCommand: true
});
```
Expand Down Expand Up @@ -1095,6 +1122,7 @@ const applicationLoadBalancedFargateService = new ecsPatterns.ApplicationLoadBal
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
});

const networkLoadBalancedFargateService = new ecsPatterns.NetworkLoadBalancedFargateService(this, 'NLBFargateServiceWithCustomEphemeralStorage', {
Expand All @@ -1105,6 +1133,7 @@ const networkLoadBalancedFargateService = new ecsPatterns.NetworkLoadBalancedFar
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
});
```

Expand All @@ -1119,6 +1148,7 @@ const queueProcessingFargateService = new ecsPatterns.NetworkLoadBalancedFargate
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
securityGroups: [securityGroup],
});
```
Expand Down Expand Up @@ -1195,6 +1225,7 @@ const service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'myS
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
ipAddressType: elbv2.IpAddressType.DUAL_STACK,
});

Expand All @@ -1203,6 +1234,7 @@ const applicationLoadBalancedEc2Service = new ecsPatterns.ApplicationLoadBalance
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
ipAddressType: elbv2.IpAddressType.DUAL_STACK,
});
```
Expand All @@ -1225,6 +1257,7 @@ const networkLoadbalancedFargateService = new ecsPatterns.NetworkLoadBalancedFar
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
ipAddressType: elbv2.IpAddressType.DUAL_STACK,
});

Expand All @@ -1233,6 +1266,7 @@ const networkLoadbalancedEc2Service = new ecsPatterns.NetworkLoadBalancedEc2Serv
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
minHealthyPercent: 100,
ipAddressType: elbv2.IpAddressType.DUAL_STACK,
});
```
Loading

0 comments on commit 3606deb

Please sign in to comment.