Amazon EKS — Workloads Hands-On Lab
In this lab, you’ll deploy a web application with a database backend on EKS, using Deployments, StatefulSets, ConfigMaps, Secrets, Jobs, and Services.
Prerequisites
- Running EKS cluster with managed node group
kubectlconfigured- EBS CSI Driver installed (for StatefulSet storage)
Step 1 — Create a Namespace
kubectl create namespace workloads-lab
Step 2 — Create Secrets
kubectl create secret generic db-credentials \
--namespace workloads-lab \
--from-literal=POSTGRES_USER=appuser \
--from-literal=POSTGRES_PASSWORD=S3cur3P@ss \
--from-literal=POSTGRES_DB=appdb
Step 3 — Create a ConfigMap
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: workloads-lab
data:
APP_ENV: "lab"
LOG_LEVEL: "debug"
DB_PORT: "5432"
EOF
Step 4 — Deploy PostgreSQL with StatefulSet
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: workloads-lab
spec:
serviceName: postgres-headless
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: db-credentials
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
memory: "512Mi"
readinessProbe:
exec:
command: ["pg_isready", "-U", "appuser", "-d", "appdb"]
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp2
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: workloads-lab
spec:
type: ClusterIP
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: workloads-lab
spec:
type: ClusterIP
selector:
app: postgres
ports:
- port: 5432
EOF
Wait for the StatefulSet to be ready:
kubectl rollout status statefulset/postgres -n workloads-lab --timeout=120s
Step 5 — Run a Database Migration Job
cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
namespace: workloads-lab
spec:
template:
spec:
containers:
- name: migrate
image: postgres:16-alpine
command:
- sh
- -c
- |
PGPASSWORD=$POSTGRES_PASSWORD psql -h postgres -U $POSTGRES_USER -d $POSTGRES_DB -c "
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO messages (content) VALUES ('Hello from EKS!');
INSERT INTO messages (content) VALUES ('Lab setup complete');
"
envFrom:
- secretRef:
name: db-credentials
resources:
requests:
cpu: "100m"
memory: "64Mi"
restartPolicy: Never
backoffLimit: 3
ttlSecondsAfterFinished: 300
EOF
Wait for the Job to complete:
kubectl wait --for=condition=complete job/db-migration -n workloads-lab --timeout=60s
kubectl logs job/db-migration -n workloads-lab
Step 6 — Deploy the Web Application
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: workloads-lab
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: app
image: nginx:alpine
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
memory: "128Mi"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: web-app
namespace: workloads-lab
spec:
type: ClusterIP
selector:
app: web-app
ports:
- port: 80
targetPort: 80
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb
namespace: workloads-lab
spec:
minAvailable: 2
selector:
matchLabels:
app: web-app
EOF
Step 7 — Verify Everything
# Check all resources
kubectl get all -n workloads-lab
# Verify StatefulSet DNS
kubectl run dns-test --rm -it --image=busybox --restart=Never -n workloads-lab -- \
nslookup postgres-headless.workloads-lab.svc.cluster.local
# Check environment variables are injected
kubectl exec deploy/web-app -n workloads-lab -- env | grep -E "APP_ENV|LOG_LEVEL|DB_PORT"
# Verify database migration
kubectl exec statefulset/postgres -n workloads-lab -- \
psql -U appuser -d appdb -c "SELECT * FROM messages;"
# Test PDB
kubectl get pdb -n workloads-lab
Step 8 — Test Rolling Update
# Trigger a rolling update
kubectl set image deployment/web-app app=nginx:latest -n workloads-lab
# Watch the rollout
kubectl rollout status deployment/web-app -n workloads-lab
# Rollback
kubectl rollout undo deployment/web-app -n workloads-lab
Clean Up
kubectl delete namespace workloads-lab
Note: Deleting the namespace removes all resources including PVCs and their associated EBS volumes.