Amazon EKS — Networking Hands-On Lab
In this lab, you’ll build an EKS-ready VPC from scratch, launch a cluster into it, verify pod networking with the VPC CNI, and explore how pods get real VPC IP addresses.
Lab Objectives
- Create a production-grade VPC with public and private subnets
- Launch an EKS cluster using the custom VPC
- Verify pod-to-pod networking
- Inspect how VPC CNI assigns IPs to pods
- Test DNS resolution inside pods
- Clean up all resources
Prerequisites
aws --version # AWS CLI v2
eksctl version # eksctl
kubectl version --client # kubectl
Confirm your identity:
aws sts get-caller-identity
Step 1 — Create a VPC with Subnets
Create a VPC with 3 public and 3 private subnets:
# Create VPC
VPC_ID=$(aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=eks-lab-vpc}]' \
--query 'Vpc.VpcId' --output text)
echo "VPC: $VPC_ID"
# Enable DNS hostnames
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames
Create subnets:
# Public subnets
PUB_SUB_A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.1.0/24 --availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-pub-a},{Key=kubernetes.io/role/elb,Value=1}]' \
--query 'Subnet.SubnetId' --output text)
PUB_SUB_B=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 --availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-pub-b},{Key=kubernetes.io/role/elb,Value=1}]' \
--query 'Subnet.SubnetId' --output text)
# Private subnets
PRIV_SUB_A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.10.0/24 --availability-zone us-east-1a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-priv-a},{Key=kubernetes.io/role/internal-elb,Value=1}]' \
--query 'Subnet.SubnetId' --output text)
PRIV_SUB_B=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.11.0/24 --availability-zone us-east-1b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=eks-priv-b},{Key=kubernetes.io/role/internal-elb,Value=1}]' \
--query 'Subnet.SubnetId' --output text)
echo "Public: $PUB_SUB_A, $PUB_SUB_B"
echo "Private: $PRIV_SUB_A, $PRIV_SUB_B"
Create Internet Gateway and NAT Gateway
# Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=eks-lab-igw}]' \
--query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID
# Elastic IP for NAT Gateway
EIP_ID=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
# NAT Gateway in public subnet
NAT_ID=$(aws ec2 create-nat-gateway \
--subnet-id $PUB_SUB_A --allocation-id $EIP_ID \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=eks-lab-nat}]' \
--query 'NatGateway.NatGatewayId' --output text)
echo "Waiting for NAT Gateway to become available..."
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID
echo "NAT Gateway: $NAT_ID"
Configure Route Tables
# Public route table
PUB_RTB=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=eks-pub-rtb}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PUB_RTB --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
aws ec2 associate-route-table --route-table-id $PUB_RTB --subnet-id $PUB_SUB_A
aws ec2 associate-route-table --route-table-id $PUB_RTB --subnet-id $PUB_SUB_B
# Private route table
PRIV_RTB=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=eks-priv-rtb}]' \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PRIV_RTB --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_ID
aws ec2 associate-route-table --route-table-id $PRIV_RTB --subnet-id $PRIV_SUB_A
aws ec2 associate-route-table --route-table-id $PRIV_RTB --subnet-id $PRIV_SUB_B
Step 2 — Create the EKS Cluster
Now use eksctl with your custom VPC:
# Save as eks-networking-lab.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: networking-lab
region: us-east-1
version: "1.29"
vpc:
id: "vpc-REPLACE_WITH_VPC_ID"
subnets:
private:
us-east-1a:
id: "subnet-REPLACE_WITH_PRIV_SUB_A"
us-east-1b:
id: "subnet-REPLACE_WITH_PRIV_SUB_B"
public:
us-east-1a:
id: "subnet-REPLACE_WITH_PUB_SUB_A"
us-east-1b:
id: "subnet-REPLACE_WITH_PUB_SUB_B"
managedNodeGroups:
- name: lab-nodes
instanceType: t3.medium
desiredCapacity: 2
minSize: 1
maxSize: 3
privateNetworking: true
Replace the placeholder IDs with your actual values, then create:
eksctl create cluster -f eks-networking-lab.yaml
Expected Duration: 15-20 minutes.
Step 3 — Verify Pod Networking
Deploy two test pods:
# Deploy two nginx pods
kubectl run pod-a --image=nginx --labels="app=test-a"
kubectl run pod-b --image=nginx --labels="app=test-b"
# Wait for them to be running
kubectl wait --for=condition=Ready pod/pod-a pod/pod-b --timeout=60s
Check Pod IPs
kubectl get pods -o wide
Expected output:
NAME READY STATUS IP NODE
pod-a 1/1 Running 10.0.10.45 ip-10-0-10-100.ec2.internal
pod-b 1/1 Running 10.0.11.88 ip-10-0-11-200.ec2.internal
Notice: Pod IPs (10.0.10.x, 10.0.11.x) are from your private subnet CIDR ranges. These are real VPC IPs — not overlay addresses.
Test Pod-to-Pod Communication
# From pod-a, curl pod-b
kubectl exec pod-a -- curl -s $(kubectl get pod pod-b -o jsonpath='{.status.podIP}'):80
# Expected: nginx welcome page HTML
Step 4 — Inspect VPC CNI Internals
Check ENIs on a Node
# Get the node's instance ID
NODE_NAME=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
INSTANCE_ID=$(kubectl get node $NODE_NAME -o jsonpath='{.spec.providerID}' | cut -d/ -f5)
# List ENIs attached to the node
aws ec2 describe-network-interfaces \
--filters Name=attachment.instance-id,Values=$INSTANCE_ID \
--query 'NetworkInterfaces[*].{ENI:NetworkInterfaceId,PrivateIps:PrivateIpAddresses[*].PrivateIpAddress}' \
--output table
You’ll see the primary ENI with multiple secondary private IPs — those are reserved for pods.
Check VPC CNI Configuration
# Current VPC CNI version
kubectl describe daemonset aws-node -n kube-system | grep Image
# VPC CNI environment variables
kubectl get daemonset aws-node -n kube-system \
-o jsonpath='{.spec.template.spec.containers[0].env[*].name}' | tr ' ' '\n'
Check Max Pods on a Node
kubectl get node $NODE_NAME \
-o jsonpath='{.status.allocatable.pods}'
For t3.medium, you’ll see 17 (based on the ENI and IP limits for this instance type).
Step 5 — Test DNS Resolution
# Check CoreDNS is running
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Test DNS from a pod
kubectl exec pod-a -- nslookup kubernetes.default.svc.cluster.local
Expected output:
Server: 10.100.0.10
Address: 10.100.0.10#53
Name: kubernetes.default.svc.cluster.local
Address: 10.100.0.1
Test External DNS
kubectl exec pod-a -- nslookup amazon.com
This works because your private subnets route through the NAT Gateway for internet-bound traffic.
Step 6 — Deploy a Service and Test
# Create a ClusterIP service
kubectl expose pod pod-a --name=svc-a --port=80
# Access the service from pod-b
kubectl exec pod-b -- curl -s svc-a.default.svc.cluster.local:80
This confirms end-to-end networking: pod → CoreDNS → service discovery → target pod.
Step 7 — Clean Up
# Delete pods and service
kubectl delete pod pod-a pod-b
kubectl delete svc svc-a
# Delete the cluster
eksctl delete cluster --name networking-lab --region us-east-1
# Delete VPC resources (NAT Gateway first — takes a few minutes)
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_ID
echo "Waiting for NAT Gateway deletion..."
sleep 120
# Release Elastic IP
aws ec2 release-address --allocation-id $EIP_ID
# Detach and delete Internet Gateway
aws ec2 detach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID
# Delete subnets
aws ec2 delete-subnet --subnet-id $PUB_SUB_A
aws ec2 delete-subnet --subnet-id $PUB_SUB_B
aws ec2 delete-subnet --subnet-id $PRIV_SUB_A
aws ec2 delete-subnet --subnet-id $PRIV_SUB_B
# Delete route tables (non-main)
aws ec2 delete-route-table --route-table-id $PUB_RTB
aws ec2 delete-route-table --route-table-id $PRIV_RTB
# Delete VPC
aws ec2 delete-vpc --vpc-id $VPC_ID
echo "Cleanup complete!"
What You Learned
- How to build an EKS-ready VPC with proper subnet tagging
- Pods get real VPC IP addresses from the VPC CNI
- Pod-to-pod communication uses native VPC routing
- How ENIs and secondary IPs work on nodes
- DNS resolution within EKS clusters
- Service discovery works end-to-end