Why This Matters
VPC networking is the backbone of every secure data engineering architecture on AWS. Without understanding subnets, route tables, security groups, and VPC endpoints, you cannot design data pipelines that are both secure and cost-effective. Misconfigured networking leads to either security vulnerabilities (publicly exposed databases) or unexpected costs (unnecessary NAT gateway data transfer charges). Interviewers expect you to articulate why specific network designs matter for data workloads.
What is a VPC?
A Virtual Private Cloud (VPC) is a logically isolated section of the AWS Cloud where you launch resources in a virtual network you define. Think of it as your own private data center in the cloud - you control IP addresses, subnets, route tables, gateways, and security.
Key Components
| Component | Purpose |
|---|---|
| VPC | Top-level container - one VPC per region |
| Subnet | A range of IP addresses within a VPC, mapped to an AZ |
| Internet Gateway (IGW) | Enables internet access for public subnets |
| NAT Gateway | Allows private subnets to reach the internet (outbound only) |
| Route Tables | Define traffic routing rules for subnets |
| Security Groups | Stateful virtual firewalls at instance level |
| NACLs | Stateless firewalls at subnet level |
VPC Sizing
A VPC uses a CIDR block (e.g., 10.0.0.0/16) giving you 65,536 IP addresses:
- /16: 65,536 IPs - large production environments
- /20: 4,096 IPs - medium workloads
- /24: 256 IPs - small proof-of-concept
Subnets and Route Tables
Subnets are the building blocks of VPC networking. Each subnet resides in a single Availability Zone and can be public or private.
Public vs Private Subnets
| Property | Public Subnet | Private Subnet |
|---|---|---|
| Internet Access | Direct via IGW | Via NAT Gateway only |
| Default Route | 0.0.0.0/0 to IGW | 0.0.0.0/0 to NAT GW |
| Public IP | Auto-assign enabled | Auto-assign disabled |
| Typical Use | Load balancers, Bastion hosts | Databases, app servers |
Route Table Rules
Every subnet is associated with a route table containing ordered routing rules.
{
"Routes": [
{
"Destination": "10.0.0.0/16",
"Target": "local",
"Status": "active"
},
{
"Destination": "0.0.0.0/0",
"Target": "igw-0abc1234def56789",
"Status": "active"
}
]
}
Key rules:
- The
localroute (VPC CIDR) is always present and cannot be removed - More specific routes always take precedence (longest prefix match)
- A subnet can only have one route table associated at a time
- Multiple subnets can share a route table
VPC Endpoints
VPC Endpoints allow private connections to AWS services without using an internet gateway, NAT device, VPN, or Direct Connect. Traffic stays within the AWS network.
Gateway vs Interface Endpoints
| Feature | Gateway Endpoint | Interface Endpoint |
|---|---|---|
| Supported Services | S3, DynamoDB only | 70+ AWS services |
| Cost | Free | ~$0.01/hr per AZ |
| How it works | Adds route to route table | Creates ENI in subnet |
| Scaling | Automatic | One per AZ for HA |
| Performance | VPC-routed (fastest) | AWS PrivateLink |
Gateway Endpoint Setup
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc1234def56789 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-0private123
Interface Endpoint Setup
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc1234def56789 \
--service-name com.amazonaws.us-east-1.redshift \
--subnet-ids subnet-0private456 \
--private-dns-enabled
Security Groups vs NACLs
Security is layered in AWS. Understanding the difference is critical for both architecture and interviews.
Security Groups (Stateful)
- Virtual firewall at the instance level
- Stateful: Inbound traffic automatically allows response outbound
- Only support allow rules (no deny rules)
- All rules evaluated before deciding to allow traffic
- Apply to one or more instances
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc1234 \
--protocol tcp \
--port 5439 \
--source-group sg-0abcd5678
Network ACLs (Stateless)
- Firewall at the subnet level
- Stateless: Return traffic must be explicitly allowed
- Support both allow and deny rules
- Rules evaluated in numerical order (lowest first)
- Apply to all instances in the subnet
VPC for Data Engineering
Data engineering workloads have specific networking requirements. Most data services should run in private subnets. Access to S3 and other AWS services should go through VPC endpoints.
Service Communication Matrix
| Service | Subnet Type | Inbound Ports | Outbound Needs |
|---|---|---|---|
| Redshift | Private/Data | 5439 (from app SG) | S3 endpoint, Glue endpoint |
| Airflow (MWAA) | Private/App | 443 (from ALB) | RDS, S3 endpoint |
| Glue | Private/App | None (serverless) | S3, Glue endpoint |
| EMR | Private/App | 443, 8443 (from app) | S3 endpoint, DynamoDB |
| Kinesis | Private/App | 443 (from source) | CloudWatch, S3 endpoint |
| MSK (Kafka) | Private/Data | 9092/9094 (from app) | S3, CloudWatch |
Security Group Patterns
# Redshift SG - Allow only from Airflow SG
aws ec2 authorize-security-group-ingress \
--group-id sg-redshift \
--protocol tcp \
--port 5439 \
--source-group sg-airflow
# Airflow SG - Allow from ALB SG
aws ec2 authorize-security-group-ingress \
--group-id sg-airflow \
--protocol tcp \
--port 443 \
--source-group sg-alb
# Glue SG - Allow outbound to Redshift
aws ec2 authorize-security-group-egress \
--group-id sg-glue \
--protocol tcp \
--port 5439 \
--source-group sg-redshift
Real-World Project Structure
vpc-configuration/
âââ main-vpc/
â âââ vpc.tf # VPC definition with CIDR
â âââ subnets.tf # Public, private app, private data
â âââ route-tables.tf # Route table associations
â âââ internet-gateway.tf # IGW for public subnets
â âââ nat-gateway.tf # NAT GW for private subnets
âââ endpoints/
â âââ s3-gateway.tf # Gateway endpoint for S3
â âââ dynamodb-gateway.tf # Gateway endpoint for DynamoDB
â âââ glue-interface.tf # Interface endpoint for Glue
â âââ redshift-interface.tf # Interface endpoint for Redshift
âââ security/
â âââ security-groups.tf # SG rules for data services
â âââ nacls.tf # NACL rules for subnet protection
â âââ flow-logs.tf # VPC Flow Logs configuration
âââ scripts/
â âââ create-vpc.py # VPC provisioning script
â âââ configure-endpoints.py # Endpoint setup automation
â âââ validate-network.py # Connectivity validation
âââ monitoring/
âââ flow-logs-dashboard.json # CloudWatch dashboard
âââ alarms.json # Network monitoring alarms
Production Python Code
import boto3
import json
from botocore.exceptions import ClientError
class VPCManager:
"""Manages VPC infrastructure for data engineering pipelines."""
def __init__(self, region='us-east-1'):
self.region = region
self.ec2 = boto3.client('ec2', region_name=region)
def create_data_platform_vpc(self, cidr_block='10.0.0.0/16'):
"""Create VPC with DNS support for data engineering."""
try:
vpc = self.ec2.create_vpc(CidrBlock=cidr_block)
vpc_id = vpc['Vpc']['VpcId']
self.ec2.create_tags(
Resources=[vpc_id],
Tags=[{'Key': 'Name', 'Value': 'data-platform-vpc'}]
)
self.ec2.modify_vpc_attribute(
VpcId=vpc_id,
EnableDnsSupport={'Value': True}
)
self.ec2.modify_vpc_attribute(
VpcId=vpc_id,
EnableDnsHostnames={'Value': True}
)
return vpc_id
except ClientError as e:
print(f"Error creating VPC: {e.response['Error']['Message']}")
raise
def create_subnets(self, vpc_id, availability_zones):
"""Create public, private app, and private data subnets."""
subnet_configs = {
'public': {'offset': 0, 'map_public_ip': True},
'private_app': {'offset': 10, 'map_public_ip': False},
'private_data': {'offset': 20, 'map_public_ip': False}
}
subnets = {}
for az_index, az in enumerate(availability_zones):
for subnet_type, config in subnet_configs.items():
third_octet = (az_index * 30) + config['offset']
cidr = f"10.0.{third_octet}.0/24"
subnet = self.ec2.create_subnet(
VpcId=vpc_id,
CidrBlock=cidr,
AvailabilityZone=f"{self.region}{az}",
TagSpecifications=[{
'ResourceType': 'subnet',
'Tags': [
{'Key': 'Name', 'Value': f'{subnet_type}-{az}'},
{'Key': 'Type', 'Value': subnet_type}
]
}]
)
subnet_id = subnet['Subnet']['SubnetId']
self.ec2.modify_subnet_attribute(
SubnetId=subnet_id,
MapPublicIpOnLaunch={'Value': config['map_public_ip']}
)
subnets[f"{subnet_type}_{az}"] = subnet_id
return subnets
def create_gateway_endpoint(self, vpc_id, service_name, route_table_ids):
"""Create a Gateway VPC endpoint (free for S3 and DynamoDB)."""
try:
response = self.ec2.create_vpc_endpoint(
VpcId=vpc_id,
ServiceName=service_name,
RouteTableIds=route_table_ids,
VpcEndpointType='Gateway'
)
endpoint_id = response['VpcEndpoint']['VpcEndpointId']
print(f"Created Gateway Endpoint: {endpoint_id} for {service_name}")
return endpoint_id
except ClientError as e:
print(f"Error creating gateway endpoint: {e.response['Error']['Message']}")
raise
def create_interface_endpoint(self, vpc_id, service_name, subnet_ids):
"""Create an Interface VPC endpoint (~$0.01/hr per AZ)."""
try:
response = self.ec2.create_vpc_endpoint(
VpcId=vpc_id,
ServiceName=service_name,
SubnetIds=subnet_ids,
VpcEndpointType='Interface',
PrivateDnsEnabled=True
)
endpoint_id = response['VpcEndpoint']['VpcEndpointId']
print(f"Created Interface Endpoint: {endpoint_id} for {service_name}")
return endpoint_id
except ClientError as e:
print(f"Error creating interface endpoint: {e.response['Error']['Message']}")
raise
def create_security_group(self, vpc_id, group_name, description):
"""Create a security group for data engineering resources."""
try:
response = self.ec2.create_security_group(
VpcId=vpc_id,
GroupName=group_name,
Description=description
)
group_id = response['GroupId']
self.ec2.create_tags(
Resources=[group_id],
Tags=[{'Key': 'Name', 'Value': group_name}]
)
return group_id
except ClientError as e:
print(f"Error creating security group: {e.response['Error']['Message']}")
raise
def add_sg_rule(self, group_id, protocol, port, source_sg):
"""Add ingress rule to security group from source security group."""
try:
self.ec2.authorize_security_group_ingress(
GroupId=group_id,
IpPermissions=[{
'IpProtocol': protocol,
'FromPort': port,
'ToPort': port,
'UserIdGroupPairs': [{'GroupId': source_sg}]
}]
)
print(f"Added rule: {protocol}/{port} from {source_sg} to {group_id}")
except ClientError as e:
if 'InvalidPermission.Duplicate' in str(e):
print(f"Rule already exists: {protocol}/{port} from {source_sg}")
else:
print(f"Error adding rule: {e.response['Error']['Message']}")
raise
def enable_flow_logs(self, vpc_id, log_group_name):
"""Enable VPC Flow Logs for network monitoring."""
try:
iam = boto3.client('iam')
role_name = 'VPCFlowLogsRole'
trust_policy = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "vpc-flow-logs.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
role = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=json.dumps(trust_policy)
)
iam.put_role_policy(
RoleName=role_name,
PolicyName='FlowLogsPolicy',
PolicyDocument=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
}]
})
)
response = self.ec2.create_flow_logs(
ResourceIds=[vpc_id],
ResourceType='VPC',
LogDestinationType='cloud-watch-logs',
LogGroupName=log_group_name,
DeliverLogsPermissionArn=role['Role']['Arn'],
TrafficType='ALL'
)
return response['FlowLogIds']
except ClientError as e:
print(f"Error enabling flow logs: {e.response['Error']['Message']}")
raise
if __name__ == '__main__':
manager = VPCManager(region='us-east-1')
vpc_id = manager.create_data_platform_vpc()
print(f"Created VPC: {vpc_id}")
subnets = manager.create_subnets(vpc_id, ['a', 'b', 'c'])
print(f"Created subnets: {list(subnets.keys())}")
manager.create_gateway_endpoint(
vpc_id,
'com.amazonaws.us-east-1.s3',
[subnets['private_data_a']]
)
manager.enable_flow_logs(vpc_id, '/aws/vpc/flowlogs')
Mathematical Formulas
NAT Gateway Cost Calculation
def calculate_nat_cost(gb_per_month, hours_per_month=730):
"""
Calculate monthly NAT Gateway cost.
Formula: Total Cost = (Hours * $0.045) + (GB * $0.045)
"""
hourly_cost = hours_per_month * 0.045
data_processing = gb_per_month * 0.045
total = hourly_cost + data_processing
return round(total, 2)
# Example: 1 TB/month through NAT Gateway
monthly_cost = calculate_nat_cost(1024)
print(f"NAT Gateway monthly cost: ${monthly_cost}")
# Output: $91.30 (hourly) + $46.08 (data) = $137.38
VPC Endpoint Savings
def calculate_endpoint_savings(data_gb_monthly, service='s3'):
"""
Calculate savings using VPC endpoints vs NAT Gateway.
NAT: $0.045/GB data processing
Gateway Endpoint: Free
Interface Endpoint: ~$0.01/hr per AZ
"""
nat_cost = data_gb_monthly * 0.045
if service == 's3':
endpoint_cost = 0
else:
endpoint_cost = 0.01 * 730
savings = nat_cost - endpoint_cost
return round(savings, 2)
# S3 with 5 TB/month: save $225/month with Gateway Endpoint
savings = calculate_endpoint_savings(5120, 's3')
print(f"Monthly savings with S3 Gateway Endpoint: ${savings}")
Performance Considerations
| Factor | Recommendation | Impact |
|---|---|---|
| Subnet Sizing | Plan CIDR blocks for growth | Avoids VPC recreation |
| AZ Distribution | Deploy resources across 2+ AZs | Protects against AZ failure |
| NAT Gateway | Deploy per-AZ for HA | Prevents cross-AZ traffic charges |
| VPC Endpoints | Use Gateway for S3/DynamoDB | Eliminates NAT data transfer costs |
| Security Groups | Reference SGs instead of IPs | Automatic updates when instances change |
| NACLs | Use for deny rules only | Avoids complexity with stateful rules |
| Flow Logs | Enable for debugging and compliance | Captures all network metadata |
Security Considerations
- Place all data stores in private subnets (no public IPs)
- Use VPC endpoints to keep AWS service traffic off the public internet
- Implement security groups with least-privilege ingress rules
- Use NACLs as secondary defense with explicit deny rules
- Enable VPC Flow Logs for network monitoring and compliance
- Deploy NAT Gateways in each AZ for high availability
- Use VPC peering or Transit Gateway for cross-VPC communication
- Restrict security group source to specific SGs, not CIDR ranges
- Enable VPC peering DNS resolution for cross-VPC access
- Use AWS Network Firewall for advanced threat protection
Common Pitfalls
| Pitfall | Consequence | Prevention |
|---|---|---|
| Public RDS/Redshift | Database exposed to internet | Always use private subnets |
| Missing VPC endpoints | Unnecessary NAT data transfer costs | Configure endpoints for S3, Glue, DynamoDB |
| Single-AZ deployment | Complete outage on AZ failure | Deploy across 2+ AZs |
| Using public IPs on private resources | Security vulnerability | Disable auto-assign public IP |
| Overly permissive SG rules | Unauthorized access to resources | Scope to specific source SGs |
| Not enabling Flow Logs | Cannot debug network issues | Enable for all VPCs |
| Wrong CIDR sizing | Requires VPC recreation | Plan for future growth |
| Single NAT Gateway | Single point of failure | Deploy per-AZ |
Interview Questions & Answers
Q1: What is the difference between a Security Group and a NACL?
Answer: Security Groups are stateful firewalls at the instance level - inbound traffic automatically allows the response outbound. NACLs are stateless firewalls at the subnet level - you must explicitly allow both inbound and outbound traffic. Security Groups only support allow rules; NACLs support both allow and deny rules. Security Groups are the primary firewall; NACLs are the secondary defense layer.
Q2: When would you use a Gateway VPC Endpoint vs an Interface Endpoint?
Answer: Use a Gateway Endpoint for S3 and DynamoDB - it's free, performs better (VPC-routed), and scales automatically. Use Interface Endpoints for all other services (Redshift, Glue, Kinesis) - they create ENIs in your subnets with a private DNS name, costing ~$0.01/hr per AZ.
Q3: Why should data engineering resources run in private subnets?
Answer: Private subnets have no direct internet access, reducing the attack surface. Data stores like Redshift and RDS should never be publicly accessible. Use NAT Gateways for outbound internet access and VPC Endpoints for AWS service access - this keeps traffic off the public internet and reduces data transfer costs.
Q4: How do you enable an EC2 instance in a private subnet to access S3?
Answer: Create a Gateway VPC Endpoint for S3 and associate it with the private subnet's route table. This adds a route for the S3 prefix list that routes traffic through the endpoint rather than through the internet. The instance can then use the S3 API endpoint without a public IP or NAT Gateway.
Q5: What is the purpose of VPC Flow Logs?
Answer: VPC Flow Logs capture metadata about IP traffic going to and from network interfaces. They are useful for troubleshooting connectivity issues, monitoring traffic patterns, detecting unauthorized traffic, and compliance auditing. Flow logs can be sent to CloudWatch Logs or S3.
Q6: How would you design a VPC for a data pipeline with Redshift, Airflow, and Glue?
Answer: Place Redshift in a private data subnet (no public IP). Run Airflow (MWAA) in a private app subnet. Use VPC endpoints for S3 and Glue. Security groups: ALB to Airflow (443), Airflow to Redshift (5439), Glue to Redshift (5439). Use separate route tables for public, private, and endpoint subnets.
Q7: How do you ensure high availability for NAT Gateways?
Answer: Deploy NAT Gateways in each Availability Zone with its own Elastic IP. Create separate route tables for private subnets in each AZ, each pointing to the local NAT Gateway. This ensures that if one AZ fails, other AZs' private instances can still reach the internet through their local NAT Gateway.
Q8: How do VPC Endpoints reduce data transfer costs?
Answer: Without VPC Endpoints, traffic between your VPC and AWS services traverses the public internet, incurring data transfer out charges (0.01/hr). For high-volume data pipelines, this can save thousands of dollars monthly.