Amazon EKS — Networking Hands-On Lab

5 minute read

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

Updated: