Skip to content

Commit

Permalink
fix(ecs): nat network mode for windows tasks (#4317)
Browse files Browse the repository at this point in the history
* fix(ecs): nat network mode for windows tasks

Add NAT network mode to support Windows container networking.
This is the only network mode supported by Windows.
See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/windows_task_IAM_roles.html for additional details.

fixes #4272

* remove unnecessary whitespaces

* PR review feedback - consolidate the comments.

* PR feedback - simplify ephemeral port mapping logic
  • Loading branch information
kolomied authored and mergify[bot] committed Oct 3, 2019
1 parent 54bcebd commit 9ceb995
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 81 deletions.
16 changes: 14 additions & 2 deletions packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ export class TaskDefinition extends TaskDefinitionBase {
...(isEc2Compatible(props.compatibility) ? ["EC2"] : []),
...(isFargateCompatible(props.compatibility) ? ["FARGATE"] : []),
],
networkMode: this.networkMode,
networkMode: this.renderNetworkMode(this.networkMode),
placementConstraints: Lazy.anyValue({ produce: () =>
!isFargateCompatible(this.compatibility) ? this.placementConstraints : undefined
}, { omitEmptyArray: true }),
Expand Down Expand Up @@ -335,7 +335,7 @@ export class TaskDefinition extends TaskDefinitionBase {
if (portMapping.hostPort !== undefined && portMapping.hostPort !== 0) {
return portMapping.protocol === Protocol.UDP ? ec2.Port.udp(portMapping.hostPort) : ec2.Port.tcp(portMapping.hostPort);
}
if (this.networkMode === NetworkMode.BRIDGE) {
if (this.networkMode === NetworkMode.BRIDGE || this.networkMode === NetworkMode.NAT) {
return EPHEMERAL_PORT_RANGE;
}
return portMapping.protocol === Protocol.UDP ? ec2.Port.udp(portMapping.containerPort) : ec2.Port.tcp(portMapping.containerPort);
Expand Down Expand Up @@ -437,6 +437,10 @@ export class TaskDefinition extends TaskDefinitionBase {
private findContainer(containerName: string): ContainerDefinition | undefined {
return this.containers.find(c => c.containerName === containerName);
}

private renderNetworkMode(networkMode: NetworkMode): string | undefined {
return (networkMode === NetworkMode.NAT) ? undefined : networkMode;
}
}

/**
Expand Down Expand Up @@ -470,6 +474,14 @@ export enum NetworkMode {
* single container instance when port mappings are used.
*/
HOST = 'host',

/**
* The task utilizes NAT network mode required by Windows containers.
*
* This is the only supported network mode for Windows containers. For more information, see
* [Task Definition Parameters](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#network_mode).
*/
NAT = 'nat'
}

/**
Expand Down
160 changes: 82 additions & 78 deletions packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1088,94 +1088,98 @@ export = {
},

'correctly setting ingress and egress port': {
'with bridge network mode and 0 host port'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
const container = taskDefinition.addContainer('MainContainer', {
image: ContainerImage.fromRegistry('hello'),
memoryLimitMiB: 512,
});
container.addPortMappings({ containerPort: 8000 });
container.addPortMappings({ containerPort: 8001 });

const service = new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition
});
'with bridge/NAT network mode and 0 host port'(test: Test) {
[NetworkMode.BRIDGE, NetworkMode.NAT].forEach((networkMode: NetworkMode) => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode });
const container = taskDefinition.addContainer('MainContainer', {
image: ContainerImage.fromRegistry('hello'),
memoryLimitMiB: 512,
});
container.addPortMappings({ containerPort: 8000 });
container.addPortMappings({ containerPort: 8001 });

// WHEN
const lb = new elbv2.ApplicationLoadBalancer(stack, "lb", { vpc });
const listener = lb.addListener("listener", { port: 80 });
listener.addTargets("target", {
port: 80,
targets: [service.loadBalancerTarget({
containerName: "MainContainer",
containerPort: 8001
})]
});
const service = new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition
});

// THEN
expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', {
Description: "Load balancer to target",
FromPort: 32768,
ToPort: 65535
}));
// WHEN
const lb = new elbv2.ApplicationLoadBalancer(stack, "lb", { vpc });
const listener = lb.addListener("listener", { port: 80 });
listener.addTargets("target", {
port: 80,
targets: [service.loadBalancerTarget({
containerName: "MainContainer",
containerPort: 8001
})]
});

expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', {
Description: "Load balancer to target",
FromPort: 32768,
ToPort: 65535
}));
// THEN
expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', {
Description: "Load balancer to target",
FromPort: 32768,
ToPort: 65535
}));

expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', {
Description: "Load balancer to target",
FromPort: 32768,
ToPort: 65535
}));
});

test.done();
},

'with bridge network mode and host port other than 0'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef');
const container = taskDefinition.addContainer('MainContainer', {
image: ContainerImage.fromRegistry('hello'),
memoryLimitMiB: 512,
});
container.addPortMappings({ containerPort: 8000 });
container.addPortMappings({ containerPort: 8001, hostPort: 80 });

const service = new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition
});
'with bridge/NAT network mode and host port other than 0'(test: Test) {
[NetworkMode.BRIDGE, NetworkMode.NAT].forEach((networkMode: NetworkMode) => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') });
const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { networkMode });
const container = taskDefinition.addContainer('MainContainer', {
image: ContainerImage.fromRegistry('hello'),
memoryLimitMiB: 512,
});
container.addPortMappings({ containerPort: 8000 });
container.addPortMappings({ containerPort: 8001, hostPort: 80 });

// WHEN
const lb = new elbv2.ApplicationLoadBalancer(stack, "lb", { vpc });
const listener = lb.addListener("listener", { port: 80 });
listener.addTargets("target", {
port: 80,
targets: [service.loadBalancerTarget({
containerName: "MainContainer",
containerPort: 8001
})]
});
const service = new ecs.Ec2Service(stack, 'Service', {
cluster,
taskDefinition
});

// THEN
expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', {
Description: "Load balancer to target",
FromPort: 80,
ToPort: 80,
}));
// WHEN
const lb = new elbv2.ApplicationLoadBalancer(stack, "lb", { vpc });
const listener = lb.addListener("listener", { port: 80 });
listener.addTargets("target", {
port: 80,
targets: [service.loadBalancerTarget({
containerName: "MainContainer",
containerPort: 8001
})]
});

expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', {
Description: "Load balancer to target",
FromPort: 80,
ToPort: 80
}));
// THEN
expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', {
Description: "Load balancer to target",
FromPort: 80,
ToPort: 80,
}));

expect(stack).to(haveResource('AWS::EC2::SecurityGroupEgress', {
Description: "Load balancer to target",
FromPort: 80,
ToPort: 80
}));
});

test.done();
},
Expand Down
27 changes: 26 additions & 1 deletion packages/@aws-cdk/aws-ecs/test/test.container-definition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert';
import { expect, haveResource, haveResourceLike, InspectionFailure } from '@aws-cdk/assert';
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
import ssm = require('@aws-cdk/aws-ssm');
import cdk = require('@aws-cdk/core');
Expand Down Expand Up @@ -274,6 +274,31 @@ export = {

test.done();
},
},

"With network mode NAT": {
"produces undefined CF networkMode property"(test: Test) {
// GIVEN
const stack = new cdk.Stack();

// WHEN
new ecs.TaskDefinition(stack, 'TD', {
compatibility: ecs.Compatibility.EC2,
networkMode: ecs.NetworkMode.NAT
});

// THEN
expect(stack).to(haveResource('AWS::ECS::TaskDefinition', (props: any, inspection: InspectionFailure) => {
if (props.NetworkMode === undefined) {
return true;
}

inspection.failureReason = 'CF template should not have NetworkMode defined for a task definition that relies on NAT network mode.';
return false;
}));

test.done();
}
}
},

Expand Down

0 comments on commit 9ceb995

Please sign in to comment.