rusty neuronnotes
Practical CDK - Custom VPC Network
Introduction
In this post, we’ll create a simple, custom VPC Network using AWS CDK. With this stack, we’ll set up a network with three subnets each for different purposes.
Since these posts are focused on practicality, I won’t talk about best practices, etc. as you’ll find better resources about these subjects and details in AWS Documentation. See Useful Resources at the end of the page.
Let’s start with opening a directory and creating our CDK application.
# Let's create a folder for our CDK application.
mkdir -p practical-cdk/custom-vpc && cd $_
# Initialize the CDK application.
npx aws-cdk@2.x init app --language typescript
It’s totally optional, but let’s commit this initial state.
git add .
git commit -m 'Initial commit for custom-vpc-stack'
Now we can start writing our constructor.
VPC Network
As usual, let’s import the necessary libraries in ./lib/custom-vpc-stack.ts
and set the class attributes.
// ...
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ssm from 'aws-cdk-lib/aws-ssm';
export class CustomVpcStack extends Stack {
customVPC: ec2.IVpc;
customVPCPublicSubnetIds: string[] = [];
customVPCPrivateSubnetIds: string[] = [];
customVPCIsolatedSubnetIds: string[] = [];
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
}
}
Constructors
Following constructors as a whole should be in the CustomVpcStack
.
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// VPC Network
//
// Notes: For CIDR block, the recommended private IPv4 address ranges
// are specified in RFC 1918.
//
// One thing to be careful about is that if you plan to peer
// this VPC with "another" VPC, the said VPCs' CIDR block ranges
// should not overlap with each other.
//
// Also, the subnets in a particular VPC, cannot have overlapping
// CIDR block ranges with each other.
//
this.customVPC = new ec2.Vpc(this, `custom-vpc`, {
vpcName: `custom-vpc`,
cidr: '10.1.0.0/16',
natGateways: 1,
maxAzs: 3,
subnetConfiguration: [
{
name: 'public-subnet-1',
subnetType: ec2.SubnetType.PUBLIC,
// 4096 IP Addresses
cidrMask: 20
},
{
name: 'private-subnet-1',
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
// 4096 IP Addresses
cidrMask: 20
},
{
name: 'isolated-subnet-1',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
// 256 IP Addresses
cidrMask: 24
}
]
});
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Subnet Tagging and Exports
//
// Notes: We are changing the names of our subnets to have them in a
// somewhat convenient convention. We're also exporting our
// subnet ids for each category of subnets to fetch them easily
// over SSM when we need them; e.g. in a serverless.yaml file.
//
// The names will result in the format of:
// custom-vpc-public-subnet-1-us-west-2a
// custom-vpc-private-subnet-1-us-west-2a
// custom-vpc-isolated-subnet-1-us-west-2a
//
for (const subnet of this.customVPC.publicSubnets) {
cdk.Aspects.of(subnet).add(
new cdk.Tag(
'Name',
`${this.customVPC.node.id}-${subnet.node.id.replace(/Subnet[0-9]$/, '')}-${subnet.availabilityZone}`
)
);
this.customVPCPublicSubnetIds.push(subnet.subnetId);
};
new ssm.StringListParameter(this, `custom-ssm-public-subnets`, {
parameterName: `/practical-cdk/custom-vpc/public.subnet.ids`,
stringListValue: this.customVPCPublicSubnetIds,
});
for (const subnet of this.customVPC.privateSubnets) {
cdk.Aspects.of(subnet).add(
new cdk.Tag(
'Name',
`${this.customVPC.node.id}-${subnet.node.id.replace(/Subnet[0-9]$/, '')}-${subnet.availabilityZone}`
)
);
this.customVPCPrivateSubnetIds.push(subnet.subnetId);
};
new ssm.StringListParameter(this, `custom-ssm-private-subnets`, {
parameterName: `/practical-cdk/custom-vpc/private.subnet.ids`,
stringListValue: this.customVPCPrivateSubnetIds,
});
for (const subnet of this.customVPC.isolatedSubnets) {
cdk.Aspects.of(subnet).add(
new cdk.Tag(
'Name',
`${this.customVPC.node.id}-${subnet.node.id.replace(/Subnet[0-9]$/, '')}-${subnet.availabilityZone}`
)
);
this.customVPCIsolatedSubnetIds.push(subnet.subnetId);
};
new ssm.StringListParameter(this, `custom-ssm-isolated-subnets`, {
parameterName: `/practical-cdk/custom-vpc/isolated.subnet.ids`,
stringListValue: this.customVPCIsolatedSubnetIds,
});
As a final step, we should uncomment the env
prop in the CustomVpcStack
definition. Open ./bin/custom-vpc.ts
and change the stack constructor with the following code.
new CustomVpcStack(app, 'CustomVpcStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
Deployment
Let’s check if we missed something by running the synthesizer.
npm run cdk synth
If the CloudFormation template is created without any problem, we are ready to apply our stack.
npm run cdk bootstrap # If it's the first time you are deploying a CDK application
npm run cdk deploy
Check the changes and approve them if it looks OK. Once it’s finished, you should be ready to use and deploy resources into your private network.
VPC Peering
VPC Peering, as the name suggests, is connecting two VPCs and leting the resources within these networks access each other as if they are in the same network. This is quite useful in many scenarios. One of the big advantages is that it’s possible to peer with a network that’s in another AWS account. The main rule is to make sure that these networks don’t have overlapping CIDR blocks.
The AWS documentation on this subject is really thorough, so I’ll cut my comments short. However, since this is a blog post related to AWS CDK, it would be amiss if we don’t give an example. Unfortunately, an L2 constructor for this purpose isn’t available yet. So, we’re going to use the raw CloudFormation constructor.
To peer two networks with VPC, one should make sure that the networks are created and ready beforehand. Another thing to make sure of is that you have to peer the subnets as well and it should be done both ways.
The following code is just an example and won’t be used in the application we’ve created above. Of course, you could experiment with that yourself.
////////////////////////////////////////////////////////////////////////////////
//
// VPC Peering
//
this.vpcPeering = new ec2.CfnVPCPeeringConnection(this, `peering-two-vpcs`, {
vpcId: this.firstVPC.vpcId,
peerVpcId: this.secondVPC.vpcId,
});
////////////////////////////////////////////////////////////////////////////////
//
// Subnet Peerings
//
//
// firstVPC to secondVPC
this.firstVPC.publicSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new ec2.CfnRoute(this, `public-subnets-of-first-to-second-${index}`, {
destinationCidrBlock: this.secondVPC.vpcCidrBlock,
routeTableId,
vpcPeeringConnectionId: this.vpcPeering.ref,
});
});
this.firstVPC.privateSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new ec2.CfnRoute(this, `private-subnets-of-first-to-second-${index}`, {
destinationCidrBlock: this.secondVPC.vpcCidrBlock,
routeTableId,
vpcPeeringConnectionId: this.vpcPeering.ref,
});
});
this.firstVPC.isolatedSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new ec2.CfnRoute(this, `isolated-subnets-of-first-to-second-${index}`, {
destinationCidrBlock: this.secondVPC.vpcCidrBlock,
routeTableId,
vpcPeeringConnectionId: this.vpcPeering.ref,
});
});
// secondVPC to firstVPC
this.secondVPC.publicSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new ec2.CfnRoute(this, `public-subnets-of-second-to-first-${index}`, {
destinationCidrBlock: this.firstVPC.vpcCidrBlock,
routeTableId,
vpcPeeringConnectionId: this.vpcPeering.ref,
});
});
this.secondVPC.privateSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new ec2.CfnRoute(this, `private-subnets-of-second-to-first-${index}`, {
destinationCidrBlock: this.firstVPC.vpcCidrBlock,
routeTableId,
vpcPeeringConnectionId: this.vpcPeering.ref,
});
});
this.secondVPC.isolatedSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new ec2.CfnRoute(this, `isolated-subnets-of-second-to-first-${index}`, {
destinationCidrBlock: this.firstVPC.vpcCidrBlock,
routeTableId,
vpcPeeringConnectionId: this.vpcPeering.ref,
});
});
Clean Up
To clean up what we’ve done, we can simply run cdk destroy
and approve. It’ll take care of everything.
npm run cdk destroy
Useful Resources
© 2024 rusty neuron ― Textlog theme by Heiswayi Nrird