Skip to content

Commit

Permalink
feat(ecs): support pidMode for FargateTaskDefinition (#29670)
Browse files Browse the repository at this point in the history
### Issue # (if applicable)

Closes #29619.

### Reason for this change

Support [`pidMode`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-pidmode) for `FargateTaskDefinition`.

### Description of changes

Added support for the `pidMode` property along with the necessary validation, documentation, and test coverage.

### Description of how you validated changes

- [x] Unit tests
- [x] Integration tests

### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
lpizzinidev committed Apr 22, 2024
1 parent 27b7a45 commit ed75b16
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@
"Family": "awsecsintegruntimeTaskDefGraviton28E28B263",
"Memory": "1024",
"NetworkMode": "awsvpc",
"PidMode": "host",
"RequiresCompatibilities": [
"FARGATE"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const taskDefinitiongraviton2 = new ecs.FargateTaskDefinition(stack, 'TaskDefGra
},
cpu: 256,
memoryLimitMiB: 1024,
pidMode: ecs.PidMode.HOST,
});

taskDefinitionwindows.addContainer('windowsservercore', {
Expand Down
17 changes: 17 additions & 0 deletions packages/aws-cdk-lib/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,23 @@ const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
});
```

To specify the process namespace to use for the containers in the task, use the `pidMode` property:

```ts
const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
runtimePlatform: {
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
cpuArchitecture: ecs.CpuArchitecture.ARM64,
},
memoryLimitMiB: 512,
cpu: 256,
pidMode: ecs.PidMode.HOST,
});
```

**Note:** `pidMode` is only supported for tasks that are hosted on AWS Fargate if the tasks are using platform version 1.4.0
or later (Linux). This isn't supported for Windows containers on Fargate.

To add containers to a task definition, call `addContainer()`:

```ts
Expand Down
20 changes: 16 additions & 4 deletions packages/aws-cdk-lib/aws-ecs/lib/base/task-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps {
/**
* The process namespace to use for the containers in the task.
*
* Not supported in Fargate and Windows containers.
* Only supported for tasks that are hosted on AWS Fargate if the tasks
* are using platform version 1.4.0 or later (Linux).
* Not supported in Windows containers.
*
* @default - PidMode used by the task is not specified
*/
Expand All @@ -219,8 +221,8 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps {

/**
* The operating system that your task definitions are running on.
* A runtimePlatform is supported only for tasks using the Fargate launch type.
*
* A runtimePlatform is supported only for tasks using the Fargate launch type.
*
* @default - Undefined.
*/
Expand Down Expand Up @@ -372,6 +374,15 @@ export class TaskDefinition extends TaskDefinitionBase {
*/
public readonly ephemeralStorageGiB?: number;

/**
* The process namespace to use for the containers in the task.
*
* Only supported for tasks that are hosted on AWS Fargate if the tasks
* are using platform version 1.4.0 or later (Linux).
* Not supported in Windows containers.
*/
public readonly pidMode?: PidMode;

/**
* The container definitions.
*/
Expand Down Expand Up @@ -453,9 +464,10 @@ export class TaskDefinition extends TaskDefinitionBase {
}

this.ephemeralStorageGiB = props.ephemeralStorageGiB;
this.pidMode = props.pidMode;

// validate the cpu and memory size for the Windows operation system family.
if (props.runtimePlatform?.operatingSystemFamily?._operatingSystemFamily.includes('WINDOWS')) {
if (props.runtimePlatform?.operatingSystemFamily?.isWindows()) {
// We know that props.cpu and props.memoryMiB are defined because an error would have been thrown previously if they were not.
// But, typescript is not able to figure this out, so using the `!` operator here to let the type-checker know they are defined.
this.checkFargateWindowsBasedTasksSize(props.cpu!, props.memoryMiB!, props.runtimePlatform!);
Expand Down Expand Up @@ -485,7 +497,7 @@ export class TaskDefinition extends TaskDefinitionBase {
cpu: props.cpu,
memory: props.memoryMiB,
ipcMode: props.ipcMode,
pidMode: props.pidMode,
pidMode: this.pidMode,
inferenceAccelerators: Lazy.any({
produce: () =>
!isFargateCompatible(this.compatibility) ? this.renderInferenceAccelerators() : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export interface Ec2TaskDefinitionProps extends CommonTaskDefinitionProps {
/**
* The process namespace to use for the containers in the task.
*
* Not supported in Fargate and Windows containers.
* Not supported in Windows containers.
*
* @default - PidMode used by the task is not specified
*/
Expand Down
28 changes: 18 additions & 10 deletions packages/aws-cdk-lib/aws-ecs/lib/fargate/fargate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ export class FargateService extends BaseService implements IFargateService {
throw new Error('Only one of SecurityGroup or SecurityGroups can be populated.');
}

// Platform versions not supporting referencesSecretJsonField, ephemeralStorageGiB, or pidMode on a task definition
const unsupportedPlatformVersions = [
FargatePlatformVersion.VERSION1_0,
FargatePlatformVersion.VERSION1_1,
FargatePlatformVersion.VERSION1_2,
FargatePlatformVersion.VERSION1_3,
];
const isUnsupportedPlatformVersion = props.platformVersion && unsupportedPlatformVersions.includes(props.platformVersion);

if (props.taskDefinition.ephemeralStorageGiB && isUnsupportedPlatformVersion) {
throw new Error(`The ephemeralStorageGiB feature requires platform version ${FargatePlatformVersion.VERSION1_4} or later, got ${props.platformVersion}.`);
}

if (props.taskDefinition.pidMode && isUnsupportedPlatformVersion) {
throw new Error(`The pidMode feature requires platform version ${FargatePlatformVersion.VERSION1_4} or later, got ${props.platformVersion}.`);
}

super(scope, id, {
...props,
desiredCount: props.desiredCount,
Expand All @@ -153,9 +170,7 @@ export class FargateService extends BaseService implements IFargateService {
}

this.node.addValidation({
validate: () => this.taskDefinition.referencesSecretJsonField
&& props.platformVersion
&& SECRET_JSON_FIELD_UNSUPPORTED_PLATFORM_VERSIONS.includes(props.platformVersion)
validate: () => this.taskDefinition.referencesSecretJsonField && isUnsupportedPlatformVersion
? [`The task definition of this service uses at least one container that references a secret JSON field. This feature requires platform version ${FargatePlatformVersion.VERSION1_4} or later.`]
: [],
});
Expand Down Expand Up @@ -214,10 +229,3 @@ export enum FargatePlatformVersion {
*/
VERSION1_0 = '1.0.0',
}

const SECRET_JSON_FIELD_UNSUPPORTED_PLATFORM_VERSIONS = [
FargatePlatformVersion.VERSION1_0,
FargatePlatformVersion.VERSION1_1,
FargatePlatformVersion.VERSION1_2,
FargatePlatformVersion.VERSION1_3,
];
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Compatibility,
ITaskDefinition,
NetworkMode,
PidMode,
TaskDefinition,
} from '../base/task-definition';
import { RuntimePlatform } from '../runtime-platform';
Expand Down Expand Up @@ -77,6 +78,17 @@ export interface FargateTaskDefinitionProps extends CommonTaskDefinitionProps {
* @default - Undefined.
*/
readonly runtimePlatform?: RuntimePlatform;

/**
* The process namespace to use for the containers in the task.
*
* Only supported for tasks that are hosted on AWS Fargate if the tasks
* are using platform version 1.4.0 or later (Linux).
* Not supported in Windows containers.
*
* @default - PidMode used by the task is not specified
*/
readonly pidMode?: PidMode;
}

/**
Expand Down Expand Up @@ -147,13 +159,23 @@ export class FargateTaskDefinition extends TaskDefinition implements IFargateTas
memoryMiB: props.memoryLimitMiB !== undefined ? Tokenization.stringifyNumber(props.memoryLimitMiB) : '512',
compatibility: Compatibility.FARGATE,
networkMode: NetworkMode.AWS_VPC,
pidMode: props.pidMode,
});

// eslint-disable-next-line max-len
if (props.ephemeralStorageGiB && !Token.isUnresolved(props.ephemeralStorageGiB) && (props.ephemeralStorageGiB < 21 || props.ephemeralStorageGiB > 200)) {
throw new Error('Ephemeral storage size must be between 21GiB and 200GiB');
}

if (props.pidMode) {
if (props.runtimePlatform?.operatingSystemFamily?.isWindows()) {
throw new Error('\'pidMode\' is not supported for Windows containers.');
}
if (!Token.isUnresolved(props.pidMode) && props.pidMode !== PidMode.HOST) {
throw new Error(`\'pidMode\' can only be set to \'${PidMode.HOST}\' for Fargate containers, got: \'${props.pidMode}\'.`);
}
}

this.ephemeralStorageGiB = props.ephemeralStorageGiB;
}
}
87 changes: 87 additions & 0 deletions packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
import { Annotations, Match, Template } from '../../../assertions';
import * as appscaling from '../../../aws-applicationautoscaling';
import * as batch from '../../../aws-batch';
import * as cloudwatch from '../../../aws-cloudwatch';
import * as ec2 from '../../../aws-ec2';
import * as elbv2 from '../../../aws-elasticloadbalancingv2';
Expand Down Expand Up @@ -685,6 +686,92 @@ describe('fargate service', () => {
}).toThrow(/one essential container/);
});

test('errors when platform version does not support containers which references secret JSON field', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
runtimePlatform: {
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
cpuArchitecture: ecs.CpuArchitecture.ARM64,
},
memoryLimitMiB: 512,
cpu: 256,
});

// Errors on validation, not on construction.
new ecs.FargateService(stack, 'FargateService', {
cluster,
taskDefinition,
platformVersion: ecs.FargatePlatformVersion.VERSION1_2,
});

taskDefinition.addContainer('main', {
image: ecs.ContainerImage.fromRegistry('somecontainer'),
secrets: {
envName: batch.Secret.fromSecretsManager(new secretsmanager.Secret(stack, 'testSecret'), 'secretField'),
},
});

// THEN
expect(() => {
Template.fromStack(stack);
}).toThrow(/This feature requires platform version/);
});

test('errors when platform version does not support ephemeralStorageGiB', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
runtimePlatform: {
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
cpuArchitecture: ecs.CpuArchitecture.ARM64,
},
memoryLimitMiB: 512,
cpu: 256,
ephemeralStorageGiB: 100,
});

// WHEN
// THEN
expect(() => {
new ecs.FargateService(stack, 'FargateService', {
cluster,
taskDefinition,
platformVersion: ecs.FargatePlatformVersion.VERSION1_2,
});
}).toThrow(/The ephemeralStorageGiB feature requires platform version/);
});

test('errors when platform version does not support pidMode', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
runtimePlatform: {
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
cpuArchitecture: ecs.CpuArchitecture.ARM64,
},
memoryLimitMiB: 512,
cpu: 256,
pidMode: ecs.PidMode.HOST,
});

// WHEN
// THEN
expect(() => {
new ecs.FargateService(stack, 'FargateService', {
cluster,
taskDefinition,
platformVersion: ecs.FargatePlatformVersion.VERSION1_2,
});
}).toThrow(/The pidMode feature requires platform version/);
});

test('allows adding the default container after creating the service', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('fargate task definition', () => {
cpuArchitecture: ecs.CpuArchitecture.X86_64,
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
},
pidMode: ecs.PidMode.HOST,
});

taskDefinition.addVolume({
Expand All @@ -84,6 +85,7 @@ describe('fargate task definition', () => {
Family: 'myApp',
Memory: '1024',
NetworkMode: 'awsvpc',
PidMode: 'host',
RequiresCompatibilities: [
ecs.LaunchType.FARGATE,
],
Expand Down Expand Up @@ -161,6 +163,38 @@ describe('fargate task definition', () => {

// THEN
});

test('throws when pidMode is specified on Windows', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
// THEN
expect(() => {
new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
pidMode: ecs.PidMode.HOST,
runtimePlatform: {
operatingSystemFamily: ecs.OperatingSystemFamily.WINDOWS_SERVER_2019_CORE,
cpuArchitecture: ecs.CpuArchitecture.X86_64,
},
cpu: 1024,
memoryLimitMiB: 2048,
});
}).toThrow(/'pidMode' is not supported for Windows containers./);
});

test('throws when pidMode is not host', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
// THEN
expect(() => {
new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
pidMode: ecs.PidMode.TASK,
});
}).toThrow(/'pidMode' can only be set to 'host' for Fargate containers, got: 'task'./);
});
});
describe('When configuredAtLaunch in the Volume', ()=> {
test('do not throw when configuredAtLaunch is false', () => {
Expand Down

0 comments on commit ed75b16

Please sign in to comment.