Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ecs): nat network mode for windows tasks #4317

Merged
merged 11 commits into from
Oct 3, 2019
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),
piradeepk marked this conversation as resolved.
Show resolved Hide resolved
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