Why my CDK VPC constructor does not respect maximum availability zones
Problem
In the following code snippet, we configure the maximum number of availability zones(AZs). However, CDK does not respect the value.
$ cdk --version
2.5.0 (build 0951122)
$ cat ./bin/vpc-additional-subnet-stack.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { VpcAdditionalSubnetStackStack } from '../lib/vpc-additional-subnet-stack-stack';
const app = new cdk.App();
new VpcAdditionalSubnetStackStack(app, 'VpcAdditionalSubnetStackStack', {
env: { account: '111111111111', region: 'eu-west-1' },
});%
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from "aws-cdk-lib/aws-ec2";
export class VpcAdditionalSubnetStackStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id);
const subnetConfig = [
{
cidrMask: 22,
name: "outputSubnet",
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 22,
name: "database",
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
{
cidrMask: 22,
name: "application",
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
];
// VPC
const vpc = new ec2.Vpc(this, "Lab-VPC", {
cidr: "10.0.0.0/16",
maxAzs: 3,
subnetConfiguration: subnetConfig,
});
}
}
Also, we can view there are 3 AZs in eu-west-1
region.
$ aws ec2 describe-availability-zones
{
"AvailabilityZones": [
{
"State": "available",
"OptInStatus": "opt-in-not-required",
"Messages": [],
"RegionName": "eu-west-1",
"ZoneName": "eu-west-1a",
"ZoneId": "euw1-az1",
"GroupName": "eu-west-1",
"NetworkBorderGroup": "eu-west-1",
"ZoneType": "availability-zone"
},
{
"State": "available",
"OptInStatus": "opt-in-not-required",
"Messages": [],
"RegionName": "eu-west-1",
"ZoneName": "eu-west-1b",
"ZoneId": "euw1-az2",
"GroupName": "eu-west-1",
"NetworkBorderGroup": "eu-west-1",
"ZoneType": "availability-zone"
},
{
"State": "available",
"OptInStatus": "opt-in-not-required",
"Messages": [],
"RegionName": "eu-west-1",
"ZoneName": "eu-west-1c",
"ZoneId": "euw1-az3",
"GroupName": "eu-west-1",
"NetworkBorderGroup": "eu-west-1",
"ZoneType": "availability-zone"
}
]
}
Dive into the problem
According to the Stack.availabilityZones:
If the stack is environment-agnostic (either account and/or region are tokens), this property will return an array with 2 tokens that will resolve at deploy-time to the first two availability zones returned from CloudFormation's Fn::GetAZs
intrinsic function.
From the code snippet, we had already set up the account and region. It seems that the Stack does not inherit the environment. Thus, we can check the account and region of Stack again as the following snippet.
export class VpcAdditionalSubnetStackStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
console.log('account: ', Stack.of(this).account);
console.log('region: ', Stack.of(this).region);
console.log('availability zones', Stack.of(this).availabilityZones);
$ cdk synth
account: ${Token[AWS.AccountId.6]}
region: ${Token[AWS.Region.10]}
availability zones [ '${Token[TOKEN.200]}', '${Token[TOKEN.202]}' ]
...
...
AWS CDK encodes a token whose value is not yet known at construction time[2]. We can know the environment variables are not been used in VpcAdditionalSubnetStackStack
Stack.
super(scope, id);
From the code snippet, which does not pass props
in the call tosuper()
, and the environment variable we pass when creating VpcAdditionalSubnetStackStack
is ignored. Therefore, CDK considers this to be environment-agnostic and creates only 2 AZ.
$ cat ./lib/vpc-additional-subnet-stack-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
export class VpcAdditionalSubnetStackStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// The code that defines your stack goes here
// example resource
// const queue = new sqs.Queue(this, 'VpcAdditionalSubnetStackQueue', {
// visibilityTimeout: cdk.Duration.seconds(300)
// });
}
}
By default, the CDK helps us pull a template that defines the basic constructor. Here is the example template:
super(scope, id, props);
$ cdk synth
account: 111111111111
region: eu-west-1
availability zones [ 'eu-west-1a', 'eu-west-1b', 'eu-west-1c' ]
...
After updating the super()
function, we can view the account and region output.
Summary
In order to create the availability zones of an AWS region, we must set up the following items:
- Environment variable for
account
andregion
, it can be set up via AWS credential orenv
property. - Confirm the Stack inherits the
StackProps
, if not the stack will ignore the environment variable we set up in previous steps.
References
- https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2-readme.html#advanced-subnet-configuration
- Tokens - https://docs.aws.amazon.com/zh_cn/cdk/v2/guide/tokens.html
- https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Stack.html#availabilityzones
- https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.StackProps.html#env