Skip to content

VPC Route tables in CDK

0

Hi,

I'm relatively new in the world of CDK. What I'm trying to achieve is to build a VPC with 4 public subnets, and 6 private subnets. For this I want to create 2 Route Tables, one of which is connected to the internet gatway.

This all seems to work so far. However, It seems that for every availablility zone, another route table is created. And the association to these subnets also don't seem to work as expected.

VPC Resource Map

Upon creation of the VPC I used property subnetConfiguration: [] to enforce creating subnets manually.

This is my code:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_ec2 } from 'aws-cdk-lib';

export class CcwVpcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Create VPC called CloudConsoltWorkspace with CIDR block 10.1.0.0/16 in us-east-1 with all availability zones in this region.
    const vpc = new aws_ec2.Vpc(this, 'CCW', {
      ipAddresses: aws_ec2.IpAddresses.cidr('10.1.0.0/16'),
      availabilityZones: ['us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d', 'us-east-1e', 'us-east-1f'],

      // Disable standard subnet configuration.
      subnetConfiguration: []
    });

    // Create Internet Gateway for Public Subnet Routing
    const internetGateway = new aws_ec2.CfnInternetGateway(this, 'InternetGateway', {
      tags: [{ key: 'Name', value: 'CCW-IGW' }]
    });

    new aws_ec2.CfnVPCGatewayAttachment(this, 'VpcGatewayAttachment', {
      vpcId: vpc.vpcId,
      internetGatewayId: internetGateway.ref
    });

    // Create Public Route Table
    const publicRouteTable = new aws_ec2.CfnRouteTable(this, 'PublicRouteTable', {
      vpcId: vpc.vpcId,
      tags: [{ key: 'Name', value: 'PublicRouteTable' }]
    });

    // Create a default route in the public route table to the Internet Gateway
    new aws_ec2.CfnRoute(this, 'PublicRoute', {
      routeTableId: publicRouteTable.ref,
      destinationCidrBlock: '0.0.0.0/0',
      gatewayId: internetGateway.ref
    });

    // Create Private Route Table
    const privateRouteTable = new aws_ec2.CfnRouteTable(this, 'PrivateRouteTable', {
      vpcId: vpc.vpcId,
      tags: [{ key: 'Name', value: 'PrivateRouteTable' }]
    });

    // Create 1 public subnet in this VPC with CIDR block 10.1.0.0/24
    const publicServicesSubnet = new aws_ec2.PublicSubnet(this, 'Public Services', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1a',
      cidrBlock: '10.1.0.0/24',
      mapPublicIpOnLaunch: true,
    });

    // Create 3 public subnets in this VPC with CIDR blocks 10.1.1.0/24, 10.1.2.0/24, 10.1.3.0/24
    const publicSubnet1A = new aws_ec2.PublicSubnet(this, 'PublicSubnet1A', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1a',
      cidrBlock: '10.1.1.0/24',
      mapPublicIpOnLaunch: true,
    });

    const publicSubnet1B = new aws_ec2.PublicSubnet(this, 'PublicSubnet1B', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1b',
      cidrBlock: '10.1.2.0/24',
      mapPublicIpOnLaunch: true,
    });

    const publicSubnet1C = new aws_ec2.PublicSubnet(this, 'PublicSubnet1C', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1c',
      cidrBlock: '10.1.3.0/24',
      mapPublicIpOnLaunch: true,
    });


    // Create 3 private subnets in this VPC with CIDR blocks 10.1.11.0/24, 10.1.12.0/24, 10.1.13.0/24
    const privateSubnet1A = new aws_ec2.PrivateSubnet(this, 'PrivateSubnet1A', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1a',
      cidrBlock: '10.1.11.0/24',
    });

    const privateSubnet1B = new aws_ec2.PrivateSubnet(this, 'PrivateSubnet1B', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1b',
      cidrBlock: '10.1.12.0/24',
    });

    const privateSubnet1C = new aws_ec2.PrivateSubnet(this, 'PrivateSubnet1C', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1c',
      cidrBlock: '10.1.13.0/24',
    });


    // Create 3 private subnets in this VPC with CIDR blocks 10.1.11.0/24, 10.1.12.0/24, 10.1.13.0/24
    const privateDbSubnet1A = new aws_ec2.PrivateSubnet(this, 'PrivateDbSubnet1A', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1a',
      cidrBlock: '10.1.21.0/24',
    });

    const privateDbSubnet1B = new aws_ec2.PrivateSubnet(this, 'PrivateDbSubnet1B', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1b',
      cidrBlock: '10.1.22.0/24',
    });

    const privateDbSubnet1C = new aws_ec2.PrivateSubnet(this, 'PrivateDbSubnet1C', {
      vpcId: vpc.vpcId,
      availabilityZone: 'us-east-1c',
      cidrBlock: '10.1.23.0/24',
    });

    // Associate Public Subnets with Public Route Table
    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PublicSubnet1AAssociation', {
      subnetId: publicSubnet1A.subnetId,
      routeTableId: publicRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PublicSubnet1BAssociation', {
      subnetId: publicSubnet1B.subnetId,
      routeTableId: publicRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PublicSubnet1CAssociation', {
      subnetId: publicSubnet1C.subnetId,
      routeTableId: publicRouteTable.ref
    });

    // Associate Private Subnets with Private Route Table
    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PrivateSubnet1AAssociation', {
      subnetId: privateSubnet1A.subnetId,
      routeTableId: privateRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PrivateSubnet1BAssociation', {
      subnetId: privateSubnet1B.subnetId,
      routeTableId: privateRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PrivateSubnet1CAssociation', {
      subnetId: privateSubnet1C.subnetId,
      routeTableId: privateRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PrivateDbSubnet1AAssociation', {
      subnetId: privateDbSubnet1A.subnetId,
      routeTableId: privateRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PrivateDbSubnet1BAssociation', {
      subnetId: privateDbSubnet1B.subnetId,
      routeTableId: privateRouteTable.ref
    });

    new aws_ec2.CfnSubnetRouteTableAssociation(this, 'PrivateDbSubnet1CAssociation', {
      subnetId: privateDbSubnet1C.subnetId,
      routeTableId: privateRouteTable.ref
    });

  }
}

What am I missing here?

2 Answers
2
Accepted Answer

The PrivateSubnet class is a higher-level abstraction than the low-level classes whose names are prefixed by "Cfn". The "Cfn" ones are thin wrappers around raw CloudFormation resource types, which require you to configure mostly everything yourself, without introducing any additional abstractions from CDK, but also allow for maximum flexibility for the same reason. The different layer abstractions don't tend to combine very well.

I'm not sure if it's possible to combine these specific ones, but you could use the lower level abstractions for both the subnets and the route tables. CfnSubnet would be the low-level class for a subnet: https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.CfnSubnet.html and it would go with the CfnRouteTable and CfnSubnetRouteTableAssociation classes you were already using.

The high-level PrivateSubnet abstraction seems to be assuming it needs to provide its own route table.

On a related note, usually private subnets would need their own route tables so that they can use the NAT gateway (for IPv4 traffic) in the same AZ for traffic outbound to the internet. Public subnets can share the same route table with the default route pointing to the single IGW. I understand your setup might be different, such as not needing internet connectivity over IPv4 or choosing to run it all through the same NAT gateway in a single AZ, but I thought to mention it in case the reasoning behind CDK creating separate route tables by default might seem strange.

EXPERT
answered 2 years ago
EXPERT
reviewed 2 years ago
0

Thanks, this did the trick.

I'm let with one more standard unused route table, which I can probably undo with Cfn on the VPC. But for now everything is correctly linked.

The use case is a Web application behind an ALB with databases in separate subnets. So the public subnets are for the ALB and API gateway, and the rest is all private.

answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.