Introduction
This is the third installment of the "AWS CDK 100 Drill Exercises" series.
For an overview of the AWS CDK 100 Drill Exercises, please refer to this article.
Following S3 Fundamentals in Exercise #001 and IAM Basics in Exercise #002, we'll tackle Amazon Virtual Private Cloud (VPC). VPC is the foundation of AWS networking and is essential for securely isolating resources and controlling communication.
Why VPC?
- Networking Foundation: Many AWS resources are deployed within a VPC
- Security Layer: Implements security controls at the network level
- Production Necessity: Almost all production environments require VPC
- CDK Integration: Understanding how CDK abstracts VPC resources
- Best Practices: Learning proper network design from the start
What You'll Learn
- Two approaches to creating VPCs with CDK (default and custom)
- Subnet design and CIDR block calculations
- Differences between public/private subnets
- Roles of NAT Gateway and Internet Gateway
- Traffic monitoring with VPC Flow Logs
- VPC Endpoint implementation (Gateway/Interface)
- Secure SSH access via EC2 Instance Connect Endpoint
- Security group cross-referencing and avoiding circular dependencies
📁 Code Repository: All code examples for this exercise are available on GitHub.
Architecture Overview
Here's what we'll build in this exercise:
We'll implement two patterns:
- Default VPC: Created with CDK default settings
- Custom VPC: Created with custom configurations
1. Default VPC (CDKDefault)
- VPC created with CDK's default settings
- Fully functional VPC with minimal code
2. Custom VPC (CustomVPC)
- VPC with custom configuration
- Up to 3 Availability Zones (varies by region)
- 6 types of subnets (External, Management, Internal, Application, Isolated, TransitGateway)
- Custom CIDR blocks
- Single NAT Gateway (cost optimization)
- VPC Flow Logs implementation
- All traffic logged to S3
- Rejected traffic logged to CloudWatch Logs
- VPC Endpoints
- Gateway Endpoints (S3, DynamoDB)
- Interface Endpoints (Systems Manager)
- EC2 Instance Connect Endpoint
- Secure SSH access without going through the internet
- Security group cross-referencing
Prerequisites
To proceed with this exercise, you'll need:
- AWS CLI v2 installed and configured
- Node.js 20+
- AWS CDK CLI (
npm install -g aws-cdk) - Basic knowledge of TypeScript
- AWS Account (can be done with Free Tier)
- Basic understanding of VPC concepts (CIDR, subnets, routing)
Project Directory Structure
vpc-basics/
├── bin/
│ └── vpc-basics.ts # Application entry point
├── lib/
│ └── stacks/
│ ├── vpc-cdkdefault-stack.ts # CDK Default VPC Creation Stack
│ └── vpc-basics-stack.ts # Custom VPC stack definition
├── test/
│ ├── compliance/
│ │ └── cdk-nag.test.ts # CDK Nag compliance tests
│ ├── snapshot/
│ │ └── snapshot.test.ts # Snapshot tests
│ └── unit/
│ ├── vpc-cdkdefault.test.ts # Unit tests
│ └── vpc-basics.test.ts # Unit tests
├── cdk.json
├── package.json
└── tsconfig.json
Pattern 1: Understanding CDK Default VPC
This is the simplest VPC creation. CDK automatically handles all configurations.
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class VpcCDKDefaultStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create VPC with CDK default settings
new ec2.Vpc(this, 'CDKDefault', {});
}
}
Default Configuration Details
What CDK automatically configures:
- CIDR Block:
10.0.0.0/16(65,536 IP addresses) - Availability Zones: Available AZs in the region (up to 3)
- Subnet Configuration:
- Public Subnet: One per AZ
- Private Subnet: One per AZ
- NAT Gateway: One per AZ (high availability configuration)
- Internet Gateway: One
- Route Tables: Automatically configured
Generated resources:
CDKDefault VPC (10.0.0.0/16)
├── AZ-1
│ ├── Public Subnet (10.0.0.0/19) - 8,192 IPs
│ ├── Private Subnet (10.0.96.0/19) - 8,192 IPs
│ └── NAT Gateway
├── AZ-2
│ ├── Public Subnet (10.0.32.0/19) - 8,192 IPs
│ ├── Private Subnet (10.0.128.0/19) - 8,192 IPs
│ └── NAT Gateway
├── AZ-3
│ ├── Public Subnet (10.0.64.0/19) - 8,192 IPs
│ ├── Private Subnet (10.0.160.0/19) - 8,192 IPs
│ └── NAT Gateway
└── Internet Gateway
Pros and Cons of Default VPC
Pros:
- Minimal code
- Production-ready configuration
- High availability (multiple AZs, multiple NAT Gateways)
- Follows best practices
Cons:
- High cost (multiple NAT Gateways)
- No CIDR range customization
- Fixed number of subnets
💡 For development environments, we recommend using a custom VPC for cost reduction.
Pattern 2: Creating a Custom VPC
In real projects, you'll create VPCs tailored to requirements.
import { pascalCase } from "change-case-commonjs";
const vpcName = [
pascalCase(props.project), // Project name
pascalCase(props.environment), // Environment identifier (dev/test/prod)
'CustomVPC', // Purpose
]
.join('/');
const customVpc = new ec2.Vpc(this, 'CustomVPC', {
vpcName,
ipAddresses: ec2.IpAddresses.cidr('10.1.0.0/16'),
maxAzs: 3, // Use up to 3 AZs
natGateways: 1, // Only 1 NAT Gateway (cost optimization)
subnetConfiguration: [
{
cidrMask: 26, // 64 IPs per AZ (/26 = 2^(32-26) = 64)
name: 'External',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 27, // 32 IPs per AZ (/27 = 2^(32-27) = 32)
name: 'Management',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 22, // 1024 IPs per AZ (/22 = 2^(32-22) = 1024)
name: 'Internal',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 22, // 1024 IPs per AZ
name: 'Application',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24, // 256 IPs per AZ (/24 = 2^(32-24) = 256)
name: 'Isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
{
cidrMask: 28, // 16 IPs per AZ (/28 = 2^(32-28) = 16)
name: 'TransitGateway',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
}
],
});
Understanding CIDR Calculations
CIDR (Classless Inter-Domain Routing) defines IP address ranges.
The basic calculation formula:
Available IP Addresses = 2^(32 - CIDR mask)
/16 = 2^(32-16) = 2^16 = 65,536 IPs
/22 = 2^(32-22) = 2^10 = 1,024 IPs
/24 = 2^(32-24) = 2^8 = 256 IPs
/26 = 2^(32-26) = 2^6 = 64 IPs
/27 = 2^(32-27) = 2^5 = 32 IPs
/28 = 2^(32-28) = 2^4 = 16 IPs
For each subnet, AWS reserves 5 IPv4 addresses. (Documentation here)
-
.0: Network address -
.1: VPC router -
.2: DNS -
.3: Reserved for future use -
.255: Broadcast (not used in VPC but reserved)
The actual usable IPs are calculated value - 5.
Understanding Subnet Types
| Type | Description | Routing | Use Cases |
|---|---|---|---|
PUBLIC |
Route to Internet Gateway | Internet via IGW | Load balancers, NAT Gateway, Bastion |
PRIVATE_WITH_EGRESS |
Route to NAT Gateway | Outbound only via NAT | Application servers, VPC Endpoints |
PRIVATE_ISOLATED |
No route to internet | VPC only | Databases, Transit Gateway attachments |
Subnet Configuration Design
CustomVPC (10.1.0.0/16) - 65,536 IPs
├── AZ-1 (ap-northeast-1a)
│ ├── ExternalSubnet-1 (10.1.0.0/26) - 64 IPs (59 usable)
│ ├── ManagementSubnet-1 (10.1.0.64/27) - 32 IPs (27 usable)
│ ├── InternalSubnet-1 (10.1.0.128/22) - 1,024 IPs (1,019 usable)
│ ├── ApplicationSubnet-1 (10.1.4.128/22) - 1,024 IPs (1,019 usable)
│ ├── IsolatedSubnet-1 (10.1.8.128/24) - 256 IPs (251 usable)
│ └── TransitGatewaySubnet-1 (10.1.9.0/28) - 16 IPs (11 usable)
├── AZ-2 (ap-northeast-1c)
│ ├── ExternalSubnet-2 (10.1.0.96/26) - 64 IPs (59 usable)
│ ├── ManagementSubnet-2 (10.1.0.160/27) - 32 IPs (27 usable)
│ ├── InternalSubnet-2 (10.1.9.16/22) - 1,024 IPs (1,019 usable)
│ ├── ApplicationSubnet-2 (10.1.13.16/22) - 1,024 IPs (1,019 usable)
│ ├── IsolatedSubnet-2 (10.1.17.16/24) - 256 IPs (251 usable)
│ └── TransitGatewaySubnet-2 (10.1.18.0/28) - 16 IPs (11 usable)
├── AZ-3 (ap-northeast-1d)
│ ├── ExternalSubnet-3 (10.1.0.192/26) - 64 IPs (59 usable)
│ ├── ManagementSubnet-3 (10.1.0.224/27) - 32 IPs (27 usable)
│ ├── InternalSubnet-3 (10.1.17.32/22) - 1,024 IPs (1,019 usable)
│ ├── ApplicationSubnet-3 (10.1.21.32/22) - 1,024 IPs (1,019 usable)
│ ├── IsolatedSubnet-3 (10.1.25.32/24) - 256 IPs (251 usable)
│ └── TransitGatewaySubnet-3 (10.1.26.32/28) - 16 IPs (11 usable)
├── NAT Gateway (AZ-1 only)
└── Internet Gateway
Why 6 Types of Subnets?
This doesn't mean you should always create many subnets, but we've created 6 as examples of subnet segmentation. Dividing subnets too finely wastes reserved IPs per subnet, so adjust the subnet configuration according to your actual project requirements. For basic projects, two types—Public and Private Subnets—are often sufficient.
- ExternalSubnet (Public): For internet-facing load balancers, NAT Gateway placement
- ManagementSubnet (Public): For Bastion Hosts and operational management
- InternalSubnet (Private with Egress): For VPC Endpoints, internal load balancers
- ApplicationSubnet (Private with Egress): For application servers, ECS tasks, Lambda functions
- IsolatedSubnet (Private Isolated): For database layers like RDS, ElastiCache
- TransitGatewaySubnet (Private Isolated): For Transit Gateway attachments
VPC Flow Logs Implementation
VPC Flow Logs capture information about IP traffic flowing to and from network interfaces in your VPC.
Flow Logs to S3
import * as s3 from 'aws-cdk-lib/aws-s3';
// Create secure S3 bucket
const flowLogBucket = new s3.Bucket(this, 'FlowLogBucket', {
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true, // Require HTTPS
removalPolicy: cdk.RemovalPolicy.DESTROY, // ⚠️ For dev (use RETAIN for prod)
autoDeleteObjects: true, // ⚠️ For dev (use RETAIN removalPolicy for prod)
});
// Log all traffic to S3
customVpc.addFlowLog('FlowLogToS3', {
destination: ec2.FlowLogDestination.toS3(
flowLogBucket,
'vpcFlowLog/',
{
fileFormat: ec2.FlowLogFileFormat.PLAIN_TEXT,
hiveCompatiblePartitions: true, // For Athena queries
perHourPartition: true,
}
),
trafficType: ec2.FlowLogTrafficType.ALL,
});
Generated log file structure:
s3://bucket-name/vpcFlowLog/
└── AWSLogs/
└── aws-account-id=123456789012/
└── aws-service=vpcflowlogs/
└── aws-region=ap-northeast-1/
└── year=2024/
└── month=12/
└── day=20/
└── hour=12/
└── <AWSAccountID>_vpcflowlogs_<region>_xxxxx.log.gz
Flow Logs to CloudWatch Logs
import * as logs from 'aws-cdk-lib/aws-logs';
// Log rejected traffic only to CloudWatch Logs
customVpc.addFlowLog('FlowLog', {
destination: ec2.FlowLogDestination.toCloudWatchLogs(
new logs.LogGroup(this, 'FlowLogGroup', {
retention: logs.RetentionDays.ONE_WEEK,
removalPolicy: cdk.RemovalPolicy.DESTROY,
})
),
trafficType: ec2.FlowLogTrafficType.REJECT, // Rejected only
});
Flow Logs Comparison
| Destination | Pros | Cons | Use Cases |
|---|---|---|---|
| S3 | - Ideal for long-term storage - Queryable with Athena - Cost effective |
- No real-time analysis | Auditing, compliance, long-term analysis |
| CloudWatch Logs | - Real-time monitoring - Metric filters - Alerting |
- Higher storage costs | Security monitoring, immediate threat detection |
Flow Log Format
version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status
2 123456789012 eni-1234abcd 10.0.1.5 203.0.113.5 49152 80 6 10 5000 1670000000 1670000060 ACCEPT OK
Key fields (documentation):
-
srcaddr/dstaddr: Source/destination IP addresses -
srcport/dstport: Source/destination ports -
protocol: IP protocol number (6=TCP, 17=UDP) -
action: ACCEPT or REJECT
VPC Endpoints Implementation
VPC Endpoints enable private connections between your VPC and AWS services.
Gateway Endpoints (S3, DynamoDB)
const endpointSubnets = customVpc.selectSubnets({
subnetGroupName: 'Internal',
});
// S3 Gateway Endpoint
customVpc.addGatewayEndpoint('S3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
subnets: [{ subnets: endpointSubnets.subnets }],
});
// DynamoDB Gateway Endpoint
customVpc.addGatewayEndpoint('DynamoDBEndpoint', {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
subnets: [{ subnets: endpointSubnets.subnets }],
});
Interface Endpoints (Systems Manager)
// SSM Interface Endpoint
customVpc.addInterfaceEndpoint('SSMEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SSM,
subnets: {
subnets: endpointSubnets.subnets,
},
});
// SSM Messages Interface Endpoint
customVpc.addInterfaceEndpoint('SSMMessagesEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
subnets: {
subnets: endpointSubnets.subnets,
},
});
Comparison: Gateway vs Interface Endpoints
| Feature | Gateway Endpoint | Interface Endpoint |
|---|---|---|
| Supported Services | S3, DynamoDB | Most AWS services |
| Pricing | Free | Hourly + data transfer |
| Implementation | Route table entry | ENI in subnet |
| DNS | Service endpoint | Private DNS |
| High Availability | Managed by AWS | Per AZ |
Why Use VPC Endpoints?
- Security: Traffic doesn't traverse the internet
- Performance: Lower latency
- Cost savings: Reduce NAT Gateway data transfer charges
- Compliance: Keep data within AWS network
EC2 Instance Connect Endpoint
EC2 Instance Connect Endpoint allows SSH connections to EC2 instances in private subnets without public IPs or Bastion hosts.
Please note that the Instance Connect Endpoint has the following restrictions. For details, please refer to the documentation here.
- Maximum number of EC2 Instance Connect Endpoints per AWS account per AWS Region: 5
- Maximum number of EC2 Instance Connect Endpoints per VPC: 1
- Maximum number of EC2 Instance Connect Endpoints per subnet: 1
- Maximum number of concurrent connections per EC2 Instance Connect Endpoint: 20
// Security group for Instance Connect
const ec2InstanceConnectsg = new ec2.SecurityGroup(this, 'EC2InstanceConnectSG', {
vpc: customVpc,
description: 'Security group for EC2 Instance Connect Endpoint',
allowAllOutbound: false, // Deny default outbound
});
// Select first subnet in InternalSubnet
const iceSubnet = endpointSubnets.subnets[0];
// Create Instance Connect Endpoint
new ec2.CfnInstanceConnectEndpoint(this, 'EC2InstanceConnectEndpoint', {
subnetId: iceSubnet.subnetId,
preserveClientIp: false,
securityGroupIds: [ec2InstanceConnectsg.securityGroupId],
});
// Security group for EC2 instances
const ec2sg = new ec2.SecurityGroup(this, 'EC2SG', {
vpc: customVpc,
description: 'Security group for EC2 instances',
allowAllOutbound: true,
});
Avoiding Circular Dependencies with Security Groups
⚠️ Important: Using addIngressRule or addEgressRule creates circular dependencies between two security groups. While cdk synth succeeds, cdk deploy will fail.
Wrong approach (causes circular dependency):
// ❌ This will cause an error
ec2sg.addIngressRule(
ec2InstanceConnectsg,
ec2.Port.tcp(22),
'Allow SSH from Instance Connect'
);
ec2InstanceConnectsg.addEgressRule(
ec2sg,
ec2.Port.tcp(22),
'Allow SSH to EC2 instances'
);
// ❌ Dev-DrillexercisesVpcBasics failed: ValidationError:
Circular dependency between resources:
[EC2InstanceConnectSG697BC6D2, EC2InstanceConnectEndpoint, EC2SG244E8056]
Correct approach (using CloudFormation resources directly):
// ✅ Use CfnSecurityGroupIngress/Egress
// Ingress: Instance Connect SG -> EC2 SG
new ec2.CfnSecurityGroupIngress(this, 'AllowSSHFromInstanceConnect', {
ipProtocol: 'tcp',
fromPort: 22,
toPort: 22,
groupId: ec2sg.securityGroupId,
sourceSecurityGroupId: ec2InstanceConnectsg.securityGroupId,
description: 'Allow SSH from Instance Connect SG',
});
// Egress: Instance Connect SG -> EC2 SG
new ec2.CfnSecurityGroupEgress(this, 'AllowSSHToEC2SG', {
ipProtocol: 'tcp',
fromPort: 22,
toPort: 22,
groupId: ec2InstanceConnectsg.securityGroupId,
destinationSecurityGroupId: ec2sg.securityGroupId,
description: 'Allow SSH to EC2 SG',
});
Using Instance Connect Endpoint
To connect to an EC2 instance via Instance Connect Endpoint using AWS CLI, use the following command. Refer to the documentation for details.
# SSH connection via Instance Connect Endpoint
aws ec2-instance-connect ssh \
--instance-id i-1234567890abcdef0 \
--connection-type eice
Why Use Instance Connect Endpoint?
Comparison with traditional methods:
| Method | Public IP | Bastion Host | SSH Key Management | Additional Cost |
|---|---|---|---|---|
| EC2 with Public IP | Required | ✅Not needed | Required | ✅None |
| Bastion Host | Required for Bastion | Required | Required | EC2 charges |
| Instance Connect Endpoint | ✅Not needed | ✅Not needed | ✅Not needed | Yes |
Benefits:
- No internet exposure
- No SSH key management (IAM authentication)
- No Bastion host needed (cost reduction)
- All connections logged in CloudTrail
CloudFormation Output Example
Key parts of the CloudFormation template generated after deployment:
{
"Resources": {
"CustomVPC616E3387": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.1.0.0/16",
"EnableDnsHostnames": true,
"EnableDnsSupport": true,
"Tags": [
{
"Key": "Name",
"Value": "Myproject/Dev/CustomVPC"
}
]
}
},
"CustomVPCS3Endpoint": {
"Type": "AWS::EC2::VPCEndpoint",
"Properties": {
"ServiceName": "com.amazonaws.ap-northeast-1.s3",
"VpcEndpointType": "Gateway",
"RouteTableIds": [...]
}
},
"CustomVPCSSMEndpoint": {
"Type": "AWS::EC2::VPCEndpoint",
"Properties": {
"ServiceName": "com.amazonaws.ap-northeast-1.ssm",
"VpcEndpointType": "Interface",
"PrivateDnsEnabled": true,
"SecurityGroupIds": [...],
"SubnetIds": [...]
}
}
}
}
Deployment and Validation
Deployment
# Check differences
cdk diff --project=myproject --env=dev
# Deploy
cdk deploy "**" --project=myproject --env=dev
Validation
- Verify VPC
# List VPCs
aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=*Myproject/Dev/CustomVPC*"
# Check subnets
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=<vpc-id>"
- Verify VPC Endpoints
# List VPC Endpoints
aws ec2 describe-vpc-endpoints \
--filters "Name=vpc-id,Values=<vpc-id>"
- Verify Flow Logs
# Check logs in S3 bucket
aws s3 ls s3://<bucket-name>/vpcFlowLog/ --recursive
# Check CloudWatch Logs
aws logs describe-log-streams \
--log-group-name <log-group-name>
- Verify Security Groups
# List security groups
aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=<vpc-id>"
Cleanup
# Delete stacks
cdk destroy "**" --project=myproject --env=dev
# Force delete without confirmation
cdk destroy "**" --force --project=myproject --env=dev
💡 Note
- This example sets
autoDeleteObjects: truefor development environments, so S3 buckets are automatically deleted when the stack is deleted - For production, consider using
removalPolicy: cdk.RemovalPolicy.RETAINto protect data
Best Practices
Network Design
- CIDR Planning: Design CIDR blocks considering future expansion
- Multiple AZs: Use at least 2 AZs for high availability
- Specify maximum with
maxAzs, but actual number depends on the region - Tokyo region (ap-northeast-1) has a maximum of 3 AZs available
- Specify maximum with
- Subnet Isolation: Use different subnets for different tiers (Web, App, DB)
- Reserved IPs: Account for the 5 IPs AWS reserves in each subnet
Security
- Least Privilege: Open only necessary ports in security groups
- Flow Logs: Enable flow logs for all VPCs
- VPC Endpoints: Use to avoid going through the internet
- Private Subnets: Place databases in completely isolated subnets
- NACLs: Provide additional layers with Network ACLs when needed
Cost Optimization
- NAT Gateway Count: One NAT Gateway is sufficient for development
- VPC Endpoints: Choose based on usage frequency
- High frequency: Interface Endpoint (worth the hourly rate)
- Low frequency: Via NAT Gateway (pay-as-you-go)
- Flow Logs: Use S3 for reduced long-term storage costs
- Resource Tags: Tag all resources for cost tracking
Operations
- Naming Conventions: Use consistent naming patterns
Example: {project}-{environment}-{resource}-{account}-{region}
- Tagging Strategy: Use Environment, Project, Owner, CostCenter tags
- Monitoring: Monitor VPC metrics in CloudWatch
- Documentation: Document network diagrams and CIDR allocations
Testing
- Snapshot Tests: Clarify change differences
- Unit Tests: Validate resource existence and configuration
- Compliance Tests: Check security best practices with CDK Nag
Cost Estimation
Example Pricing for Key Components (Tokyo Region)
For CDK Default VPC:
| Resource | Quantity | Estimated Monthly Cost |
|---|---|---|
| VPC | 1 | $0 (Free) |
| NAT Gateway | 3 | $133.92 ($0.062/hour × 24 × 30 × 3) |
| NAT Gateway Data Processing | 100GB | $18.6 ($0.062/GB × 3) |
| VPC Gateway Endpoint | - | $0 |
| VPC Interface Endpoint | - | $0 |
| Endpoint Data Processing | - | $0 |
| Total | - | ~$152.52/month |
For Custom VPC:
| Resource | Quantity | Estimated Monthly Cost |
|---|---|---|
| VPC | 1 | $0 (Free) |
| NAT Gateway | 1 | $39.42 ($0.062/hour × 24 × 30) |
| NAT Gateway Data Processing | 100GB | $6.20 ($0.062/GB) |
| VPC Gateway Endpoint | 2 | $0 |
| VPC Interface Endpoint | 2 | $15.12 ($0.012/hour × 2 × 24 × 30) |
| Endpoint Data Processing | 10GB | $0.12 ($0.012/GB) |
| S3 Storage (Flow Logs) | 50GB | $1.15 ($0.023/GB) |
| CloudWatch Logs (7-day retention) | 5GB | $0.35 ($0.033/GB) |
| Total | - | ~$62.36/month |
💡 Cost Reduction Tips
- Development: One NAT Gateway is sufficient (multiple recommended for production) or use NAT instances with EC2
- Flow Logs: Can be temporarily disabled in development
- VPC Endpoints: Choose based on usage frequency
Summary
In this exercise, we learned VPC fundamentals through AWS CDK.
What We Learned
- VPC Basics: Differences between CDK default and custom VPC
- Subnet Design: CIDR calculations and 3-tier subnet configuration
- Traffic Management: NAT Gateway, Internet Gateway, routing
- Visibility: Network monitoring with VPC Flow Logs
- VPC Endpoints: When to use Gateway vs Interface Endpoints
- Secure Access: Implementing Instance Connect Endpoint
- Security Groups: Cross-referencing and avoiding circular dependencies
- Best Practices: Security, cost, and operations perspectives
Key Takeaways
- Network Design: Proper CIDR planning is crucial for future expansion
- Security Layers: Network-level isolation and control
- Visibility: Flow log monitoring is essential
- Cost Optimization: Choose NAT Gateway count and VPC Endpoints wisely
- High Availability: Multi-AZ and failover design
References
- Amazon VPC Official Documentation
- VPC Best Practices
- VPC Flow Logs
- VPC Endpoints
- EC2 Instance Connect Endpoint
- My GitHub Repository
Let's continue learning practical AWS CDK patterns through the 100 drill exercises!
If you found this helpful, please ⭐ the repository!

Top comments (0)