## Task 45 The Nautilus DevOps team is tasked with enabling internet access for an EC2 instance running in a private subnet. This instance should be able to upload a test file to a public S3 bucket once it can access the internet. To achieve this, the team must set up a NAT Gateway in a public subnet within the same VPC. 1) A VPC named xfusion-priv-vpc and a private subnet xfusion-priv-subnet have already been created. 2) An EC2 instance named xfusion-priv-ec2 is already running in the private subnet. 3) The EC2 instance is configured with a cron job that uploads a test file to a bucket xfusion-nat-285654546 once internet is accessible. Your task is to: Create a public subnet named xfusion-pub-subnet in the same VPC. Create an Internet Gateway and attach it to the VPC. Create a route table xfusion-pub-rt and associate it with the public subnet. Allocate an Elastic IP and create a NAT Gateway named xfusion-natgw. Update the private route table to route 0.0.0.0/0 traffic via the NAT Gateway. Once complete, verify that the EC2 instance can reach the internet by confirming the presence of the test file in the S3 bucket xfusion-nat-285654546. After completing all the configuration, please wait a few minutes for the test file to appear in the bucket, as it may take 2–3 minutes. ### Solution # NAT Gateway Task (xfusion-priv-vpc) The **managed NAT Gateway** version — contrast with the earlier NAT *instance* task, which needed a self-managed EC2 box with source/dest-check disabled, IP forwarding, and iptables MASQUERADE. A NAT **Gateway** is a fully AWS-managed resource: no instance, no OS config, no source/dest check, auto-scaling and HA within its AZ. You just place it in a public subnet, give it an EIP, and route the private subnet's default route at it. Much less to get wrong. The architecture: private subnet → its route table `0.0.0.0/0 → NAT GW` → NAT GW (in public subnet) → public subnet's route table `0.0.0.0/0 → IGW` → internet → S3. Two route tables, two different default routes. Run on `aws-client`. ## Phase 1 — Discover VPC + private subnet ```bash REGION=us-east-1 VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=xfusion-priv-vpc \ --region $REGION --query 'Vpcs[0].VpcId' --output text) VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids $VPC_ID --region $REGION \ --query 'Vpcs[0].CidrBlock' --output text) PRIV_SUBNET=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=xfusion-priv-subnet" \ --region $REGION --query 'Subnets[0].SubnetId' --output text) PRIV_AZ=$(aws ec2 describe-subnets --subnet-ids $PRIV_SUBNET --region $REGION \ --query 'Subnets[0].AvailabilityZone' --output text) ``` ## Phase 2 — Public subnet + Internet Gateway ```bash # Pick a free /24 in the VPC for the public subnet BASE=$(echo $VPC_CIDR | cut -d. -f1-2) USED=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \ --region $REGION --query 'Subnets[].CidrBlock' --output text | tr '\t' '\n') for i in $(seq 1 254); do CAND="${BASE}.${i}.0/24" echo "$USED" | grep -Fxq "$CAND" || { PUB_CIDR=$CAND; break; } done # Public subnet (same AZ as private keeps NAT traffic in-AZ, avoids cross-AZ charges) PUB_SUBNET=$(aws ec2 create-subnet \ --vpc-id $VPC_ID --cidr-block $PUB_CIDR \ --availability-zone $PRIV_AZ --region $REGION \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=xfusion-pub-subnet}]' \ --query 'Subnet.SubnetId' --output text) aws ec2 modify-subnet-attribute --subnet-id $PUB_SUBNET \ --map-public-ip-on-launch --region $REGION # Internet Gateway (reuse if the VPC already has one) IGW_ID=$(aws ec2 describe-internet-gateways \ --filters "Name=attachment.vpc-id,Values=$VPC_ID" \ --region $REGION --query 'InternetGateways[0].InternetGatewayId' --output text) if [ "$IGW_ID" = "None" ] || [ -z "$IGW_ID" ]; then IGW_ID=$(aws ec2 create-internet-gateway --region $REGION \ --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=xfusion-igw}]' \ --query 'InternetGateway.InternetGatewayId' --output text) aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID --region $REGION fi ``` ## Phase 3 — Public route table (→ IGW), associate with public subnet ```bash PUB_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID --region $REGION \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=xfusion-pub-rt}]' \ --query 'RouteTable.RouteTableId' --output text) aws ec2 create-route --route-table-id $PUB_RT \ --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID --region $REGION aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB_SUBNET --region $REGION ``` This route table makes `xfusion-pub-subnet` genuinely public — its `0.0.0.0/0` points at the IGW. **The NAT Gateway must live in this subnet** so its own outbound traffic (forwarding on behalf of the private instance) can reach the internet via the IGW. ## Phase 4 — Elastic IP + NAT Gateway ```bash # Allocate an EIP for the NAT GW NAT_EIP_ALLOC=$(aws ec2 allocate-address --domain vpc --region $REGION \ --tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=xfusion-nat-eip}]' \ --query 'AllocationId' --output text) # Create the NAT Gateway IN THE PUBLIC SUBNET NATGW_ID=$(aws ec2 create-nat-gateway \ --subnet-id $PUB_SUBNET \ --allocation-id $NAT_EIP_ALLOC \ --region $REGION \ --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=xfusion-natgw}]' \ --query 'NatGateway.NatGatewayId' --output text) # NAT GW takes ~1-2 min to become available — wait for it # aws ec2 wait nat-gateway-available --nat-gateway-ids $NATGW_ID --region $REGION STATE="" until [ "$STATE" = "available" ]; do STATE=$(aws ec2 describe-nat-gateways --nat-gateway-ids $NATGW_ID --region $REGION \ --query 'NatGateways[0].State' --output text) echo "$NATGW_ID: $STATE" [ "$STATE" = "available" ] || sleep 10 done ``` Two must-get-right points: - **NAT GW goes in the PUBLIC subnet, not the private one.** This trips people constantly. The NAT GW needs a path to the internet for the traffic it forwards, so it sits in the public subnet (which routes to the IGW). The *private* instance then routes *to* the NAT GW. Putting the NAT GW in the private subnet creates a routing loop with no internet path — nothing works. - **A NAT GW requires an EIP** (public NAT GWs). `allocate-address` → pass its allocation ID to `create-nat-gateway`. The EIP is the NAT GW's public-facing address that S3 sees. ## Phase 5 — Private route table → NAT Gateway ```bash # Find the route table serving the private subnet (explicit assoc, else main) PRIV_RT=$(aws ec2 describe-route-tables \ --filters "Name=association.subnet-id,Values=$PRIV_SUBNET" \ --region $REGION --query 'RouteTables[0].RouteTableId' --output text) if [ "$PRIV_RT" = "None" ] || [ -z "$PRIV_RT" ]; then PRIV_RT=$(aws ec2 describe-route-tables \ --filters "Name=vpc-id,Values=$VPC_ID" "Name=association.main,Values=true" \ --region $REGION --query 'RouteTables[0].RouteTableId' --output text) fi # Point the private default route at the NAT GW (create, or replace if one exists) aws ec2 create-route --route-table-id $PRIV_RT \ --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NATGW_ID --region $REGION 2>/dev/null || \ aws ec2 replace-route --route-table-id $PRIV_RT \ --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NATGW_ID --region $REGION ``` This is the step that actually gives the private instance internet access: its subnet's `0.0.0.0/0` now flows to the NAT GW (`--nat-gateway-id`, not `--gateway-id` which is for IGWs). The instance keeps *no* public IP — outbound-only internet via NAT, which is exactly the private-instance pattern. ## Verify ```bash # NAT GW available aws ec2 describe-nat-gateways --nat-gateway-ids $NATGW_ID --region $REGION \ --query 'NatGateways[0].{State:State,Subnet:SubnetId,EIP:NatGatewayAddresses[0].PublicIp}' # Private route table points at the NAT GW aws ec2 describe-route-tables --route-table-ids $PRIV_RT --region $REGION \ --query 'RouteTables[0].Routes[?DestinationCidrBlock==`0.0.0.0/0`]' # Poll the bucket for the cron-uploaded test file (2-3 min after config) for n in $(seq 1 8); do FILES=$(aws s3 ls s3://xfusion-nat-285654546/ --region $REGION 2>/dev/null) if [ -n "$FILES" ]; then echo "SUCCESS — bucket contents:"; echo "$FILES"; break; fi echo "waiting for cron upload... ($n)"; sleep 30 done ``` Want: NAT GW `State: available` with its EIP; the private route table showing `0.0.0.0/0 → nat-...`; and a file appearing in the bucket within 2–3 minutes. **The file appearing is the end-to-end proof** — the private instance (no public IP) reached S3 entirely through the NAT Gateway. ## NAT Gateway vs NAT Instance (the contrast) | | NAT Gateway (this task) | NAT Instance (earlier task) | |---|---|---| | Management | Fully AWS-managed | You run/patch an EC2 box | | Source/dest check | N/A (managed) | Must disable manually | | iptables / IP forwarding | None | You configure MASQUERADE + `ip_forward` | | HA / scaling | Automatic within AZ | Single instance, you handle HA | | Cost | Higher hourly + data processing | Just the EC2 instance | The NAT Gateway is the production-standard choice; the NAT instance exists mostly for cost-sensitive or learning scenarios. This task is the "right way." ## Debug if the file doesn't appear 1. **NAT GW stuck in `pending`** → wait longer (up to 2 min), or it `failed` (usually the EIP was already in use — allocate a fresh one). 2. **Private route wrong** → confirm `0.0.0.0/0` points at `nat-...` (NAT GW), not `igw-...`. A private subnet routing to an IGW directly doesn't work without a public IP on the instance. 3. **Public subnet's route table missing the IGW route** → the NAT GW itself can't reach the internet, so forwarded traffic dies. Confirm `xfusion-pub-rt` has `0.0.0.0/0 → igw-...` and is associated with the public subnet the NAT GW lives in. 4. **Give it the full 2–3 min** — the cron runs on an interval; the file won't appear instantly even once networking is correct.