Skip to content

Commit

Permalink
feat(ecs): add maxSwap and swappiness properties to LinuxParamete…
Browse files Browse the repository at this point in the history
…rs (#18703)

Add support to `MaxSwap ` and `Swappiness` attributes in the `LinuxParameters` construct.

Closes #18460

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
flavioleggio committed Sep 6, 2022
1 parent 17c94eb commit 08eb1d6
Show file tree
Hide file tree
Showing 13 changed files with 3,253 additions and 4 deletions.
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,25 @@ The task execution role is automatically granted read permissions on the secrets
files is restricted to the EC2 launch type for files hosted on S3. Further details provided in the AWS documentation
about [specifying environment variables](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html).

### Linux parameters

To apply additional linux-specific options related to init process and memory management to the container, use the `linuxParameters` property:

```ts
declare const taskDefinition: ecs.TaskDefinition;

taskDefinition.addContainer('container', {
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
memoryLimitMiB: 1024,
linuxParameters: new ecs.LinuxParameters(this, 'LinuxParameters', {
initProcessEnabled: true,
sharedMemorySize: 1024,
maxSwap: 5000,
swappiness: 90,
}),
});
```

### System controls

To set system controls (kernel parameters) on the container, use the `systemControls` prop:
Expand Down
64 changes: 62 additions & 2 deletions packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,37 @@ export interface LinuxParametersProps {
readonly initProcessEnabled?: boolean;

/**
* The value for the size (in MiB) of the /dev/shm volume.
* The value for the size of the /dev/shm volume.
*
* @default No shared memory.
*/
readonly sharedMemorySize?: number;

/**
* The total amount of swap memory a container can use. This parameter
* will be translated to the --memory-swap option to docker run.
*
* This parameter is only supported when you are using the EC2 launch type.
* Accepted values are positive integers.
*
* @default No swap.
*/
readonly maxSwap?: cdk.Size;

/**
* This allows you to tune a container's memory swappiness behavior. This parameter
* maps to the --memory-swappiness option to docker run. The swappiness relates
* to the kernel's tendency to swap memory. A value of 0 will cause swapping to
* not happen unless absolutely necessary. A value of 100 will cause pages to
* be swapped very aggressively.
*
* This parameter is only supported when you are using the EC2 launch type.
* Accepted values are whole numbers between 0 and 100. If a value is not
* specified for maxSwap then this parameter is ignored.
*
* @default 60
*/
readonly swappiness?: number;
}

/**
Expand All @@ -31,10 +57,20 @@ export class LinuxParameters extends Construct {
private readonly initProcessEnabled?: boolean;

/**
* The shared memory size. Not valid for Fargate launch type
* The shared memory size (in MiB). Not valid for Fargate launch type
*/
private readonly sharedMemorySize?: number;

/**
* The max swap memory
*/
private readonly maxSwap?: cdk.Size;

/**
* The swappiness behavior
*/
private readonly swappiness?: number;

/**
* Capabilities to be added
*/
Expand All @@ -61,8 +97,30 @@ export class LinuxParameters extends Construct {
constructor(scope: Construct, id: string, props: LinuxParametersProps = {}) {
super(scope, id);

this.validateProps(props);

this.sharedMemorySize = props.sharedMemorySize;
this.initProcessEnabled = props.initProcessEnabled;
this.maxSwap = props.maxSwap;
this.swappiness = props.maxSwap ? props.swappiness : undefined;
}

private validateProps(props: LinuxParametersProps) {
if (
!cdk.Token.isUnresolved(props.sharedMemorySize) &&
props.sharedMemorySize !== undefined &&
(!Number.isInteger(props.sharedMemorySize) || props.sharedMemorySize < 0)
) {
throw new Error(`sharedMemorySize: Must be an integer greater than 0; received ${props.sharedMemorySize}.`);
}

if (
!cdk.Token.isUnresolved(props.swappiness) &&
props.swappiness !== undefined &&
(!Number.isInteger(props.swappiness) || props.swappiness < 0 || props.swappiness > 100)
) {
throw new Error(`swappiness: Must be an integer between 0 and 100; received ${props.swappiness}.`);
}
}

/**
Expand Down Expand Up @@ -106,6 +164,8 @@ export class LinuxParameters extends Construct {
return {
initProcessEnabled: this.initProcessEnabled,
sharedMemorySize: this.sharedMemorySize,
maxSwap: this.maxSwap?.toMebibytes(),
swappiness: this.swappiness,
capabilities: {
add: cdk.Lazy.list({ produce: () => this.capAdd }, { omitEmpty: true }),
drop: cdk.Lazy.list({ produce: () => this.capDrop }, { omitEmpty: true }),
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-ecs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@
"@aws-cdk/aws-s3-deployment": "0.0.0",
"@aws-cdk/aws-efs": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2",
"@types/proxyquire": "^1.3.28",
Expand Down
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/container-definition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,16 @@ describe('container definition', () => {
});

describe('Can specify linux parameters', () => {
test('validation throws with out of range params', () => {
// GIVEN
const stack = new cdk.Stack();

const swappinessValues = [-1, 30.5, 101];
swappinessValues.forEach(swappiness => expect(() =>
new ecs.LinuxParameters(stack, `LinuxParametersWithSwappiness(${swappiness})`, { swappiness }))
.toThrowError(`swappiness: Must be an integer between 0 and 100; received ${swappiness}.`));
});

test('with only required properties set, it correctly sets default properties', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down Expand Up @@ -1836,6 +1846,8 @@ describe('container definition', () => {
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
initProcessEnabled: true,
sharedMemorySize: 1024,
maxSwap: cdk.Size.gibibytes(5),
swappiness: 90,
});

linuxParameters.addCapabilities(ecs.Capability.ALL);
Expand All @@ -1859,7 +1871,9 @@ describe('container definition', () => {
Drop: ['KILL'],
},
InitProcessEnabled: true,
MaxSwap: 5 * 1024,
SharedMemorySize: 1024,
Swappiness: 90,
},
}),
],
Expand All @@ -1874,6 +1888,8 @@ describe('container definition', () => {
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
initProcessEnabled: true,
sharedMemorySize: 1024,
maxSwap: cdk.Size.gibibytes(5),
swappiness: 90,
});

linuxParameters.addCapabilities(ecs.Capability.ALL);
Expand All @@ -1899,7 +1915,9 @@ describe('container definition', () => {
Drop: ['SETUID'],
},
InitProcessEnabled: true,
MaxSwap: 5 * 1024,
SharedMemorySize: 1024,
Swappiness: 90,
},
}),
],
Expand All @@ -1914,6 +1932,8 @@ describe('container definition', () => {
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
initProcessEnabled: true,
sharedMemorySize: 1024,
maxSwap: cdk.Size.gibibytes(5),
swappiness: 90,
});

// WHEN
Expand All @@ -1939,7 +1959,9 @@ describe('container definition', () => {
},
],
InitProcessEnabled: true,
MaxSwap: 5 * 1024,
SharedMemorySize: 1024,
Swappiness: 90,
},
}),
],
Expand All @@ -1954,6 +1976,8 @@ describe('container definition', () => {
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
initProcessEnabled: true,
sharedMemorySize: 1024,
maxSwap: cdk.Size.gibibytes(5),
swappiness: 90,
});

// WHEN
Expand Down Expand Up @@ -1981,7 +2005,9 @@ describe('container definition', () => {
},
],
InitProcessEnabled: true,
MaxSwap: 5 * 1024,
SharedMemorySize: 1024,
Swappiness: 90,
},
}),
],
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/ec2/integ.swap-parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as cdk from '@aws-cdk/core';
import * as integ from '@aws-cdk/integ-tests';
import * as ecs from '../../lib';
import { LinuxParameters } from '../../lib';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'aws-ecs-integ');

const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 });

// ECS cluster to host EC2 task
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', {
instanceType: new ec2.InstanceType('t2.micro'),
});

// define task to run the container
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDefinition', {
networkMode: ecs.NetworkMode.AWS_VPC,
});

// define linux parameters to enable swap
const linuxParameters = new LinuxParameters(stack, 'LinuxParameters', {
maxSwap: cdk.Size.gibibytes(5),
swappiness: 90,
});

// define container with linux parameters
new ecs.ContainerDefinition(stack, 'Container', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
linuxParameters,
memoryLimitMiB: 256,
taskDefinition,
});

// define a service to run the task definition
new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition,
});

new integ.IntegTest(app, 'SwapParametersTest', {
testCases: [stack],
});

app.synth();
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "SwapParametersTestDefaultTestDeployAssert4CDF4940.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"cb4c2fe92a33d5f7055fc524d88ede92f68a9c5c5265b0b9a91cb6a20f57a574": {
"source": {
"path": "aws-ecs-integ.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "cb4c2fe92a33d5f7055fc524d88ede92f68a9c5c5265b0b9a91cb6a20f57a574.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading

0 comments on commit 08eb1d6

Please sign in to comment.