Skip to content

Commit 08eb1d6

Browse files
authored
feat(ecs): add maxSwap and swappiness properties to LinuxParameters (#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*
1 parent 17c94eb commit 08eb1d6

13 files changed

+3253
-4
lines changed

packages/@aws-cdk/aws-ecs/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,25 @@ The task execution role is automatically granted read permissions on the secrets
487487
files is restricted to the EC2 launch type for files hosted on S3. Further details provided in the AWS documentation
488488
about [specifying environment variables](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/taskdef-envfiles.html).
489489

490+
### Linux parameters
491+
492+
To apply additional linux-specific options related to init process and memory management to the container, use the `linuxParameters` property:
493+
494+
```ts
495+
declare const taskDefinition: ecs.TaskDefinition;
496+
497+
taskDefinition.addContainer('container', {
498+
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
499+
memoryLimitMiB: 1024,
500+
linuxParameters: new ecs.LinuxParameters(this, 'LinuxParameters', {
501+
initProcessEnabled: true,
502+
sharedMemorySize: 1024,
503+
maxSwap: 5000,
504+
swappiness: 90,
505+
}),
506+
});
507+
```
508+
490509
### System controls
491510

492511
To set system controls (kernel parameters) on the container, use the `systemControls` prop:

packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts

+62-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,37 @@ export interface LinuxParametersProps {
1414
readonly initProcessEnabled?: boolean;
1515

1616
/**
17-
* The value for the size (in MiB) of the /dev/shm volume.
17+
* The value for the size of the /dev/shm volume.
1818
*
1919
* @default No shared memory.
2020
*/
2121
readonly sharedMemorySize?: number;
22+
23+
/**
24+
* The total amount of swap memory a container can use. This parameter
25+
* will be translated to the --memory-swap option to docker run.
26+
*
27+
* This parameter is only supported when you are using the EC2 launch type.
28+
* Accepted values are positive integers.
29+
*
30+
* @default No swap.
31+
*/
32+
readonly maxSwap?: cdk.Size;
33+
34+
/**
35+
* This allows you to tune a container's memory swappiness behavior. This parameter
36+
* maps to the --memory-swappiness option to docker run. The swappiness relates
37+
* to the kernel's tendency to swap memory. A value of 0 will cause swapping to
38+
* not happen unless absolutely necessary. A value of 100 will cause pages to
39+
* be swapped very aggressively.
40+
*
41+
* This parameter is only supported when you are using the EC2 launch type.
42+
* Accepted values are whole numbers between 0 and 100. If a value is not
43+
* specified for maxSwap then this parameter is ignored.
44+
*
45+
* @default 60
46+
*/
47+
readonly swappiness?: number;
2248
}
2349

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

3359
/**
34-
* The shared memory size. Not valid for Fargate launch type
60+
* The shared memory size (in MiB). Not valid for Fargate launch type
3561
*/
3662
private readonly sharedMemorySize?: number;
3763

64+
/**
65+
* The max swap memory
66+
*/
67+
private readonly maxSwap?: cdk.Size;
68+
69+
/**
70+
* The swappiness behavior
71+
*/
72+
private readonly swappiness?: number;
73+
3874
/**
3975
* Capabilities to be added
4076
*/
@@ -61,8 +97,30 @@ export class LinuxParameters extends Construct {
6197
constructor(scope: Construct, id: string, props: LinuxParametersProps = {}) {
6298
super(scope, id);
6399

100+
this.validateProps(props);
101+
64102
this.sharedMemorySize = props.sharedMemorySize;
65103
this.initProcessEnabled = props.initProcessEnabled;
104+
this.maxSwap = props.maxSwap;
105+
this.swappiness = props.maxSwap ? props.swappiness : undefined;
106+
}
107+
108+
private validateProps(props: LinuxParametersProps) {
109+
if (
110+
!cdk.Token.isUnresolved(props.sharedMemorySize) &&
111+
props.sharedMemorySize !== undefined &&
112+
(!Number.isInteger(props.sharedMemorySize) || props.sharedMemorySize < 0)
113+
) {
114+
throw new Error(`sharedMemorySize: Must be an integer greater than 0; received ${props.sharedMemorySize}.`);
115+
}
116+
117+
if (
118+
!cdk.Token.isUnresolved(props.swappiness) &&
119+
props.swappiness !== undefined &&
120+
(!Number.isInteger(props.swappiness) || props.swappiness < 0 || props.swappiness > 100)
121+
) {
122+
throw new Error(`swappiness: Must be an integer between 0 and 100; received ${props.swappiness}.`);
123+
}
66124
}
67125

68126
/**
@@ -106,6 +164,8 @@ export class LinuxParameters extends Construct {
106164
return {
107165
initProcessEnabled: this.initProcessEnabled,
108166
sharedMemorySize: this.sharedMemorySize,
167+
maxSwap: this.maxSwap?.toMebibytes(),
168+
swappiness: this.swappiness,
109169
capabilities: {
110170
add: cdk.Lazy.list({ produce: () => this.capAdd }, { omitEmpty: true }),
111171
drop: cdk.Lazy.list({ produce: () => this.capDrop }, { omitEmpty: true }),

packages/@aws-cdk/aws-ecs/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@
8484
"@aws-cdk/aws-s3-deployment": "0.0.0",
8585
"@aws-cdk/aws-efs": "0.0.0",
8686
"@aws-cdk/cdk-build-tools": "0.0.0",
87-
"@aws-cdk/integ-runner": "0.0.0",
88-
"@aws-cdk/integ-tests": "0.0.0",
8987
"@aws-cdk/cfn2ts": "0.0.0",
9088
"@aws-cdk/cx-api": "0.0.0",
89+
"@aws-cdk/integ-runner": "0.0.0",
90+
"@aws-cdk/integ-tests": "0.0.0",
9191
"@aws-cdk/pkglint": "0.0.0",
9292
"@types/jest": "^27.5.2",
9393
"@types/proxyquire": "^1.3.28",

packages/@aws-cdk/aws-ecs/test/container-definition.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -1801,6 +1801,16 @@ describe('container definition', () => {
18011801
});
18021802

18031803
describe('Can specify linux parameters', () => {
1804+
test('validation throws with out of range params', () => {
1805+
// GIVEN
1806+
const stack = new cdk.Stack();
1807+
1808+
const swappinessValues = [-1, 30.5, 101];
1809+
swappinessValues.forEach(swappiness => expect(() =>
1810+
new ecs.LinuxParameters(stack, `LinuxParametersWithSwappiness(${swappiness})`, { swappiness }))
1811+
.toThrowError(`swappiness: Must be an integer between 0 and 100; received ${swappiness}.`));
1812+
});
1813+
18041814
test('with only required properties set, it correctly sets default properties', () => {
18051815
// GIVEN
18061816
const stack = new cdk.Stack();
@@ -1836,6 +1846,8 @@ describe('container definition', () => {
18361846
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
18371847
initProcessEnabled: true,
18381848
sharedMemorySize: 1024,
1849+
maxSwap: cdk.Size.gibibytes(5),
1850+
swappiness: 90,
18391851
});
18401852

18411853
linuxParameters.addCapabilities(ecs.Capability.ALL);
@@ -1859,7 +1871,9 @@ describe('container definition', () => {
18591871
Drop: ['KILL'],
18601872
},
18611873
InitProcessEnabled: true,
1874+
MaxSwap: 5 * 1024,
18621875
SharedMemorySize: 1024,
1876+
Swappiness: 90,
18631877
},
18641878
}),
18651879
],
@@ -1874,6 +1888,8 @@ describe('container definition', () => {
18741888
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
18751889
initProcessEnabled: true,
18761890
sharedMemorySize: 1024,
1891+
maxSwap: cdk.Size.gibibytes(5),
1892+
swappiness: 90,
18771893
});
18781894

18791895
linuxParameters.addCapabilities(ecs.Capability.ALL);
@@ -1899,7 +1915,9 @@ describe('container definition', () => {
18991915
Drop: ['SETUID'],
19001916
},
19011917
InitProcessEnabled: true,
1918+
MaxSwap: 5 * 1024,
19021919
SharedMemorySize: 1024,
1920+
Swappiness: 90,
19031921
},
19041922
}),
19051923
],
@@ -1914,6 +1932,8 @@ describe('container definition', () => {
19141932
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
19151933
initProcessEnabled: true,
19161934
sharedMemorySize: 1024,
1935+
maxSwap: cdk.Size.gibibytes(5),
1936+
swappiness: 90,
19171937
});
19181938

19191939
// WHEN
@@ -1939,7 +1959,9 @@ describe('container definition', () => {
19391959
},
19401960
],
19411961
InitProcessEnabled: true,
1962+
MaxSwap: 5 * 1024,
19421963
SharedMemorySize: 1024,
1964+
Swappiness: 90,
19431965
},
19441966
}),
19451967
],
@@ -1954,6 +1976,8 @@ describe('container definition', () => {
19541976
const linuxParameters = new ecs.LinuxParameters(stack, 'LinuxParameters', {
19551977
initProcessEnabled: true,
19561978
sharedMemorySize: 1024,
1979+
maxSwap: cdk.Size.gibibytes(5),
1980+
swappiness: 90,
19571981
});
19581982

19591983
// WHEN
@@ -1981,7 +2005,9 @@ describe('container definition', () => {
19812005
},
19822006
],
19832007
InitProcessEnabled: true,
2008+
MaxSwap: 5 * 1024,
19842009
SharedMemorySize: 1024,
2010+
Swappiness: 90,
19852011
},
19862012
}),
19872013
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as ec2 from '@aws-cdk/aws-ec2';
2+
import * as cdk from '@aws-cdk/core';
3+
import * as integ from '@aws-cdk/integ-tests';
4+
import * as ecs from '../../lib';
5+
import { LinuxParameters } from '../../lib';
6+
7+
const app = new cdk.App();
8+
const stack = new cdk.Stack(app, 'aws-ecs-integ');
9+
10+
const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 });
11+
12+
// ECS cluster to host EC2 task
13+
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
14+
cluster.addCapacity('DefaultAutoScalingGroup', {
15+
instanceType: new ec2.InstanceType('t2.micro'),
16+
});
17+
18+
// define task to run the container
19+
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDefinition', {
20+
networkMode: ecs.NetworkMode.AWS_VPC,
21+
});
22+
23+
// define linux parameters to enable swap
24+
const linuxParameters = new LinuxParameters(stack, 'LinuxParameters', {
25+
maxSwap: cdk.Size.gibibytes(5),
26+
swappiness: 90,
27+
});
28+
29+
// define container with linux parameters
30+
new ecs.ContainerDefinition(stack, 'Container', {
31+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
32+
linuxParameters,
33+
memoryLimitMiB: 256,
34+
taskDefinition,
35+
});
36+
37+
// define a service to run the task definition
38+
new ecs.Ec2Service(stack, 'Service', {
39+
cluster,
40+
taskDefinition,
41+
});
42+
43+
new integ.IntegTest(app, 'SwapParametersTest', {
44+
testCases: [stack],
45+
});
46+
47+
app.synth();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "21.0.0",
3+
"files": {
4+
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
5+
"source": {
6+
"path": "SwapParametersTestDefaultTestDeployAssert4CDF4940.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"Parameters": {
3+
"BootstrapVersion": {
4+
"Type": "AWS::SSM::Parameter::Value<String>",
5+
"Default": "/cdk-bootstrap/hnb659fds/version",
6+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
7+
}
8+
},
9+
"Rules": {
10+
"CheckBootstrapVersion": {
11+
"Assertions": [
12+
{
13+
"Assert": {
14+
"Fn::Not": [
15+
{
16+
"Fn::Contains": [
17+
[
18+
"1",
19+
"2",
20+
"3",
21+
"4",
22+
"5"
23+
],
24+
{
25+
"Ref": "BootstrapVersion"
26+
}
27+
]
28+
}
29+
]
30+
},
31+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
32+
}
33+
]
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "21.0.0",
3+
"files": {
4+
"cb4c2fe92a33d5f7055fc524d88ede92f68a9c5c5265b0b9a91cb6a20f57a574": {
5+
"source": {
6+
"path": "aws-ecs-integ.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "cb4c2fe92a33d5f7055fc524d88ede92f68a9c5c5265b0b9a91cb6a20f57a574.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}

0 commit comments

Comments
 (0)