Skip to content

Commit

Permalink
Merge branch 'huijbers/pipelines-cxapi-version' of github.com:aws/aws…
Browse files Browse the repository at this point in the history
…-cdk into huijbers/pipelines-cxapi-version
  • Loading branch information
rix0rrr committed Jan 13, 2022
2 parents 7305516 + b9a4f98 commit 84060e4
Show file tree
Hide file tree
Showing 54 changed files with 2,373 additions and 325 deletions.
38 changes: 37 additions & 1 deletion packages/@aws-cdk-containers/ecs-service-extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,43 @@ nameDescription.add(new Container({
Every `ServiceDescription` requires at minimum that you add a `Container` extension
which defines the main application (essential) container to run for the service.

After that, you can optionally enable additional features for the service using the `ServiceDescription.add()` method:
### Logging using `awslogs` log driver

If no observability extensions have been configured for a service, the ECS Service Extensions configures an `awslogs` log driver for the application container of the service to send the container logs to CloudWatch Logs.

You can either provide a log group to the `Container` extension or one will be created for you by the CDK.

Following is an example of an application with an `awslogs` log driver configured for the application container:

```ts
const environment = new Environment(stack, 'production');

const nameDescription = new ServiceDescription();
nameDescription.add(new Container({
cpu: 1024,
memoryMiB: 2048,
trafficPort: 80,
image: ContainerImage.fromRegistry('nathanpeck/name'),
environment: {
PORT: '80',
},
logGroup: new awslogs.LogGroup(stack, 'MyLogGroup'),
}));
```

If a log group is not provided, no observability extensions have been created, and the `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` feature flag is enabled, then logging will be configured by default and a log group will be created for you.

The `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` feature flag is enabled by default in any CDK apps that are created with CDK v1.140.0 or v2.8.0 and later.

To enable default logging for previous versions, ensure that the `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` flag within the application stack context is set to true, like so:

```ts
stack.node.setContext(cxapi.ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER, true);
```

Alternatively, you can also set the feature flag in the `cdk.json` file. For more information, refer the [docs](https://docs.aws.amazon.com/cdk/v2/guide/featureflags.html).

After adding the `Container` extension, you can optionally enable additional features for the service using the `ServiceDescription.add()` method:

```ts
nameDescription.add(new AppMeshExtension({ mesh }));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as ecs from '@aws-cdk/aws-ecs';
import * as awslogs from '@aws-cdk/aws-logs';
import * as cdk from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { Service } from '../service';
import { ServiceExtension } from './extension-interfaces';

Expand Down Expand Up @@ -38,6 +41,13 @@ export interface ContainerExtensionProps {
readonly environment?: {
[key: string]: string,
}

/**
* The log group into which application container logs should be routed.
*
* @default - A log group is automatically created for you if the `ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER` feature flag is set.
*/
readonly logGroup?: awslogs.ILogGroup;
}

/**
Expand All @@ -51,6 +61,11 @@ export class Container extends ServiceExtension {
*/
public readonly trafficPort: number;

/**
* The log group into which application container logs should be routed.
*/
public logGroup?: awslogs.ILogGroup;

/**
* The settings for the container.
*/
Expand All @@ -60,11 +75,12 @@ export class Container extends ServiceExtension {
super('service-container');
this.props = props;
this.trafficPort = props.trafficPort;
this.logGroup = props.logGroup;
}

// @ts-ignore - Ignore unused params that are required for abstract class extend
public prehook(service: Service, scope: Construct) {
this.parentService = service;
this.scope = scope;
}

// This hook sets the overall task resource requirements to the
Expand Down Expand Up @@ -93,6 +109,31 @@ export class Container extends ServiceExtension {
containerProps = hookProvider.mutateContainerDefinition(containerProps);
});

// If no observability extensions have been added to the service description then we can configure the `awslogs` log driver
if (!containerProps.logging) {
// Create a log group for the service if one is not provided by the user (only if feature flag is set)
if (!this.logGroup && this.parentService.node.tryGetContext(cxapi.ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER)) {
this.logGroup = new awslogs.LogGroup(this.scope, `${this.parentService.id}-logs`, {
logGroupName: `${this.parentService.id}-logs`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: awslogs.RetentionDays.ONE_MONTH,
});
}

if (this.logGroup) {
containerProps = {
...containerProps,
logging: new ecs.AwsLogDriver({
streamPrefix: this.parentService.id,
logGroup: this.logGroup,
}),
};
}
} else {
if (this.logGroup) {
throw Error(`Log configuration already specified. You cannot provide a log group for the application container of service '${this.parentService.id}' while also adding log configuration separately using service extensions.`);
}
}
this.container = taskDefinition.addContainer('app', containerProps);

// Create a port mapping for the container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import * as ecs from '@aws-cdk/aws-ecs';
import * as cdk from '@aws-cdk/core';
import { ServiceExtension, ServiceBuild } from './extension-interfaces';


/**
* The autoscaling settings.
*
* @deprecated use the `minTaskCount` and `maxTaskCount` properties of `autoScaleTaskCount` in the `Service` construct
* to configure the auto scaling target for the service. For more information, please refer
* https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk-containers/ecs-service-extensions/README.md#task-auto-scaling .
*/
export interface CpuScalingProps {
/**
Expand Down Expand Up @@ -61,6 +66,9 @@ const cpuScalingPropsDefault = {

/**
* This extension helps you scale your service according to CPU utilization.
*
* @deprecated To enable target tracking based on CPU utilization, use the `targetCpuUtilization` property of `autoScaleTaskCount` in the `Service` construct.
* For more information, please refer https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk-containers/ecs-service-extensions/README.md#task-auto-scaling .
*/
export class ScaleOnCpuUtilization extends ServiceExtension {
/**
Expand Down Expand Up @@ -126,6 +134,9 @@ export class ScaleOnCpuUtilization extends ServiceExtension {
// This hook utilizes the resulting service construct
// once it is created.
public useService(service: ecs.Ec2Service | ecs.FargateService) {
if (this.parentService.scalableTaskCount) {
throw Error('Cannot specify \'autoScaleTaskCount\' in the Service construct and also provide a \'ScaleOnCpuUtilization\' extension. \'ScaleOnCpuUtilization\' is deprecated. Please only provide \'autoScaleTaskCount\'.');
}
const scalingTarget = service.autoScaleTaskCount({
minCapacity: this.minTaskCount,
maxCapacity: this.maxTaskCount,
Expand All @@ -136,5 +147,6 @@ export class ScaleOnCpuUtilization extends ServiceExtension {
scaleInCooldown: this.scaleInCooldown,
scaleOutCooldown: this.scaleOutCooldown,
});
this.parentService.enableAutoScalingPolicy();
}
}
28 changes: 17 additions & 11 deletions packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export interface ServiceProps {

/**
* The options for configuring the auto scaling target.
*
* @default none
*/
readonly autoScaleTaskCount?: AutoScalingOptions;
}
Expand Down Expand Up @@ -196,7 +198,6 @@ export class Service extends Construct {
// Ensure that the task definition supports both EC2 and Fargate
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
} as ecs.TaskDefinitionProps;

for (const extensions in this.serviceDescription.extensions) {
if (this.serviceDescription.extensions[extensions]) {
taskDefProps = this.serviceDescription.extensions[extensions].modifyTaskDefinitionProps(taskDefProps);
Expand All @@ -221,17 +222,14 @@ export class Service extends Construct {
}
}

// Set desiredCount to `undefined` if auto scaling is configured for the service
const desiredCount = props.autoScaleTaskCount ? undefined : (props.desiredCount || 1);

// Give each extension a chance to mutate the service props before
// service creation
let serviceProps = {
cluster: this.cluster,
taskDefinition: this.taskDefinition,
minHealthyPercent: 100,
maxHealthyPercent: 200,
desiredCount,
desiredCount: props.desiredCount ?? 1,
} as ServiceBuild;

for (const extensions in this.serviceDescription.extensions) {
Expand Down Expand Up @@ -273,6 +271,14 @@ export class Service extends Construct {
}
}

// Set desiredCount to `undefined` if auto scaling is configured for the service
if (props.autoScaleTaskCount || this.autoScalingPoliciesEnabled) {
serviceProps = {
...serviceProps,
desiredCount: undefined,
};
}

// Now that the service props are determined we can create
// the service
if (this.capacityType === EnvironmentCapacityType.EC2) {
Expand All @@ -291,17 +297,17 @@ export class Service extends Construct {
});

if (props.autoScaleTaskCount.targetCpuUtilization) {
const targetUtilizationPercent = props.autoScaleTaskCount.targetCpuUtilization;
this.scalableTaskCount.scaleOnCpuUtilization(`${this.id}-target-cpu-utilization-${targetUtilizationPercent}`, {
targetUtilizationPercent,
const targetCpuUtilizationPercent = props.autoScaleTaskCount.targetCpuUtilization;
this.scalableTaskCount.scaleOnCpuUtilization(`${this.id}-target-cpu-utilization-${targetCpuUtilizationPercent}`, {
targetUtilizationPercent: targetCpuUtilizationPercent,
});
this.enableAutoScalingPolicy();
}

if (props.autoScaleTaskCount.targetMemoryUtilization) {
const targetUtilizationPercent = props.autoScaleTaskCount.targetMemoryUtilization;
this.scalableTaskCount.scaleOnMemoryUtilization(`${this.id}-target-memory-utilization-${targetUtilizationPercent}`, {
targetUtilizationPercent,
const targetMemoryUtilizationPercent = props.autoScaleTaskCount.targetMemoryUtilization;
this.scalableTaskCount.scaleOnMemoryUtilization(`${this.id}-target-memory-utilization-${targetMemoryUtilizationPercent}`, {
targetUtilizationPercent: targetMemoryUtilizationPercent,
});
this.enableAutoScalingPolicy();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@aws-cdk/aws-sqs": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"@aws-cdk/region-info": "0.0.0",
"constructs": "^3.3.69"
},
Expand Down Expand Up @@ -98,6 +99,7 @@
"@aws-cdk/aws-sqs": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"@aws-cdk/region-info": "0.0.0",
"constructs": "^3.3.69"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '@aws-cdk/assert-internal/jest';
import * as appmesh from '@aws-cdk/aws-appmesh';
import * as ecs from '@aws-cdk/aws-ecs';
import * as cdk from '@aws-cdk/core';
import { AppMeshExtension, Container, Environment, ScaleOnCpuUtilization, ServiceDescription, Service } from '../lib';
import { AppMeshExtension, Container, Environment, ServiceDescription, Service } from '../lib';

describe('appmesh', () => {
test('should be able to add AWS App Mesh to a service', () => {
Expand Down Expand Up @@ -33,7 +33,6 @@ describe('appmesh', () => {
});

// THEN

// Ensure that task has an App Mesh sidecar
expect(stack).toHaveResource('AWS::ECS::TaskDefinition', {
ContainerDefinitions: [
Expand Down Expand Up @@ -276,9 +275,6 @@ describe('appmesh', () => {
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));
serviceDescription.add(new ScaleOnCpuUtilization({
initialTaskCount: 1,
}));

const mesh = new appmesh.Mesh(stack, 'my-mesh');

Expand All @@ -289,6 +285,7 @@ describe('appmesh', () => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
desiredCount: 1,
});

expect(stack).toHaveResourceLike('AWS::ECS::Service', {
Expand Down Expand Up @@ -317,9 +314,6 @@ describe('appmesh', () => {
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));
serviceDescription.add(new ScaleOnCpuUtilization({
initialTaskCount: 2,
}));

const mesh = new appmesh.Mesh(stack, 'my-mesh');

Expand All @@ -330,6 +324,7 @@ describe('appmesh', () => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
desiredCount: 2,
});

expect(stack).toHaveResourceLike('AWS::ECS::Service', {
Expand Down Expand Up @@ -358,9 +353,6 @@ describe('appmesh', () => {
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));
serviceDescription.add(new ScaleOnCpuUtilization({
initialTaskCount: 3,
}));

const mesh = new appmesh.Mesh(stack, 'my-mesh');

Expand All @@ -371,6 +363,7 @@ describe('appmesh', () => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
desiredCount: 3,
});

expect(stack).toHaveResourceLike('AWS::ECS::Service', {
Expand Down Expand Up @@ -399,9 +392,6 @@ describe('appmesh', () => {
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));
serviceDescription.add(new ScaleOnCpuUtilization({
initialTaskCount: 4,
}));

const mesh = new appmesh.Mesh(stack, 'my-mesh');

Expand All @@ -412,6 +402,7 @@ describe('appmesh', () => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
desiredCount: 4,
});

expect(stack).toHaveResourceLike('AWS::ECS::Service', {
Expand Down Expand Up @@ -440,9 +431,6 @@ describe('appmesh', () => {
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));
serviceDescription.add(new ScaleOnCpuUtilization({
initialTaskCount: 8,
}));

const mesh = new appmesh.Mesh(stack, 'my-mesh');

Expand All @@ -453,6 +441,7 @@ describe('appmesh', () => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
desiredCount: 8,
});

expect(stack).toHaveResourceLike('AWS::ECS::Service', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ describe('cloudwatch agent', () => {
});

// THEN

expect(stack).toHaveResource('AWS::ECS::TaskDefinition', {
ContainerDefinitions: [
{
Expand Down
Loading

0 comments on commit 84060e4

Please sign in to comment.