(ecs): FargateService fails on imported Subnets if availability zone attribute is missing
- 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
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