DEV Community

Cover image for AWS CDK 100 Drill Exercises #003: VPC Basics — From Network Configuration to Security

AWS CDK 100 Drill Exercises #003: VPC Basics — From Network Configuration to Security

Level 200

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?

  1. Networking Foundation: Many AWS resources are deployed within a VPC
  2. Security Layer: Implements security controls at the network level
  3. Production Necessity: Almost all production environments require VPC
  4. CDK Integration: Understanding how CDK abstracts VPC resources
  5. 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:

Architecture Overview

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
Enter fullscreen mode Exit fullscreen mode

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', {});
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
    }
  ],
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

  1. ExternalSubnet (Public): For internet-facing load balancers, NAT Gateway placement
  2. ManagementSubnet (Public): For Bastion Hosts and operational management
  3. InternalSubnet (Private with Egress): For VPC Endpoints, internal load balancers
  4. ApplicationSubnet (Private with Egress): For application servers, ECS tasks, Lambda functions
  5. IsolatedSubnet (Private Isolated): For database layers like RDS, ElastiCache
  6. 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,
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 }],
});
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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?

  1. Security: Traffic doesn't traverse the internet
  2. Performance: Lower latency
  3. Cost savings: Reduce NAT Gateway data transfer charges
  4. 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,
});
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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',
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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": [...]
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Deployment and Validation

Deployment

# Check differences
cdk diff --project=myproject --env=dev

# Deploy
cdk deploy "**" --project=myproject --env=dev
Enter fullscreen mode Exit fullscreen mode

Validation

  1. 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>"
Enter fullscreen mode Exit fullscreen mode
  1. Verify VPC Endpoints
   # List VPC Endpoints
   aws ec2 describe-vpc-endpoints \
     --filters "Name=vpc-id,Values=<vpc-id>"
Enter fullscreen mode Exit fullscreen mode
  1. 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>
Enter fullscreen mode Exit fullscreen mode
  1. Verify Security Groups
   # List security groups
   aws ec2 describe-security-groups \
     --filters "Name=vpc-id,Values=<vpc-id>"
Enter fullscreen mode Exit fullscreen mode

Cleanup

# Delete stacks
cdk destroy "**" --project=myproject --env=dev

# Force delete without confirmation
cdk destroy "**" --force --project=myproject --env=dev
Enter fullscreen mode Exit fullscreen mode

💡 Note

  • This example sets autoDeleteObjects: true for development environments, so S3 buckets are automatically deleted when the stack is deleted
  • For production, consider using removalPolicy: cdk.RemovalPolicy.RETAIN to protect data

Best Practices

Network Design

  1. CIDR Planning: Design CIDR blocks considering future expansion
  2. 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
  3. Subnet Isolation: Use different subnets for different tiers (Web, App, DB)
  4. Reserved IPs: Account for the 5 IPs AWS reserves in each subnet

Security

  1. Least Privilege: Open only necessary ports in security groups
  2. Flow Logs: Enable flow logs for all VPCs
  3. VPC Endpoints: Use to avoid going through the internet
  4. Private Subnets: Place databases in completely isolated subnets
  5. NACLs: Provide additional layers with Network ACLs when needed

Cost Optimization

  1. NAT Gateway Count: One NAT Gateway is sufficient for development
  2. VPC Endpoints: Choose based on usage frequency
    • High frequency: Interface Endpoint (worth the hourly rate)
    • Low frequency: Via NAT Gateway (pay-as-you-go)
  3. Flow Logs: Use S3 for reduced long-term storage costs
  4. Resource Tags: Tag all resources for cost tracking

Operations

  1. Naming Conventions: Use consistent naming patterns
   Example: {project}-{environment}-{resource}-{account}-{region}
Enter fullscreen mode Exit fullscreen mode
  1. Tagging Strategy: Use Environment, Project, Owner, CostCenter tags
  2. Monitoring: Monitor VPC metrics in CloudWatch
  3. Documentation: Document network diagrams and CIDR allocations

Testing

  1. Snapshot Tests: Clarify change differences
  2. Unit Tests: Validate resource existence and configuration
  3. 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

  1. VPC Basics: Differences between CDK default and custom VPC
  2. Subnet Design: CIDR calculations and 3-tier subnet configuration
  3. Traffic Management: NAT Gateway, Internet Gateway, routing
  4. Visibility: Network monitoring with VPC Flow Logs
  5. VPC Endpoints: When to use Gateway vs Interface Endpoints
  6. Secure Access: Implementing Instance Connect Endpoint
  7. Security Groups: Cross-referencing and avoiding circular dependencies
  8. 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


Let's continue learning practical AWS CDK patterns through the 100 drill exercises!
If you found this helpful, please ⭐ the repository!

Top comments (0)