Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecs): ExternalService support daemon scheduling strategy #32630

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions packages/aws-cdk-lib/aws-ecs/lib/external/external-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export interface ExternalServiceProps extends BaseServiceOptions {
* @default - A new security group is created.
*/
readonly securityGroups?: ec2.ISecurityGroup[];

/**
* Specifies whether the service will use the daemon scheduling strategy.
* If true, the service scheduler deploys exactly one task on each container instance in your cluster.
*
* When you are using this strategy, do not specify a desired number of tasks or any task placement strategies.
*
* @default false
*/
readonly daemon?: boolean;
}

/**
Expand Down Expand Up @@ -89,6 +99,14 @@ export class ExternalService extends BaseService implements IExternalService {
* Constructs a new instance of the ExternalService class.
*/
constructor(scope: Construct, id: string, props: ExternalServiceProps) {
if (props.daemon && props.desiredCount !== undefined) {
throw new Error('Daemon mode launches one task on every instance. Don\'t supply desiredCount.');
}

if (props.daemon && props.maxHealthyPercent !== undefined && props.maxHealthyPercent !== 100) {
throw new Error('Maximum percent must be 100 for daemon mode.');
}

if (props.minHealthyPercent !== undefined && props.maxHealthyPercent !== undefined && props.minHealthyPercent >= props.maxHealthyPercent) {
throw new Error('Minimum healthy percent must be less than maximum healthy percent.');
}
Expand All @@ -114,15 +132,16 @@ export class ExternalService extends BaseService implements IExternalService {
super(scope, id, {
...props,
desiredCount: props.desiredCount,
maxHealthyPercent: props.maxHealthyPercent === undefined ? 100 : props.maxHealthyPercent,
minHealthyPercent: props.minHealthyPercent === undefined ? 0 : props.minHealthyPercent,
maxHealthyPercent: props.daemon || props.maxHealthyPercent === undefined ? 100 : props.maxHealthyPercent,
minHealthyPercent: props.daemon || props.minHealthyPercent === undefined ? 0 : props.minHealthyPercent,
launchType: LaunchType.EXTERNAL,
propagateTags: propagateTagsFromSource,
enableECSManagedTags: props.enableECSManagedTags,
},
{
cluster: props.cluster.clusterName,
taskDefinition: props.deploymentController?.type === DeploymentControllerType.EXTERNAL ? undefined : props.taskDefinition.taskDefinitionArn,
schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA',
}, props.taskDefinition);

this.node.addValidation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,85 @@ describe('external service', () => {

});

test('errors if daemon and desiredCount both specified', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
addDefaultCapacityProvider(cluster, stack, vpc);
const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef');
taskDefinition.addContainer('BaseContainer', {
image: ecs.ContainerImage.fromRegistry('test'),
memoryReservationMiB: 10,
});

// THEN
expect(() => {
new ecs.ExternalService(stack, 'ExternalService', {
cluster,
taskDefinition,
daemon: true,
desiredCount: 2,
});
}).toThrow(/Don't supply desiredCount/);

});

test('errors if daemon and maximumPercent not 100', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
addDefaultCapacityProvider(cluster, stack, vpc);
const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef');
taskDefinition.addContainer('BaseContainer', {
image: ecs.ContainerImage.fromRegistry('test'),
memoryReservationMiB: 10,
});

// THEN
expect(() => {
new ecs.ExternalService(stack, 'ExternalService', {
cluster,
taskDefinition,
daemon: true,
maxHealthyPercent: 300,
});
}).toThrow(/Maximum percent must be 100 for daemon mode./);

});

test('sets daemon scheduling strategy', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
addDefaultCapacityProvider(cluster, stack, vpc);
const taskDefinition = new ecs.ExternalTaskDefinition(stack, 'TaskDef');
taskDefinition.addContainer('BaseContainer', {
image: ecs.ContainerImage.fromRegistry('test'),
memoryReservationMiB: 10,
});

new ecs.ExternalService(stack, 'ExternalService', {
cluster,
taskDefinition,
daemon: true,
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', {
SchedulingStrategy: 'DAEMON',
DeploymentConfiguration: {
MaximumPercent: 100,
MinimumHealthyPercent: 0,
},
EnableECSManagedTags: false,
LaunchType: LaunchType.EXTERNAL,
});

});

test('errors if minimum not less than maximum', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down
Loading