Skip to content

Commit

Permalink
feat: add autoscaling for apprunner
Browse files Browse the repository at this point in the history
  • Loading branch information
mazyu36 committed May 28, 2024
1 parent ccbd99f commit 63731c3
Show file tree
Hide file tree
Showing 15 changed files with 942 additions and 4 deletions.
25 changes: 23 additions & 2 deletions packages/@aws-cdk/aws-apprunner-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The `Service` construct allows you to create AWS App Runner services with `ECR P
- `Source.fromEcr()` - To define the source repository from `ECR`.
- `Source.fromEcrPublic()` - To define the source repository from `ECR Public`.
- `Source.fromGitHub()` - To define the source repository from the `Github repository`.
- `Source.fromAsset()` - To define the source from local asset directory.
- `Source.fromAsset()` - To define the source from local asset directory.


The `Service` construct implements `IGrantable`.
Expand Down Expand Up @@ -154,6 +154,27 @@ when required.

See [App Runner IAM Roles](https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles) for more details.

## Auto Scaling Configuration

To associate an App Runner service with a custom Auto Scaling Configuration, define `autoScalingConfiguration` for the service.

```ts
const autoScalingConfiguration = new apprunner.AutoScalingConfiguration(this, 'AutoScalingConfiguration', {
autoScalingConfigurationName: 'MyAutoScalingConfiguration',
maxConcurrency: 150,
maxSize: 20,
minSize: 5,
});

new apprunner.Service(this, 'DemoService', {
source: apprunner.Source.fromEcrPublic({
imageConfiguration: { port: 8000 },
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
autoScalingConfiguration,
});
```

## VPC Connector

To associate an App Runner service with a custom VPC, define `vpcConnector` for the service.
Expand Down Expand Up @@ -183,7 +204,7 @@ new apprunner.Service(this, 'Service', {
## Secrets Manager

To include environment variables integrated with AWS Secrets Manager, use the `environmentSecrets` attribute.
You can use the `addSecret` method from the App Runner `Service` class to include secrets from outside the
You can use the `addSecret` method from the App Runner `Service` class to include secrets from outside the
service definition.

```ts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import { CfnAutoScalingConfiguration } from 'aws-cdk-lib/aws-apprunner';

/**
* Properties of the App Runner Auto Scaling Configuration.
*/
export interface AutoScalingConfigurationProps {
/**
* The name for the Auto Scaling Configuration.
*/
readonly autoScalingConfigurationName?: string;

/**
* The maximum number of concurrent requests that an instance processes.
* If the number of concurrent requests exceeds this limit, App Runner scales the service up.
*
* Must be between 1 and 25.
*
* @default 100
*/
readonly maxConcurrency?: number;

/**
* The maximum number of instances that a service scales up to.
* At most maxSize instances actively serve traffic for your service.
*
* Must be between 1 and 25.
*
* @default 1
*/
readonly maxSize?: number;

/**
* The minimum number of instances that App Runner provisions for a service.
* The service always has at least minSize provisioned instances.
*
* @default 25
*/
readonly minSize?: number;
}

/**
* Attributes for the App Runner Auto Scaling Configuration.
*/
export interface AutoScalingConfigurationAttributes {
/**
* The name of the Auto Scaling Configuration.
*/
readonly autoScalingConfigurationName: string;

/**
* The ARN of the Auto Scaling Configuration.
*/
readonly autoScalingConfigurationArn: string;

/**
* The revision of the Auto Scaling Configuration.
*/
readonly autoScalingConfigurationRevision: number;
}

/**
* Represents the App Runner Auto Scaling Configuration.
*/
export interface IAutoScalingConfiguration extends cdk.IResource {
/**
* The ARN of the Auto Scaling Configuration.
* @attribute
*/
readonly autoScalingConfigurationArn: string;

/**
* The Name of the Auto Scaling Configuration.
* @attribute
*/
readonly autoScalingConfigurationName: string;

/**
* The revision of the Auto Scaling Configuration.
* @attribute
*/
readonly autoScalingConfigurationRevision: number;
}

/**
* The App Runner Auto Scaling Configuration.
*
* @resource AWS::App Runner::AutoScalingConfiguration
*/
export class AutoScalingConfiguration extends cdk.Resource implements IAutoScalingConfiguration {
/**
* Import from Auto Scaling Configuration attributes.
*/
public static fromAutoScalingConfigurationAttributes(scope: Construct, id: string,
attrs: AutoScalingConfigurationAttributes): IAutoScalingConfiguration {
const autoScalingConfigurationArn = attrs.autoScalingConfigurationArn;
const autoScalingConfigurationName = attrs.autoScalingConfigurationName;
const autoScalingConfigurationRevision = attrs.autoScalingConfigurationRevision;

class Import extends cdk.Resource {
public readonly autoScalingConfigurationArn = autoScalingConfigurationArn
public readonly autoScalingConfigurationName = autoScalingConfigurationName
public readonly autoScalingConfigurationRevision = autoScalingConfigurationRevision
}

return new Import(scope, id);
}

/**
* The ARN of the Auto Scaling Configuration.
* @attribute
*/
readonly autoScalingConfigurationArn: string;

/**
* The name of the Auto Scaling Configuration.
* @attribute
*/
readonly autoScalingConfigurationName: string;

/**
* The revision of the Auto Scaling Configuration.
* @attribute
*/
readonly autoScalingConfigurationRevision: number;

public constructor(scope: Construct, id: string, props: AutoScalingConfigurationProps) {
super(scope, id, {
physicalName: props.autoScalingConfigurationName,
});

if (props.minSize !== undefined && (props.minSize < 1 || props.minSize > 25)) {
throw new Error(`minSize must be between 1 and 25, got ${props.minSize}`);
}
if (props.maxSize !== undefined && (props.maxSize < 1 || props.maxSize > 25)) {
throw new Error(`maxSize must be between 1 and 25, got ${props.maxSize}`);
}
if (props.minSize !== undefined && props.maxSize !== undefined && !(props.minSize < props.maxSize)) {
throw new Error('maxSize must be greater than minSize');
}
if (props.maxConcurrency !== undefined && (props.maxConcurrency < 1 || props.maxConcurrency > 200)) {
throw new Error(`maxConcurrency must be between 1 and 200, got ${props.maxConcurrency}`);
}

const resource = new CfnAutoScalingConfiguration(this, 'Resource', {
autoScalingConfigurationName: props.autoScalingConfigurationName,
maxConcurrency: props.maxConcurrency,
maxSize: props.maxSize,
minSize: props.minSize,
});

this.autoScalingConfigurationArn = resource.attrAutoScalingConfigurationArn;
this.autoScalingConfigurationRevision = resource.attrAutoScalingConfigurationRevision;
this.autoScalingConfigurationName = resource.ref;
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apprunner-alpha/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// AWS::AppRunner CloudFormation Resources:
export * from './auto-scaling-configuration';
export * from './service';
export * from './vpc-connector';
16 changes: 15 additions & 1 deletion packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Lazy } from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import { CfnService } from 'aws-cdk-lib/aws-apprunner';
import { IVpcConnector } from './vpc-connector';
import { AutoScalingConfiguration } from './auto-scaling-configuration';

/**
* The image repository types
Expand Down Expand Up @@ -79,7 +80,7 @@ export class Cpu {
*
* @param unit The unit of CPU.
*/
private constructor(public readonly unit: string) {}
private constructor(public readonly unit: string) { }
}

/**
Expand Down Expand Up @@ -655,6 +656,18 @@ export interface ServiceProps {
*/
readonly autoDeploymentsEnabled?: boolean;

/**
* Specifies an App Runner Auto Scaling Configuration.
*
* A default configuration is either the AWS recommended configuration,
* or the configuration you set as the default.
*
* @see https://docs.aws.amazon.com/apprunner/latest/dg/manage-autoscaling.html
*
* @default - use a default Auto Scaling Configuration.
*/
readonly autoScalingConfiguration?: AutoScalingConfiguration;

/**
* The number of CPU units reserved for each instance of your App Runner service.
*
Expand Down Expand Up @@ -1239,6 +1252,7 @@ export class Service extends cdk.Resource implements iam.IGrantable {
this.renderCodeConfiguration(this.source.codeRepository!.codeConfiguration.configurationValues!) :
undefined,
},
autoScalingConfigurationArn: this.props.autoScalingConfiguration?.autoScalingConfigurationArn,
networkConfiguration: {
egressConfiguration: {
egressType: this.props.vpcConnector ? 'VPC' : 'DEFAULT',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Match, Template } from 'aws-cdk-lib/assertions';
import * as cdk from 'aws-cdk-lib';
import { AutoScalingConfiguration } from '../lib';

let stack: cdk.Stack;
beforeEach(() => {
stack = new cdk.Stack();
});

test('create an Auto scaling Configuration with all properties', () => {
// WHEN
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
autoScalingConfigurationName: 'MyAutoScalingConfiguration',
maxConcurrency: 150,
maxSize: 20,
minSize: 5,
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::AutoScalingConfiguration', {
AutoScalingConfigurationName: 'MyAutoScalingConfiguration',
MaxConcurrency: 150,
MaxSize: 20,
MinSize: 5,
});
});

test('create an Auto scaling Configuration without all properties', () => {
// WHEN
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::AutoScalingConfiguration', {
AutoScalingConfigurationName: Match.absent(),
MaxConcurrency: Match.absent(),
MaxSize: Match.absent(),
MinSize: Match.absent(),
});
});

test.each([0, 26])('invalid minSize', (minSize: number) => {
expect(() => {
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
minSize,
});
}).toThrow(`minSize must be between 1 and 25, got ${minSize}`);
});

test.each([0, 26])('invalid maxSize', (maxSize: number) => {
expect(() => {
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
maxSize,
});
}).toThrow(`maxSize must be between 1 and 25, got ${maxSize}`);
});

test('minSize greater than maxSize', () => {
expect(() => {
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
minSize: 5,
maxSize: 3,
});
}).toThrow('maxSize must be greater than minSize');
});

test.each([0, 201])('invalid maxConcurrency', (maxConcurrency: number) => {
expect(() => {
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
maxConcurrency,
});
}).toThrow(`maxConcurrency must be between 1 and 200, got ${maxConcurrency}`);
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 63731c3

Please sign in to comment.