Skip to content

Commit

Permalink
Merge pull request #23 from hoangnd25/disable-autoscaling
Browse files Browse the repository at this point in the history
Service: expose scaling & allow default rules to be disabled
  • Loading branch information
fwang authored Jan 4, 2025
2 parents 07cf7db + eb668c5 commit 2dbfb3f
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 16 deletions.
6 changes: 6 additions & 0 deletions .changeset/tame-taxis-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"sst": patch
"@sst/docs": patch
---

Service: allow default rules to be disabled
42 changes: 26 additions & 16 deletions packages/sst/src/constructs/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
FargateTaskDefinition,
FargateServiceProps,
ICluster,
ScalableTaskCount,
} from "aws-cdk-lib/aws-ecs";
import { LogGroup, LogRetention, RetentionDays } from "aws-cdk-lib/aws-logs";
import { Platform } from "aws-cdk-lib/aws-ecr-assets";
Expand Down Expand Up @@ -284,7 +285,7 @@ export interface ServiceProps {
*/
maxContainers?: number;
/**
* Scales in or out to achieve a target cpu utilization.
* Scales in or out to achieve a target cpu utilization. Set to `false` to disable.
* @default 70
* @example
* ```js
Expand All @@ -296,9 +297,9 @@ export interface ServiceProps {
* }
*```
*/
cpuUtilization?: number;
cpuUtilization?: number | false;
/**
* Scales in or out to achieve a target memory utilization.
* Scales in or out to achieve a target memory utilization. Set to `false` to disable.
* @default 70
* @example
* ```js
Expand All @@ -310,9 +311,9 @@ export interface ServiceProps {
* }
*```
*/
memoryUtilization?: number;
memoryUtilization?: number | false;
/**
* Scales in or out to achieve a target request count per container.
* Scales in or out to achieve a target request count per container. Set to `false` to disable.
* @default 500
* @example
* ```js
Expand All @@ -323,7 +324,7 @@ export interface ServiceProps {
* }
*```
*/
requestsPerContainer?: number;
requestsPerContainer?: number | false;
};
/**
* Bind resources for the function
Expand Down Expand Up @@ -660,6 +661,7 @@ export class Service extends Construct implements SSTConstruct {
private service?: FargateService;
private distribution?: Distribution;
private alb?: ApplicationLoadBalancer;
private scaling?: ScalableTaskCount;

constructor(scope: Construct, id: string, props: ServiceProps) {
super(scope, id);
Expand Down Expand Up @@ -695,7 +697,7 @@ export class Service extends Construct implements SSTConstruct {
const cluster = this.createCluster(vpc);
const { container, taskDefinition, service } = this.createService(cluster);
const { alb, target } = this.createLoadBalancer(vpc, service);
this.createAutoScaling(service, target);
const scaling = this.createAutoScaling(service, target);
this.alb = alb;

// Create Distribution
Expand All @@ -706,6 +708,7 @@ export class Service extends Construct implements SSTConstruct {
this.service = service;
this.container = container;
this.taskDefinition = taskDefinition;
this.scaling = scaling;
this.bindForService(props?.bind || []);
this.attachPermissionsForService(props?.permissions || []);
Object.entries(props?.environment || {}).map(([key, value]) =>
Expand Down Expand Up @@ -784,6 +787,7 @@ export class Service extends Construct implements SSTConstruct {
applicationLoadBalancer: this.alb,
hostedZone: this.distribution?.cdk.hostedZone,
certificate: this.distribution?.cdk.certificate,
scaling: this.scaling,
};
}

Expand Down Expand Up @@ -1098,20 +1102,26 @@ export class Service extends Construct implements SSTConstruct {
minCapacity: minContainers ?? 1,
maxCapacity: maxContainers ?? 1,
});
scaling.scaleOnCpuUtilization("CpuScaling", {
targetUtilizationPercent: cpuUtilization ?? 70,
scaleOutCooldown: CdkDuration.seconds(300),
});
scaling.scaleOnMemoryUtilization("MemoryScaling", {
targetUtilizationPercent: memoryUtilization ?? 70,
scaleOutCooldown: CdkDuration.seconds(300),
});
if (target) {
if (cpuUtilization !== false) {
scaling.scaleOnCpuUtilization("CpuScaling", {
targetUtilizationPercent: cpuUtilization ?? 70,
scaleOutCooldown: CdkDuration.seconds(300),
});
}
if (memoryUtilization !== false) {
scaling.scaleOnMemoryUtilization("MemoryScaling", {
targetUtilizationPercent: memoryUtilization ?? 70,
scaleOutCooldown: CdkDuration.seconds(300),
});
}
if (target && requestsPerContainer !== false) {
scaling.scaleOnRequestCount("RequestScaling", {
requestsPerTarget: requestsPerContainer ?? 500,
targetGroup: target,
});
}

return scaling;
}

private createDistribution(alb?: ApplicationLoadBalancer) {
Expand Down
45 changes: 45 additions & 0 deletions packages/sst/test/constructs/Service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test("default", async () => {
expect(service.cdk?.distribution?.distributionDomainName).toBeDefined();
expect(service.cdk?.applicationLoadBalancer).toBeDefined();
expect(service.cdk?.certificate).toBeUndefined();
expect(service.cdk?.scaling).toBeDefined();
countResources(stack, "AWS::EC2::VPC", 1);
hasResource(stack, "AWS::EC2::VPC", {
CidrBlock: "10.0.0.0/16",
Expand Down Expand Up @@ -386,6 +387,20 @@ test("scaling.cpuUtilization defined", async () => {
}),
});
});
test("scaling.cpuUtilization false", async () => {
const { service, stack } = await createService({
scaling: {
cpuUtilization: false,
},
});
countResourcesLike(stack, "AWS::ApplicationAutoScaling::ScalingPolicy", 0, {
TargetTrackingScalingPolicyConfiguration: objectLike({
PredefinedMetricSpecification: {
PredefinedMetricType: "ECSServiceAverageCPUUtilization",
},
}),
});
});

test("scaling.memoryUtilization undefined", async () => {
const { service, stack } = await createService();
Expand Down Expand Up @@ -413,6 +428,20 @@ test("scaling.memoryUtilization defined", async () => {
}),
});
});
test("scaling.memoryUtilization false", async () => {
const { service, stack } = await createService({
scaling: {
memoryUtilization: false,
},
});
countResourcesLike(stack, "AWS::ApplicationAutoScaling::ScalingPolicy", 0, {
TargetTrackingScalingPolicyConfiguration: objectLike({
PredefinedMetricSpecification: {
PredefinedMetricType: "ECSServiceAverageMemoryUtilization",
},
}),
});
});

test("scaling.requestsPerContainer undefined", async () => {
const { service, stack } = await createService();
Expand Down Expand Up @@ -440,6 +469,20 @@ test("scaling.requestsPerContainer defined", async () => {
}),
});
});
test("scaling.requestsPerContainer false", async () => {
const { service, stack } = await createService({
scaling: {
requestsPerContainer: false,
},
});
countResourcesLike(stack, "AWS::ApplicationAutoScaling::ScalingPolicy", 0, {
TargetTrackingScalingPolicyConfiguration: objectLike({
PredefinedMetricSpecification: objectLike({
PredefinedMetricType: "ALBRequestCountPerTarget",
}),
}),
});
});

test("bind", async () => {
const { stack } = await createService((stack) => {
Expand Down Expand Up @@ -581,6 +624,7 @@ test("cdk.cloudfrontDistribution is false", async () => {
expect(service.cdk?.fargateService).toBeDefined();
expect(service.cdk?.taskDefinition).toBeDefined();
expect(service.cdk?.distribution).toBeUndefined();
expect(service.cdk?.scaling).toBeDefined();
});
test("cdk.cloudfrontDistribution is props", async () => {
const { stack, service } = await createService({
Expand Down Expand Up @@ -615,6 +659,7 @@ test("cdk.applicationLoadBalancer is false", async () => {
expect(service.cdk?.taskDefinition).toBeDefined();
expect(service.cdk?.distribution).toBeUndefined();
expect(service.cdk?.applicationLoadBalancer).toBeUndefined();
expect(service.cdk?.scaling).toBeDefined();
});
test("cdk.applicationLoadBalancer is props", async () => {
const { stack, service } = await createService({
Expand Down
27 changes: 27 additions & 0 deletions www/docs/constructs/Service.about.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,33 @@ new Service(stack, "MyService", {
### Advanced examples
#### Configuring Custom Scaling
Here's an example of disabling the default scaling rules.
```js
const service = new Service(stack, "MyService", {
scaling: {
minContainers: 4,
maxContainers: 16,
cpuUtilization: false, // disable default rules
memoryUtilization: false,
requestsPerContainer: false,
}
});
```
And add a custom rule to start the service at 9am Monday to Friday
```js
import { Schedule } from "aws-cdk-lib/aws-applicationautoscaling";

service.cdk?.scaling.scaleOnSchedule("StartOnSchedule", {
schedule: Schedule.expression(`cron(0 9 ? * MON-FRI *)`),
minCapacity: 1,
});
```
#### Configuring Fargate Service
Here's an example of configuring the circuit breaker for the Fargate service.
Expand Down
1 change: 1 addition & 0 deletions www/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const CDK_DOCS_MAP = {
HttpLambdaResponseType: "aws_apigatewayv2_authorizers",
HttpUserPoolAuthorizer: "aws_apigatewayv2_authorizers",
WebSocketLambdaAuthorizer: "aws_apigatewayv2_authorizers",
ScalableTaskCount: "aws_ecs",
ServerlessCluster: "aws_rds",
IServerlessCluster: "aws_rds",
Role: "aws_iam",
Expand Down

0 comments on commit 2dbfb3f

Please sign in to comment.