Creating a VPC with AWS CDK: A Guide for Former CloudFormation Users
The AWS CDK(Cloud Development Kit) is an open-source framework to provision and manage the cloud resources using familiar programming languages.
Before AWS launched the AWS CDK in 2019, AWS CloudFormation was the only resource-provisioning tool that AWS officially supports in a so-called IaC(Infrastructure as code) way. In fact, the AWS CDK leverages the AWS CloudFormation internally.
Familiarity with AWS CloudFormation is also useful, as the output of an AWS CDK program is an AWS CloudFormation template.
I have used AWS CloudFormation to create and manage most of the AWS resources: VPC, IAM, EC2, RDS, S3, Route 53, ECR and many others. The downside of AWS CloudFormation is that it only accepts YAML or JSON format. This template-based approach is somewhat verbose and not as flexible as the object-oriented approach that the AWS CDK provides.
The AWS CDK has some advantages over AWS CloudFormation:
- supports familiar programming languages
- easier to use parameters, conditionals, loops, composition, and etc.
- supports code completion with IDE
In this post, I’m going to create a custom VPC with AWS CDK. The client language for the demonstration is Python. I will also show the CloudFormation templates for comparison between the two different approaches.
All examples in this post are written in L1 constructs in order to focus on the fundamentals.
These are essentially the same as the AWS CloudFormation resource types.
These constructs have names that begin with Cfn
.
The end result of this tutorial is as follows:
- 2 public and 2 private subnets in different AZs for better availability
- an internet gateway to communicate outside of the VPC
- 2 NAT gateways in each AZ for resources in private subnets
You can think of this architecture as a good starting point for most web applications.
Prerequisites
- AWS account
- AWS CLI
- Node.js 10.13 or later
- AWS CDK Toolkit (
npm install -g aws-cdk
) - Preferred programming language (e.g. TypeScript, Python, C#)
0. Bootstrapping and Creating the app
Bootstrapping is a process of provisioning the necessary resources for deployments. The resources include an Amazon S3 bucket for storing files and IAM roles.
To bootstrap, run:
cdk [--profile string] bootstrap aws://ACCOUNT-NUMBER/REGION
You can confirm that a new stack is created by using AWS CLI:
aws [--profile string] cloudformation list-stacks --stack-status-filter CREATE_COMPLETE
{
"StackSummaries": [
{
"StackId": "arn:aws:cloudformation:...",
"StackName": "CDKToolkit",
"TemplateDescription": "This stack includes resources needed to deploy AWS CDK apps into this environment",
"CreationTime": "...",
"LastUpdatedTime": "...",
"StackStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
After bootstrapping, create a new CDK project:
mkdir my-cdk
cd my-cdk
cdk init app --language python
The created files look as follows:
.
├── README.md
├── app.py
├── cdk.json
├── my_cdk
│ ├── __init__.py
│ └── my_cdk_stack.py
├── requirements-dev.txt
├── requirements.txt
├── source.bat
└── tests
├── __init__.py
└── unit
├── __init__.py
└── test_my_cdk_stack.py
When it’s done, simply install the libraries in your preferred virtual environment:
pip install -r requirements.txt
And specify the environment for your stack in app.py
file in your AWS CDK project:
#!/usr/bin/env python3
import aws_cdk as cdk
from my_cdk.my_cdk_stack import MyCdkStack
app = cdk.App()
MyCdkStack(app, "MyCdkStack", env=cdk.Environment(account="my_account_num", region="ap-northeast-2"))
app.synth()
For production stacks, we recommend that you explicitly specify the environment for each stack in your app using the env property.
You are now ready to deploy the AWS resources.
1. VPC & Subnets
The CIDR rules are as follows:
Subnet ID | CIDR | AZ |
---|---|---|
PublicSubnetA | 10.0.0.0/24 | A |
PublicSubnetB | 10.0.1.0/24 | B |
PrivateSubnetA | 10.0.10.0/24 | A |
PrivateSubnetB | 10.0.11.0/24 | B |
An AWS CloudFormation template to provision this VPC and subnets is:
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
AppName:
Description: The app name.
Type: String
Env:
Description: The deployment environment.
Type: String
AllowedValues:
- dev
- staging
- prod
Default: dev
Mappings:
SubnetConfig:
VPC:
CIDR: "10.0.0.0/16"
Public0:
CIDR: "10.0.0.0/24"
Public1:
CIDR: "10.0.1.0/24"
Private0:
CIDR: "10.0.10.0/24"
Private1:
CIDR: "10.0.11.0/24"
AZRegions:
ap-northeast-2: # Asia Pacific (Seoul)
AZs: ["a", "b", "c", "d"]
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock:
Fn::FindInMap:
- SubnetConfig
- VPC
- CIDR
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub ${AppName}-vpc
- Key: Env
Value: !Ref Env
PublicSubnet0:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone:
Fn::Sub:
- "${AWS::Region}${AZ}"
- AZ: !Select [0, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
CidrBlock:
Fn::FindInMap:
- SubnetConfig
- Public0
- CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Network
Value: public
- Key: Name
Value: !Join
- "-"
- - !Ref AppName
- public
- !Select [0, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
- Key: Env
Value: !Ref Env
DependsOn: VPC
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone:
Fn::Sub:
- "${AWS::Region}${AZ}"
- AZ: !Select [1, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
CidrBlock:
Fn::FindInMap:
- SubnetConfig
- Public1
- CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Network
Value: public
- Key: Name
Value: !Join
- "-"
- - !Ref AppName
- public
- !Select [1, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
- Key: Env
Value: !Ref Env
DependsOn: VPC
PrivateSubnet0:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone:
Fn::Sub:
- "${AWS::Region}${AZ}"
- AZ: !Select [0, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
CidrBlock:
Fn::FindInMap:
- SubnetConfig
- Private0
- CIDR
Tags:
- Key: Network
Value: private
- Key: Name
Value: !Join
- "-"
- - !Ref AppName
- private
- !Select [0, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
- Key: Env
Value: !Ref Env
DependsOn: VPC
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone:
Fn::Sub:
- "${AWS::Region}${AZ}"
- AZ: !Select [1, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
CidrBlock:
Fn::FindInMap:
- SubnetConfig
- Private1
- CIDR
Tags:
- Key: Network
Value: private
- Key: Name
Value: !Join
- "-"
- - !Ref AppName
- private
- !Select [1, !FindInMap ["AZRegions", !Ref "AWS::Region", "AZs"]]
- Key: Env
Value: !Ref Env
DependsOn: VPC
Note that I intentionally added ENV
variable to specify the deployment environment to follow the best practices.
I also set a Mappings
to predefine the CIDRs for better maintainability.
The AWS CloudFormation template above can be transformed into a Python file By editing my_cdk/my_cdk_stack.py
:
from aws_cdk import Stack, aws_ec2, CfnTag
from constructs import Construct
class MyCdkStack(Stack):
ENV: str = "dev"
APP_NAME: str = "myservice"
CIDRS: dict[str, str] = {
"vpc": "10.0.0.0/16",
"public0": "10.0.0.0/24",
"public1": "10.0.1.0/24",
"private0": "10.0.10.0/24",
"private1": "10.0.11.0/24",
}
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# The code that defines your stack goes here
vpc = aws_ec2.CfnVPC(
self,
id="VPC",
cidr_block=self.CIDRS["vpc"],
enable_dns_hostnames=True,
enable_dns_support=True,
instance_tenancy="default",
tags=[
CfnTag(key="Name", value=f"{self.APP_NAME}-vpc"),
CfnTag(key="Env", value=self.ENV),
],
)
public_subnet0 = aws_ec2.CfnSubnet(
self,
id="PublicSubnet0",
vpc_id=vpc.ref,
availability_zone=self.availability_zones[0],
cidr_block=self.CIDRS["public0"],
map_public_ip_on_launch=True,
tags=[
CfnTag(key="Network", value="public"),
CfnTag(key="Name", value=f"{self.APP_NAME}-public-a"),
CfnTag(key="Env", value=self.ENV),
],
)
public_subnet1 = aws_ec2.CfnSubnet(
self,
id="PublicSubnet1",
vpc_id=vpc.ref,
availability_zone=self.availability_zones[1],
cidr_block=self.CIDRS["public1"],
map_public_ip_on_launch=True,
tags=[
CfnTag(key="Network", value="public"),
CfnTag(key="Name", value=f"{self.APP_NAME}-public-b"),
CfnTag(key="Env", value=self.ENV),
],
)
private_subnet0 = aws_ec2.CfnSubnet(
self,
id="PrivateSubnet0",
vpc_id=vpc.ref,
availability_zone=self.availability_zones[0],
cidr_block=self.CIDRS["private0"],
tags=[
CfnTag(key="Network", value="private"),
CfnTag(key="Name", value=f"{self.APP_NAME}-private-a"),
CfnTag(key="Env", value=self.ENV),
],
)
private_subnet1 = aws_ec2.CfnSubnet(
self,
id="PrivateSubnet1",
vpc_id=vpc.ref,
availability_zone=self.availability_zones[1],
cidr_block=self.CIDRS["private1"],
tags=[
CfnTag(key="Network", value="private"),
CfnTag(key="Name", value=f"{self.APP_NAME}-private-b"),
CfnTag(key="Env", value=self.ENV),
],
)
Here are some benefits you can find in this comparison:
- more concise representation (the custom
!Ref
tag to.ref
) - easier reference to availability zones
The next step is to synthesize the stack to create an AWS CloudFormation template:
cdk synth
This command reads the account and region information in app.py
file and generates the corresponding AWS CloudFormation templates in cdk.out
directory in JSON format.
The output of the command may look quite different from the template above, but they essentially create the same VPC.
To deploy our VPC, run:
cdk [--profile string] deploy
Confirm that a new stack named MyCdkStack
is created by using AWS CLI:
aws [--profile string] cloudformation list-stack-resources --stack-name MyCdkStack
{
"StackResources": [
{
"StackName": "MyCdkStack",
"StackId": "arn:aws:cloudformation:...",
"LogicalResourceId": "PrivateSubnet0",
"PhysicalResourceId": "...",
"ResourceType": "AWS::EC2::Subnet",
"Timestamp": "...",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"StackName": "MyCdkStack",
"StackId": "arn:aws:cloudformation:...",
"LogicalResourceId": "PrivateSubnet1",
"PhysicalResourceId": "...",
"ResourceType": "AWS::EC2::Subnet",
"Timestamp": "...",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"StackName": "MyCdkStack",
"StackId": "arn:aws:cloudformation:...",
"LogicalResourceId": "PublicSubnet0",
"PhysicalResourceId": "...",
"ResourceType": "AWS::EC2::Subnet",
"Timestamp": "...",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"StackName": "MyCdkStack",
"StackId": "arn:aws:cloudformation:...",
"LogicalResourceId": "PublicSubnet1",
"PhysicalResourceId": "...",
"ResourceType": "AWS::EC2::Subnet",
"Timestamp": "...",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
},
{
"StackName": "MyCdkStack",
"StackId": "arn:aws:cloudformation:...",
"LogicalResourceId": "VPC",
"PhysicalResourceId": "...",
"ResourceType": "AWS::EC2::VPC",
"Timestamp": "...",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
}
]
}
You can further confirm that our new VPC or subnets are configured as we wanted by using AWS CLI:
aws [--profile string] ec2 describe-vpcs --vpc-ids (vpc-id)
{
"Vpcs": [
{
"CidrBlock": "10.0.0.0/16",
"DhcpOptionsId": "...",
"State": "available",
"VpcId": "...",
"OwnerId": "...",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "...",
"CidrBlock": "10.0.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": [
{
"Key": "aws:cloudformation:logical-id",
"Value": "VPC"
},
{
"Key": "Name",
"Value": "myservice-vpc"
},
{
"Key": "aws:cloudformation:stack-id",
"Value": "arn:aws:cloudformation:..."
},
{
"Key": "Env",
"Value": "dev"
},
{
"Key": "aws:cloudformation:stack-name",
"Value": "MyCdkStack"
}
]
}
]
}
2. Internet & NAT Gateways
The AWS CloudFormation template to create an Internet gateway and two NAT gateways:
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Network
Value: public
- Key: Name
Value: !Sub ${AppName}-igw
- Key: Env
Value: !Ref Env
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
DependsOn: [VPC, InternetGateway]
ElasticIP0:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
ElasticIP1:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NATGateway0:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt ElasticIP0.AllocationId
SubnetId: !Ref PublicSubnet0
DependsOn: PublicSubnet0
NATGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt ElasticIP1.AllocationId
SubnetId: !Ref PublicSubnet1
DependsOn: PublicSubnet1
The AWS CDK contructs:
internet_gateway = aws_ec2.CfnInternetGateway(
self,
id="InternetGateway",
tags=[
CfnTag(key="Network", value="public"),
CfnTag(key="Name", value=f"{self.APP_NAME}-igw"),
CfnTag(key="Env", value=self.ENV),
],
)
aws_ec2.CfnVPCGatewayAttachment(
self,
id="VPCGatewayAttachment",
vpc_id=vpc.ref,
internet_gateway_id=internet_gateway.ref,
)
eip0 = aws_ec2.CfnEIP(self, id="ElasticIP0", domain="vpc")
eip1 = aws_ec2.CfnEIP(self, id="ElasticIP1", domain="vpc")
nat_gateway0 = aws_ec2.CfnNatGateway(
self,
id="NATGateway0",
allocation_id=eip0.attr_allocation_id,
subnet_id=public_subnet0.ref,
)
nat_gateway1 = aws_ec2.CfnNatGateway(
self,
id="NATGateway1",
allocation_id=eip1.attr_allocation_id,
subnet_id=public_subnet1.ref,
)
To update the stack, issue:
cdk [--profile string] synth
cdk [--profile string] deploy
From now on, I will skip the synth
and deploy
steps.
3. NACL & Route Tables
The route tables I’m going to create are as follows:
-
PublicRouteTable
Destination Target 10.0.0.0/16 local 0.0.0.0/0 InternetGateway -
PrivateRouteTable0
Destination Target 10.0.0.0/16 local 0.0.0.0/0 NATGateway0 -
PrivateRouteTable1
Destination Target 10.0.0.0/16 local 0.0.0.0/0 NATGateway1
The AWS CloudFormation template to create all necessary route tables and routes:
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Network
Value: public
- Key: "Name"
Value: !Sub ${AppName}-public-rt
- Key: Env
Value: !Ref Env
DependsOn: VPC
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
DependsOn: [PublicRouteTable, InternetGateway]
PublicSubnetRouteTableAssociation0:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet0
RouteTableId: !Ref PublicRouteTable
DependsOn: [PublicSubnet0, PublicRouteTable]
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
DependsOn: [PublicSubnet1, PublicRouteTable]
PublicNetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VPC
Tags:
- Key: Network
Value: public
- Key: "Name"
Value: !Sub ${AppName}-public-nacl
- Key: Env
Value: !Ref Env
DependsOn: VPC
PublicNetworkAclInboundEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: "0.0.0.0/0"
Egress: false
NetworkAclId: !Ref PublicNetworkAcl
Protocol: -1
RuleAction: allow
RuleNumber: 100
PortRange:
From: 0
To: 65535
DependsOn: PublicNetworkAcl
PublicNetworkAclOutboundEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
CidrBlock: "0.0.0.0/0"
Egress: true
NetworkAclId: !Ref PublicNetworkAcl
Protocol: -1
RuleAction: allow
RuleNumber: 100
PortRange:
From: 0
To: 65535
DependsOn: PublicNetworkAcl
PublicSubnetNetworkAclAssociation0:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId: !Ref PublicSubnet0
NetworkAclId: !Ref PublicNetworkAcl
DependsOn: [PublicSubnet0, PublicNetworkAcl]
PublicSubnetNetworkAclAssociation1:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId: !Ref PublicSubnet1
NetworkAclId: !Ref PublicNetworkAcl
DependsOn: [PublicSubnet1, PublicNetworkAcl]
PrivateRouteTable0:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub ${AppName}-private-rt0
- Key: Env
Value: !Ref Env
DependsOn: VPC
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub ${AppName}-private-rt1
- Key: Env
Value: !Ref Env
DependsOn: VPC
PrivateRoute0:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable0
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NATGateway0
DependsOn: [PrivateRouteTable0, NATGateway0]
PrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NATGateway1
DependsOn: [PrivateRouteTable1, NATGateway1]
PrivateSubnetRouteTableAssociation0:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet0
RouteTableId: !Ref PrivateRouteTable0
DependsOn: [PrivateSubnet0, PrivateRouteTable0]
PrivateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable1
DependsOn: [PrivateSubnet1, PrivateRouteTable1]
The AWS CDK contructs:
public_route = aws_ec2.CfnRouteTable(
self,
id="PublicRouteTable",
vpc_id=vpc.ref,
tags=[
CfnTag(key="Network", value="public"),
CfnTag(key="Name", value=f"{self.APP_NAME}-public-rt"),
CfnTag(key="Env", value=self.ENV),
],
)
aws_ec2.CfnRoute(
self,
id="PublicRoute",
route_table_id=public_route.ref,
destination_cidr_block="0.0.0.0/0",
gateway_id=internet_gateway.ref,
)
aws_ec2.CfnSubnetRouteTableAssociation(
self,
id="PublicSubnetRouteTableAssociation0",
route_table_id=public_route.ref,
subnet_id=public_subnet0.ref,
)
aws_ec2.CfnSubnetRouteTableAssociation(
self,
id="PublicSubnetRouteTableAssociation1",
route_table_id=public_route.ref,
subnet_id=public_subnet1.ref,
)
public_network_acl = aws_ec2.CfnNetworkAcl(
self,
id="PublicNetworkAcl",
vpc_id=vpc.ref,
tags=[
CfnTag(key="Network", value="public"),
CfnTag(key="Name", value=f"{self.APP_NAME}-public-nacl"),
CfnTag(key="Env", value=self.ENV),
],
)
aws_ec2.CfnNetworkAclEntry(
self,
id="PublicNetworkAclInboundEntry",
cidr_block="0.0.0.0/0",
egress=False,
network_acl_id=public_network_acl.ref,
protocol=-1,
rule_action="allow",
rule_number=100,
port_range=aws_ec2.CfnNetworkAclEntry.PortRangeProperty(from_=0, to=65535),
)
aws_ec2.CfnNetworkAclEntry(
self,
id="PublicNetworkAclOutboundEntry",
cidr_block="0.0.0.0/0",
egress=True,
network_acl_id=public_network_acl.ref,
protocol=-1,
rule_action="allow",
rule_number=100,
port_range=aws_ec2.CfnNetworkAclEntry.PortRangeProperty(from_=0, to=65535),
)
aws_ec2.CfnSubnetNetworkAclAssociation(
self,
id="PublicSubnetNetworkAclAssociation0",
subnet_id=public_subnet0.ref,
network_acl_id=public_network_acl.ref,
)
aws_ec2.CfnSubnetNetworkAclAssociation(
self,
id="PublicSubnetNetworkAclAssociation1",
subnet_id=public_subnet1.ref,
network_acl_id=public_network_acl.ref,
)
private_route_table0 = aws_ec2.CfnRouteTable(
self,
id="PrivateRouteTable0",
vpc_id=vpc.ref,
tags=[
CfnTag(key="Name", value=f"{self.APP_NAME}-private-rt0"),
CfnTag(key="Env", value=self.ENV),
],
)
private_route_table1 = aws_ec2.CfnRouteTable(
self,
id="PrivateRouteTable1",
vpc_id=vpc.ref,
tags=[
CfnTag(key="Name", value=f"{self.APP_NAME}-private-rt1"),
CfnTag(key="Env", value=self.ENV),
],
)
aws_ec2.CfnRoute(
self,
id="PrivateRoute0",
route_table_id=private_route_table0.ref,
destination_cidr_block="0.0.0.0/0",
nat_gateway_id=nat_gateway0.ref,
)
aws_ec2.CfnRoute(
self,
id="PrivateRoute1",
route_table_id=private_route_table1.ref,
destination_cidr_block="0.0.0.0/0",
nat_gateway_id=nat_gateway1.ref,
)
aws_ec2.CfnSubnetRouteTableAssociation(
self,
id="PrivateSubnetRouteTableAssociation0",
subnet_id=private_subnet0.ref,
route_table_id=private_route_table0.ref,
)
aws_ec2.CfnSubnetRouteTableAssociation(
self,
id="PrivateSubnetRouteTableAssociation1",
subnet_id=private_subnet1.ref,
route_table_id=private_route_table1.ref,
)
Lastly, you can list up all of the resources in our VPC stack by using AWS CLI:
aws [--profile string] cloudformation describe-stack-resources --stack-name MyCdkStack
To destroy all resources in the stack, run:
cdk [--profile string] destroy
Are you sure you want to delete: MyCdkStack (y/n)? y
MyCdkStack: destroying...
...
✅ MyCdkStack: destroyed
About Construct Levels
Constructs are categorized into three different levels.
- layer 1 (AWS CloudFormation-only)
- layer 2 (Curated)
- layer 3 (Patterns)
If you are someone with prior experience with AWS CloudFormation, there’s not much to learn to use L1 (layer 1) constructs. But if you are not, using L1 constructs requires a complete understanding of the underlying AWS resource models.
L2 constructs provide a higher-level API than L1 constructs. They might lack fine-grained configuration like L1 constructs, but come with reasonable defaults and convenient methods.
L3 constructs include multiple kind of resources to provision a complete AWS architectures.
You can choose whatever constructs in different layers for particular use cases. However, you can’t use L2 property types with L1 constructs, or vice versa.