Amazon EKS — Workloads Hands-On Lab

3 minute read

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
  • kubectl configured
  • 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.


Updated: