Kubernetes on AWS (EKS)
Refresher on Kubernetes concepts with hands-on EKS demos. Pods, Services, Deployments, Ingress, and HPA.
Kubernetes Refresher
Kubernetes (K8s) orchestrates containerized applications — handling deployment, scaling, networking, and self-healing. This module is a refreshercovering cluster architecture, components, object properties, and hands-on demos on AWS EKS.
| Azure | AWS | Notes |
|---|---|---|
AKS | EKS | Both are managed K8s. AKS control plane is free; EKS costs $0.10/hr |
Azure Container Registry | ECR | Private Docker registry |
AKS + AGIC | EKS + ALB Controller | Ingress to load balancer integration |
AKS Node Pools | EKS Node Groups | Same concept — groups of worker nodes |
Anatomy of a Kubernetes Cluster
A K8s cluster has two layers: the Control Plane (the brain) and the Worker Nodes (the muscle). On managed services like EKS, AWS runs the control plane for you.
┌─────────────────────── CONTROL PLANE ───────────────────────┐
│ │
│ ┌──────────────┐ ┌───────────┐ ┌──────────────────────┐ │
│ │ API Server │ │ etcd │ │ Controller Manager │ │
│ │ (kube-api) │ │ (key-val │ │ (node, replicaset, │ │
│ │ │ │ store) │ │ endpoint, etc.) │ │
│ └──────────────┘ └───────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────┐ ┌────────────────────────────────────┐ │
│ │ Scheduler │ │ Cloud Controller Manager (EKS) │ │
│ └──────────────┘ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌──────── WORKER NODE 1 ────────┐ ┌──────── WORKER NODE 2 ────────┐
│ ┌─────────┐ ┌────────────┐ │ │ ┌─────────┐ ┌────────────┐ │
│ │ kubelet │ │ kube-proxy │ │ │ │ kubelet │ │ kube-proxy │ │
│ └─────────┘ └────────────┘ │ │ └─────────┘ └────────────┘ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ Container Runtime │ │ │ │ Container Runtime │ │
│ │ (containerd / CRI-O) │ │ │ │ (containerd / CRI-O) │ │
│ └──────────────────────────┘ │ │ └──────────────────────────┘ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
│ │Pod A│ │Pod B│ │Pod C│ │ │ │Pod D│ │Pod E│ │
│ └─────┘ └─────┘ └─────┘ │ │ └─────┘ └─────┘ │
└───────────────────────────────┘ └───────────────────────────────┘Control Plane Components
The control plane makes global decisions about the cluster and detects/responds to events.
🧠 API Server (kube-apiserver)
The front door to the cluster. Every interaction — kubectl, dashboards, other components — goes through the API server. It validates and processes REST requests, then updates etcd.
💾 etcd
A distributed key-value store that holds all cluster state: nodes, pods, configs, secrets. It's the single source of truth. If etcd is lost, the cluster is lost. Always back it up.
📋 Scheduler (kube-scheduler)
Watches for newly created pods with no assigned node. Evaluates constraints (resource requests, affinity rules, taints) and picks the best nodefor each pod.
🔄 Controller Manager (kube-controller-manager)
Runs control loops that watch cluster state and make changes to reach desired state. Includes: Node Controller (detects node failures), ReplicaSet Controller (maintains pod count), Endpoint Controller (populates service endpoints).
☁️ Cloud Controller Manager
Links K8s to the cloud provider's APIs. On EKS, it creates AWS load balancers for LoadBalancer services, manages EBS volumes for PersistentVolumes, and handles node lifecycle with EC2.
On EKS: AWS fully manages the control plane — API server, etcd, scheduler, and controllers run across multiple AZs automatically. You never SSH into control plane nodes. This is the value of managed K8s.
Worker Node Components
Every worker node runs three essential components that keep pods alive and networked:
🤖 kubelet
The agent running on every node. It receives pod specs from the API server and ensures the described containers are running and healthy. It:
- Pulls container images from registries (ECR, Docker Hub)
- Starts/stops containers via the container runtime
- Reports node status and pod health back to the API server
- Executes liveness and readiness probes
- Mounts volumes (EBS, EFS, ConfigMaps, Secrets)
🌐 kube-proxy
Handles networking rules on each node. When you create a K8s Service, kube-proxy sets up iptables/IPVS rules so that traffic to the Service's ClusterIP gets forwarded to the correct pod(s). It:
- Maintains network rules for Service → Pod routing
- Load-balances across pod replicas using round-robin
- Handles NodePort and ClusterIP traffic forwarding
- Updates rules dynamically as pods are added/removed
📦 Container Runtime
The software that actually runs containers. K8s doesn't run containers directly — it delegates to a runtime via the Container Runtime Interface (CRI).
| Runtime | Description | Used By |
|---|---|---|
| containerd | Industry standard, lightweight, Docker-compatible | EKS default, GKE, most clusters |
| CRI-O | OCI-focused, minimal, designed specifically for K8s | OpenShift, some bare-metal |
| Docker Engine | Full Docker daemon (deprecated as K8s runtime in v1.24) | Legacy clusters only |
Docker images still work! K8s dropped Docker as a runtime, not as an image format. Images built with docker build are OCI-compliant and work perfectly with containerd and CRI-O.
How They Work Together
You run: kubectl apply -f deployment.yaml
1. kubectl → API Server: "Create 3 pods of my-app"
2. API Server → etcd: Stores the desired state
3. Scheduler → API Server: "Pod X should go on Node 2" (picks best node)
4. API Server → kubelet: "Hey Node 2, run Pod X"
5. kubelet → containerd: "Pull image, start container"
6. kubelet → API Server: "Pod X is Running"
7. kube-proxy: Updates iptables so Service routes to new pod
8. Controller Manager: Continuously watches — if a pod dies, repeats 3-7Properties of Kubernetes Objects
K8s objects have fundamental properties that shape how you design and operate applications. Understanding these is critical for building resilient systems.
⚛️ Atomicity of Pods
A pod is the atomic unit of scheduling. All containers in a pod are scheduled together, run on the same node, and share the same lifecycle:
- If any container in a pod fails its restart policy, the entire pod may be restarted
- All containers share the same network namespace (same IP, can communicate via
localhost) - All containers share the same storage volumes
- A pod cannot be split across multiple nodes — it's all-or-nothing
Rule of thumb: Put containers in the same pod only if they must share the same lifecycle and communicate via localhost (e.g., app + sidecar log collector). Otherwise, use separate pods.
💨 Ephemerality
Pods are ephemeral by design — they can be killed, rescheduled, or replaced at any time. This is a feature, not a bug:
- No guarantees on lifespan — a pod may live for seconds or months
- New IP on restart — never hardcode pod IPs (use Services instead)
- Local storage is lost on pod deletion (use PersistentVolumes for data)
- Nodes can fail — pods on a failed node are rescheduled to healthy nodes
Pod dies or node fails:
┌──────────┐ ┌──────────┐
│ Pod v1 │ ──X── │ Pod v1 │ ← pod is terminated
│ IP: 10.1 │ │ IP: gone │
└──────────┘ └──────────┘
│
Scheduler picks new node
│
┌──────────┐
│ Pod v1 │ ← brand new pod
│ IP: 10.7 │ ← different IP!
└──────────┘
This is why you always use a Service (stable IP) to reach pods.📜 Declarative Model
You declare the desired state (e.g., "I want 3 replicas running image v2") and K8s continuously reconciles the actual state to match. You never say "start a container" — you say "the desired state is 3 containers."
| Imperative (how) | Declarative (what) |
|---|---|
kubectl run nginx --image=nginx | kubectl apply -f deployment.yaml |
| "Create this pod now" | "Ensure 3 pods exist, always" |
| One-time action | Continuous reconciliation |
| Good for quick testing | Required for production |
🏷️ Labels & Selectors
Labels are key-value pairs attached to objects. Selectors filter objects by labels. This is how K8s connects everything — a Service finds its pods, a Deployment manages its ReplicaSet, and HPA targets a Deployment, all through label matching.
# Pod with labels
metadata:
labels:
app: my-api
env: production
version: v2
# Service uses selector to find matching pods
spec:
selector:
app: my-api # matches pods with app=my-api
ports:
- port: 80📐 Resource Requests & Limits
Every container should declare how much CPU and memory it needs. This is critical for scheduling and stability:
| Setting | What It Does | If Not Set |
|---|---|---|
| Requests | Guaranteed minimum — the scheduler uses this to find a node with enough capacity | Pod may be scheduled on a full node |
| Limits | Maximum allowed — container is throttled (CPU) or killed (memory) if exceeded | Container can consume all node resources |
resources:
requests:
cpu: 100m # 100 millicores = 10% of 1 CPU core
memory: 128Mi # guaranteed 128MB RAM
limits:
cpu: 500m # max 50% of 1 CPU core (throttled beyond this)
memory: 256Mi # killed (OOMKilled) if exceeds 256MBAlways set resource requests. Without them, the scheduler can't make good decisions and pods may be evicted under pressure. Without limits, a runaway container can starve other pods on the same node.
Core Kubernetes Objects
📦 Pods
The smallest deployable unit. A pod wraps one or more containers that share the same network namespace (same IP, same ports) and storage volumes. You rarely create pods directly — you use Deployments.
🚀 Deployments
Manages ReplicaSets and handles rolling updates. Declares the desired state: image, replicas, update strategy. K8s continuously ensures that state is met. If a pod dies, the Deployment creates a replacement.
🌐 Services
A stable network abstraction over ephemeral pods. Because pods get new IPs when rescheduled, you need a fixed endpoint. Services provide this:
| Type | Scope | Use Case |
|---|---|---|
| ClusterIP | Internal only | Backend services talking to each other |
| NodePort | Exposes on each node's IP | Development, simple external access |
| LoadBalancer | Cloud provider LB | Production — creates an ALB/NLB on EKS |
| ExternalName | DNS alias | Maps to an external service (e.g., RDS endpoint) |
🚪 Ingress
HTTP routing rules that map domains/paths to services. On EKS, the AWS ALB Ingress Controller translates Ingress resources into real ALBs.
📁 Namespaces
Virtual clusters within a cluster. Isolate environments (dev/staging/prod) or teams. Resource quotas and network policies can be scoped per namespace.
📊 HPA (Horizontal Pod Autoscaler)
Autoscales pod replicas based on CPU, memory, or custom metrics. It's the K8s equivalent of EC2 Auto Scaling Groups.
Other Important Objects
🔐 Secrets & ConfigMaps
ConfigMaps store non-sensitive config. Secrets store sensitive data (base64-encoded, not encrypted by default — use KMS on EKS).
💽 PersistentVolumes (PV/PVC)
Abstract storage from pods. PVCs request storage, PVs provide it. On EKS, backed by EBS or EFS.
🕐 Jobs & CronJobs
Jobs run a pod to completion (batch processing). CronJobs schedule jobs on a cron schedule.
📡 DaemonSets
Ensures a pod runs on every node. Perfect for monitoring agents, log collectors (e.g., Fluentd, Datadog).
🗃️ StatefulSets
Like Deployments but with stable identities and persistent storage. For databases and stateful apps (Kafka, Elasticsearch).
🔒 NetworkPolicies
Firewall rules for pod-to-pod traffic. By default, all pods can talk to all pods. NetworkPolicies restrict this.
EKS — Elastic Kubernetes Service
EKS is AWS's managed Kubernetes. AWS manages the control plane(API server, etcd, scheduler) and you manage the worker nodes.
Setting Up EKS
# Install eksctl (the EKS CLI — like 'az aks create')
brew install eksctl
# Create a cluster (takes ~15 minutes)
eksctl create cluster \
--name sandbox-eks \
--region us-east-1 \
--version 1.29 \
--nodegroup-name standard \
--node-type t3.medium \
--nodes 2 \
--nodes-min 1 \
--nodes-max 4
# Verify
kubectl get nodeseksctl create cluster is similar to az aks create. Both create the control plane + node pool. EKS costs $0.10/hr for the control plane; AKS control plane is free.
Demo 1: Deploy the Sandbox App to EKS
Create Kubernetes manifests and deploy the app:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sandbox-app
labels:
app: sandbox
spec:
replicas: 3
selector:
matchLabels:
app: sandbox
template:
metadata:
labels:
app: sandbox
spec:
containers:
- name: app
image: node:18-alpine
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: sandbox-service
spec:
type: LoadBalancer
selector:
app: sandbox
ports:
- port: 80
targetPort: 3000# Create a secret for DB credentials
kubectl create secret generic db-credentials \
--from-literal=host=your-rds-endpoint \
--from-literal=password=your-password
# Apply the deployment
kubectl apply -f k8s/deployment.yaml
# Watch pods come up
kubectl get pods -w
# Get the LoadBalancer URL
kubectl get svc sandbox-service
# → EXTERNAL-IP column shows the ALB DNSDemo 2: Horizontal Pod Autoscaler
Scale the app automatically based on CPU:
# Create HPA — target 50% CPU, scale 2-10 pods
kubectl autoscale deployment sandbox-app \
--cpu-percent=50 \
--min=2 \
--max=10
# Generate load (from another terminal)
kubectl run load-gen --image=busybox --restart=Never -- \
/bin/sh -c "while true; do wget -q -O- http://sandbox-service; done"
# Watch the HPA scale up
kubectl get hpa -w
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# sandbox-app Deployment/sandbox-app 72%/50% 2 10 4
# Clean up load generator
kubectl delete pod load-genThe metrics server must be installed for HPA to work. On EKS, install it with:kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Demo 3: Ingress with AWS ALB Controller
Use the AWS ALB Ingress Controller to create a real ALB from K8s Ingress rules:
# Install ALB Controller (via Helm)
helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=sandbox-eksapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sandbox-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sandbox-service
port:
number: 80kubectl apply -f k8s/ingress.yaml
kubectl get ingress # Get the ALB DNS from ADDRESS columnEKS Cleanup
# Delete the cluster (removes all resources)
eksctl delete cluster --name sandbox-eks --region us-east-1EKS clusters cost ~$0.10/hr ($72/month) for the control plane plus EC2 costs for worker nodes. Always delete after learning sessions!
Key Takeaways
- Control plane = API server + etcd + scheduler + controllers (managed by AWS on EKS)
- Worker nodes = kubelet (agent) + kube-proxy (networking) + container runtime (containerd)
- Pods are atomic — all containers co-scheduled, same node, same network, same lifecycle
- Pods are ephemeral — design for failure, never rely on pod IPs, use Services for stable endpoints
- Declarative model — declare desired state, K8s continuously reconciles
- Labels + selectors are how K8s connects everything (Services → Pods, Deployments → ReplicaSets)
- Always set resource requests/limits — scheduling and stability depend on them
- EKS = managed K8s on AWS.
eksctlis the easiest way to create clusters - HPA scales pods horizontally based on metrics (like ASG for containers)
- ALB Controller bridges K8s Ingress with AWS ALBs
- Always clean up EKS clusters — they're not cheap!