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(codedeploy) - Added ECS CodeDeploy support #22081

Closed
wants to merge 2 commits into from
Closed
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
70 changes: 70 additions & 0 deletions packages/@aws-cdk/aws-codedeploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,73 @@ const deploymentGroup = codedeploy.LambdaDeploymentGroup.fromLambdaDeploymentGro
deploymentGroupName: 'MyExistingDeploymentGroup',
});
```

## ECS Applications

To create a new CodeDeploy Application that deploys to an ECS service:

```ts
const application = new codedeploy.EcsApplication(this, 'CodeDeployApplication', {
applicationName: 'MyApplication', // optional property
});
```

To import an already existing Application:

```ts
const application = codedeploy.EcsApplication.fromEcsApplicationName(
this,
'ExistingCodeDeployApplication',
'MyExistingApplication',
);
```

## ECS Deployment Groups

To enable traffic shifting deployments for ECS Services, CodeDeploy uses TaskSets and TargetGroups, which can balance incoming traffic between two different versions of your function. Before deployment, the listener sends 100% of requests to the production target group. When you publish a new version of the task definition to your stack, CodeDeploy will send a small percentage of traffic to the new task sets, monitor, and validate before shifting 100% of traffic to the new version.

To create a new CodeDeploy Deployment Group that deploys to a ECS service:

```ts
declare const myApplication: codedeploy.EcsApplication;
declare const service: ecs.FargateService;
declare const targetGroup = elbv2.ITargetGroup;
declare const listener = elbv2.IApplicationListener;

const deploymentGroup = new codedeploy.EcsDeploymentGroup(this, 'BlueGreenDeployment', {
application: myApplication, // optional property: one will be created for you if not provided
services: [
service
],
prodTrafficRoute: {
listener,
targetGroup,
}
deploymentConfig: codedeploy.EcsDeploymentConfig.CANARY_10_PERCENT_5_MINUTES,
});
```

### Create a custom ECS Deployment Config

CodeDeploy for ECS comes with built-in configurations for traffic shifting.
If you want to specify your own strategy,
you can do so with the EcsDeploymentConfig construct,
letting you specify precisely how fast a new function version is deployed.

```ts
const config = new codedeploy.EcsDeploymentConfig(this, 'CustomConfig', {
trafficRoutingConfig: new codeDeploy.TimeBasedCanaryTrafficRoutingConfig(5, 10);
});
```

### Import an existing ECS Deployment Group

To import an already existing Deployment Group:

```ts
declare const application: codedeploy.EcsApplication;
const deploymentGroup = codedeploy.EcsDeploymentGroup.fromEcsDeploymentGroupAttributes(this, 'ExistingCodeDeployDeploymentGroup', {
application,
deploymentGroupName: 'MyExistingDeploymentGroup',
});
```
78 changes: 63 additions & 15 deletions packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-config.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,93 @@
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { arnForDeploymentConfig } from '../utils';
import { CfnDeploymentConfig } from '../codedeploy.generated';
import { ITrafficRoutingConfig } from '../traffic-routing-config';
import { arnForDeploymentConfig, validateName } from '../utils';

/**
* The Deployment Configuration of an ECS Deployment Group.
* The default, pre-defined Configurations are available as constants on the {@link EcsDeploymentConfig} class
* (for example, `EcsDeploymentConfig.AllAtOnce`).
*
* Note: CloudFormation does not currently support creating custom ECS configs outside
* of using a custom resource. You can import custom deployment config created outside the
* CDK or via a custom resource with {@link EcsDeploymentConfig#fromEcsDeploymentConfigName}.
* To create a custom Deployment Configuration,
* instantiate the {@link EcsDeploymentConfig} Construct.
*/
export interface IEcsDeploymentConfig {
/**
* @attribute
*/
readonly deploymentConfigName: string;
/**
* @attribute
*/
readonly deploymentConfigArn: string;
}

/**
* Construction properties of {@link EcsDeploymentConfig}.
*/
export interface EcsDeploymentConfigProps {
/**
* The physical, human-readable name of the Deployment Configuration.
*
* @default a name will be auto-generated
*/
readonly deploymentConfigName?: string;

/**
* The configuration that specifies how traffic is shifed from one
* Amazon ECS task set to another during an Amazon ECS deployment.
*
* @default a name will be auto-generated
*/
readonly trafficRoutingConfig?: ITrafficRoutingConfig;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a related open PR that creates LambdaDeploymentConfig class, and we should make sure the Props structures are consistent. I chose to copy over the structure that LambdaDeploymentConfig has, where percentage, interval, etc are in the root Props structure:
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_codedeploy.CustomLambdaDeploymentConfig.html

I'm not super opinionated either way if you feel like this is a better structure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I preferred the pattern in the ServerDeploymentConfig. I worry about the pattern in LambdaDeploymentConfig assumes any potential future types would have a similar shape (type + percentage + interval). Seems if a future deployment config type didn't look like that, then it would be hard to extend the current implementation.

I may be overthinking though 🤷‍♂️

cplee marked this conversation as resolved.
Show resolved Hide resolved

}

/**
* A custom Deployment Configuration for an ECS Deployment Group.
*
* Note: This class currently stands as namespaced container of the default configurations
* until CloudFormation supports custom ECS Deployment Configs. Until then it is closed
* (private constructor) and does not extend {@link Construct}
*
* @resource AWS::CodeDeploy::DeploymentConfig
*/
export class EcsDeploymentConfig {
export class EcsDeploymentConfig extends cdk.Resource implements IEcsDeploymentConfig {
public static readonly ALL_AT_ONCE = deploymentConfig('CodeDeployDefault.ECSAllAtOnce');
public static readonly LINEAR_10_PERCENT_1_MINUTES = deploymentConfig('CodeDeployDefault.ECSLinear10PercentEvery1Minutes');
public static readonly LINEAR_10_PERCENT_3_MINUTES = deploymentConfig('CodeDeployDefault.ECSLinear10PercentEvery3Minutes');
public static readonly CANARY_10_PERCENT_5_MINUTES = deploymentConfig('CodeDeployDefault.ECSCanary10Percent5Minutes');
public static readonly CANARY_10_PERCENT_15_MINUTES = deploymentConfig('CodeDeployDefault.ECSCanary10Percent15Minutes');

/**
* Import a custom Deployment Configuration for an ECS Deployment Group defined outside the CDK.
*
* @param _scope the parent Construct for this new Construct
* @param _id the logical ID of this new Construct
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param ecsDeploymentConfigName the name of the referenced custom Deployment Configuration
* @returns a Construct representing a reference to an existing custom Deployment Configuration
*/
public static fromEcsDeploymentConfigName(_scope: Construct, _id: string, ecsDeploymentConfigName: string): IEcsDeploymentConfig {
public static fromEcsDeploymentConfigName(scope: Construct, id: string, ecsDeploymentConfigName: string): IEcsDeploymentConfig {
ignore(scope);
ignore(id);
return deploymentConfig(ecsDeploymentConfigName);
}

private constructor() {
// nothing to do until CFN supports custom ECS deployment configurations
public readonly deploymentConfigName: string;
public readonly deploymentConfigArn: string;

constructor(scope: Construct, id: string, props: EcsDeploymentConfigProps = {}) {
super(scope, id, {
physicalName: props.deploymentConfigName,
});
const resource = new CfnDeploymentConfig(this, 'Resource', {
computePlatform: 'ECS',
deploymentConfigName: this.physicalName,
trafficRoutingConfig: props.trafficRoutingConfig?.renderTrafficRoutingConfig(),
});

this.deploymentConfigName = resource.ref;
this.deploymentConfigArn = arnForDeploymentConfig(this.deploymentConfigName);

this.node.addValidation({ validate: () => validateName('Deployment config', this.physicalName) });
}

}

function deploymentConfig(name: string): IEcsDeploymentConfig {
Expand All @@ -50,3 +96,5 @@ function deploymentConfig(name: string): IEcsDeploymentConfig {
deploymentConfigArn: arnForDeploymentConfig(name),
};
}

function ignore(_x: any) { return; }
Loading