Scenario
We need to prepare infrastructure for ECS tasks and EC2 instances. It has to span across at least two Availability Zones. These workloads require outbound internet access to download updates and call external APIs. However, inbound access is not allowed. Additionally application should send data to S3 in cost effective way and so we have to deploy necessary infrastructure for that traffic too.
Note: You are required to design the VPC networking architecture only. Creation of ECS clusters, services, or EC2 instances is not part of this task.
Task
Design and implement a VPC network architecture 10.0.0.0/16 that meets the following requirements:
- Network Isolation: Workloads must reside in subnets across two Availability Zones with no public IP addresses and must not be directly reachable from the internet.
- Egress Control: Workloads must be able to initiate outbound connections to the public internet over HTTP and HTTPS. The internet must not be able to initiate connections to them.
Note: You should create and configure a new non-default security group.
- Egress Restrictions: Outbound traffic from workloads should be limited to only the required protocols and destinations.
- Cost Awareness: The architecture should account for cost-efficient routing when accessing AWS-managed services, minimizing unnecessary NAT Gateway usage.
Note: You can use either the AWS Management Console or AWS CLI to complete this task.
Step 1: Create the VPC, Internet Gateway, and Subnets
# Create VPC
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --query 'Vpc.VpcId' --output text)
# Create Internet Gateway and attach to VPC
IGW_ID=$(aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID
# Get two AZs in the current region
AZ1=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0].ZoneName' --output text)
AZ2=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[1].ZoneName' --output text)
# Create Public Subnets (one per AZ)
PUBLIC_SUBNET_1=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24 --availability-zone $AZ1 --query 'Subnet.SubnetId' --output text)
PUBLIC_SUBNET_2=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.2.0/24 --availability-zone $AZ2 --query 'Subnet.SubnetId' --output text)
# Create Private Subnets (one per AZ)
PRIVATE_SUBNET_1=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.3.0/24 --availability-zone $AZ1 --query 'Subnet.SubnetId' --output text)
PRIVATE_SUBNET_2=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.4.0/24 --availability-zone $AZ2 --query 'Subnet.SubnetId' --output text)
Step 2: Configure the Public Route Table
PUBLIC_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
# Route internet traffic to the IGW
aws ec2 create-route --route-table-id $PUBLIC_RT --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
# Associate with both Public Subnets
aws ec2 associate-route-table --subnet-id $PUBLIC_SUBNET_1 --route-table-id $PUBLIC_RT
aws ec2 associate-route-table --subnet-id $PUBLIC_SUBNET_2 --route-table-id $PUBLIC_RT
Step 3: Create the NAT Gateway and Private Route Table
# Allocate an Elastic IP for the NAT Gateway
EIP_ALLOC=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
# Create NAT Gateway in a public subnet
NAT_GW=$(aws ec2 create-nat-gateway --subnet-id $PUBLIC_SUBNET_1 --allocation-id $EIP_ALLOC --query 'NatGateway.NatGatewayId' --output text)
# Wait for the NAT Gateway to become available
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_GW
# Create Private Route Table
PRIVATE_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
# Route internet traffic from private subnets to the NAT Gateway
aws ec2 create-route --route-table-id $PRIVATE_RT --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_GW
# Associate with both Private Subnets
aws ec2 associate-route-table --subnet-id $PRIVATE_SUBNET_1 --route-table-id $PRIVATE_RT
aws ec2 associate-route-table --subnet-id $PRIVATE_SUBNET_2 --route-table-id $PRIVATE_RT
A single NAT Gateway keeps costs down. It must sit in a public subnet so it can route traffic out through the Internet Gateway, acting as a proxy for the private subnet instances.
Step 4: Create the VPC Gateway Endpoint for S3
# Get region reliably (works even if aws configure is not set)
REGION=$(aws ec2 describe-availability-zones \
--query 'AvailabilityZones[0].RegionName' \
--output text)
SERVICE_NAME="com.amazonaws.$REGION.s3"
# Create the Gateway Endpoint and associate it with the Private Route Table
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name $SERVICE_NAME \
--vpc-endpoint-type Gateway \
--route-table-ids $PRIVATE_RT
Gateway Endpoints inject prefix-list routes into your route tables automatically. Any traffic destined for S3 IPs will use the endpoint (free) instead of the NAT Gateway (per-GB data processing charges).
Step 5: Configure the Security Group for Egress Control
# Create the Security Group
SG_ID=$(aws ec2 create-security-group --group-name private-app-sg --description "Private App SG" --vpc-id $VPC_ID --query 'GroupId' --output text)
# Revoke the default allow-all outbound rule
aws ec2 revoke-security-group-egress --group-id $SG_ID --ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'
# Allow outbound HTTPS (Port 443) and HTTP (Port 80) only
aws ec2 authorize-security-group-egress --group-id $SG_ID --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-egress --group-id $SG_ID --protocol tcp --port 80 --cidr 0.0.0.0/0
Security groups are stateful. With no inbound rules and restricted outbound rules, instances can initiate requests to external APIs, and the return traffic is automatically allowed back in.