aws / aws/aws-cdk

(ecs): FargateService fails on imported Subnets if availability zone attribute is missing

Open
#30,104 2 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-ecs bug effort/medium p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

Creating a new `ecs.FargateService` with specific `subnet-ids` via its `vpcSubnets: ec2.SubnetSelection` property fails even when only `subnet-id` is required.

> Note: we have an environment constraint and can't use `vpc.fromLookup()` during CDK Synth to use CDK Context and automatically load all subnets and their attributes.

Due to environment constraints, our AWS CDK Construct must depend on existing CloudFormation Stack Exported Outputs in the AWS Environment.

Sample code:
```typescript
const privateSubnetsRef = Fn.importValue(
stackName + "-privatesubnets",
);
const privateSubnets = Fn.split(",", privateSubnetsRef).map(s => ec2.Subnet.fromSubnetId(this, s, s))

vpc.selectSubnets({
subnets: privateSubnets,
})
```

Work Around:

```typescript
// Create FargateService with default `networkConfiguration`
const svc = new ecs.FargateService(this, "FargateService", {
taskDefinition,
cluster: this.ecsCluster,
desiredCount: this.data.desired,
// Use default subnetSelection ...
// Public subnets if `assignPublicIp` is set, otherwise the first available one of Private, Isolated, Public, in that order.
// vpcSubnets: this.vpc.selectSubnets({
// subnets: privateSubnets,
// }),
// required, else CDK creates new SecurityGroup
securityGroups: [this.securityGroup],
});

// Use AWS CDK escape hatch to override networkConfiguration without `vpc.selectSubnets` limitations....
// https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html#develop-customize-escape-l2
const cfnSvc = svc.node.defaultChild as ecs.CfnService;
cfnSvc.networkConfiguration = {
awsvpcConfiguration: {
subnets: Fn.split(",", privateSubnetsRef),
securityGroups: [Fn.ref("SecurityGroup")],
},
};

```

### Expected Behavior

Providing the ecsFargateService an explicit list of SubnetIds should not require the Availability Zone attribute per Subnet ... (the bug is caused by - `L480`:

https://github.com/aws/aws-cdk/blob/46168aac07c0d3f50ad10c31801751d083474081/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts#L474-L486

### Current Behavior

Error is thrown

```console
You cannot reference a Subnet's availability zone if it was not supplied. Add the availabilityZone when importing using Subnet.fromSubnetAttributes()

236 | // ecs.FargateService -> launchType == "FARGATE"
237 | // TODO: is ServiceName: &configCode, required?
> 238 | const svc = new ecs.FargateService(this, "FargateService", {
| ^
239 | taskDefinition,
240 | cluster: this.ecsCluster,
241 | desiredCount: this.data.desired,

at ImportedSubnet.get availabilityZone [as availabilityZone] (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:35138)
at node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:2861
at Array.map ()
at Object.get availabilityZones [as availabilityZones] (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:2852)
at ImportedVpc.reifySelectionDefaults (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:7431)
at ImportedVpc.selectSubnetObjects (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:4733)
at ImportedVpc.selectSubnets (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.js:1:2705)
at FargateService.configureAwsVpcNetworkingWithSecurityGroups (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ecs/lib/base/base-service.js:1:25843)
at new FargateService (node_modules/.pnpm/aws-cdk-lib@2.140.0_constructs@10.3.0/node_modules/aws-cdk-lib/aws-ecs/lib/fargate/fargate-service.js:1:3382)
at new EcsService (src/index.ts:238:19)
at Object. (test/ecs-service-stack.test.ts:113:5)
```

### Reproduction Steps

```typescript
import { Fn } from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import { Construct } from "constructs";

export interface EcsServiceTestProps {
/**
* The environment stack name passed in during synth
*
* required to import environment stack exported outputs
*/
environmentStackName: string;
}

export class EcsServiceTest extends Construct {
constructor(scope: Construct, name: string, props: EcsServiceTestProps) {
{
super(scope, name);

// Define "Environment" Stack Imports
const vpcRef = Fn.importValue(props.environmentStackName + "-vpc");
const availabilityZonesRef = Fn.importValue(
props.environmentStackName + "-azs",
);
const ecsClusterNameRef = Fn.importValue(
props.environmentStackName + "-cluster",
);
const privateSubnetsRef = Fn.importValue(
props.environmentStackName + "-privatesubnets",
);

// Import VPC, Subnets and ECS Cluster
const vpc = ec2.Vpc.fromVpcAttributes(this, "Vpc", {
availabilityZones: Fn.split(",", availabilityZonesRef),
privateSubnetIds: Fn.split(",", privateSubnetsRef),
vpcId: vpcRef,
});
const privateSubnets = Fn.split(",", privateSubnetsRef).map((s) =>
ec2.Subnet.fromSubnetId(this, s, s),
);
const cluster = ecs.Cluster.fromClusterAttributes(this, "Cluster", {
clusterName: ecsClusterNameRef.toString(),
vpc: vpc,
});

const cpu = 1014;
const memoryLimitMiB = 100;
const taskDefinition = new ecs.FargateTaskDefinition(
this,
"TaskDefinition",
{
cpu,
memoryLimitMiB,
},
);

taskDefinition.addContainer("MyService", {
image: ecs.ContainerImage.fromRegistry("my-repo/my-image"),
cpu,
memoryLimitMiB,
});

new ecs.FargateService(this, "FargateService", {
taskDefinition,
cluster,
desiredCount: 2,
// This fails
vpcSubnets: vpc.selectSubnets({
subnets: privateSubnets,
}),
// required, else CDK creates new SecurityGroup
securityGroups: [
ec2.SecurityGroup.fromSecurityGroupId(
this,
"SecurityGroup",
Fn.ref("SecurityGroup"),
),
],
});

// // AWS CDK escape hatch to override networkConfiguration and work around failure
// // https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html#develop-customize-escape-l2
// const cfnSvc = svc.node.defaultChild as ecs.CfnService;
// cfnSvc.networkConfiguration = {
// awsvpcConfiguration: {
// subnets: Fn.split(",", privateSubnetsRef),
// securityGroups: [Fn.ref("SecurityGroup")],
// },
// };
}
}
}

```

### Possible Solution

Either:

- the `ecs.BaseService` allows passing in Subnets by `ISubnet[]` , or
- the `vpc.selectSubnets()` should not error if the `availabilityZone` attribute does not exist on the subnet?

### Additional Information/Context

_No response_

### CDK CLI Version

2.140.0

### Framework Version

2.140.0

### Node.js Version

20.12.2

### OS

WSL

### Language

TypeScript

### Language Version

5.3.3

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with packages/aws-cdk-lib/aws-ec2/lib/vpc.ts around lines 474-486 and trace the subnet selection path shown in the stack trace through aws-ecs/lib/base/base-service.js. Reproduce the failure using test/ecs-service-stack.test.ts or the provided TypeScript example. Done means an explicit list of imported subnet IDs can be passed to FargateService without requiring each subnet's availability zone, with coverage for the existing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, infrastructure
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.