Epitomsoft Academy · Cloud & DevOps course

Kubernetes in Production on AWS

An end-to-end course: from a developer’s git push, through a CI/CD pipeline, to a secure, autoscaling, fully monitored Kubernetes cluster on Amazon EKS – with real examples and working code.

What you will learn

From code to a production cluster

Kubernetes is the industry standard for running containerised applications. Using one realistic example – the “campus jobs” web app – you will follow the complete journey: building and shipping releases, serving user requests, and operating the platform reliably.

  • Explain the Kubernetes architecture: control plane, nodes, pods and controllers
  • Build a CI/CD pipeline with GitHub Actions, Docker and Amazon ECR
  • Trace a user request from DNS to a pod and the database
  • Write Deployments, Services, Ingress, ConfigMaps, Secrets and autoscalers
  • Secure workloads with IAM roles for service accounts and RBAC
  • Monitor, log, alert and roll back with confidence

Course at a glance

  • Sessions: 3 animated video lessons + reading + lab
  • Level: Intermediate
  • Prerequisites: Linux command line, Git and basic Docker
  • You will build: A containerised API deployed with CI/CD to a local Kubernetes cluster, with autoscaling and monitoring

Session 1 of 3

The CI/CD pipeline: from git push to EKS

Follow release 1.4 of the campus jobs API through build, tests, security scans, Docker, Amazon ECR and a zero-downtime rolling update.

1 2 3 4 5 6 7 8 Developer Code · git push GitHub Repository · PR GitHub Actions CI/CD workflow Quality gates Tests · SonarQube · Trivy Docker image Build & tag Amazon ECR Image registry Argo CD / Helm Continuous delivery Amazon EKS Rolling update
Live example
Read the full transcript
  1. Welcome: Hi, I am Epi! This is session one of Kubernetes in production. You will see how a code change travels from a developer’s laptop to a live Kubernetes cluster on AWS. Our example: releasing version 1.4 of the campus jobs API, which adds a remote-only filter.
  2. Step 1 · Commit: It starts with a developer. They write the new feature, commit it and run git push. The code lands in a GitHub repository, usually through a pull request that a teammate reviews and approves.
  3. Step 2 · Trigger CI: The merge triggers GitHub Actions, the CI/CD pipeline. A workflow file stored in the repository tells it exactly what to do, step by step, on a fresh build machine.
  4. Step 3 · Quality gates: First come the quality gates. The pipeline builds the code, runs the automated tests, and scans it: SonarQube checks code quality, and Trivy looks for known vulnerabilities. If any gate fails, the pipeline stops, and nothing is deployed.
  5. Step 4 · Build image: Next, the pipeline builds a Docker image: the application plus everything it needs to run, packaged together. The image is tagged with the version and the git commit, so every release can be traced back to its code.
  6. Step 5 · Push to ECR: The image is pushed to Amazon ECR, a private container registry. ECR keeps every version, so you can always roll back, and it can scan images again for vulnerabilities.
  7. Step 6 · Continuous delivery: Now continuous delivery takes over. A tool such as Argo CD or Helm updates the Kubernetes manifests to the new image tag. With GitOps, the desired state of the cluster is stored in Git, and the cluster is synced to match it.
  8. Step 7 · Rolling update: Amazon EKS performs a rolling update. New pods start with version 1.4, and they must pass their health checks before old pods are removed. Users never see any downtime.
  9. Step 8 · Feedback: Monitoring closes the loop. If error rates rise after a release, alerts fire, and the team can roll back to the previous version in seconds. The developer gets fast feedback, and the cycle starts again.
  10. Recap: That is the CI/CD pipeline: commit, build, test, scan, package, push, deploy and monitor, all automated. In the next session, we follow what happens when a user actually opens the app.

Session 2 of 3

How a user request flows through Kubernetes

Follow one request through Route 53, the load balancer, Ingress, Service and Pods – with configuration, secrets and the database.

Amazon EKS cluster · namespace: production 1 2 3 4 5 6 7 8 Users Browser · mobile app Route 53 DNS Load balancer AWS ALB · HTTPS Ingress Host & path rules Service Stable address Pods (×3) Your app containers ConfigMap & Secret Settings · passwords Amazon RDS PostgreSQL · Multi-AZ
Live example
Read the full transcript
  1. Welcome: Welcome to session two! Now we follow one user request through a production Kubernetes cluster on AWS. Our example: a student opens jobs dot example dot com, and searches for remote jobs.
  2. Step 1 · DNS: The user types the address in a browser or app. Amazon Route 53, the AWS DNS service, translates the name into the address of the load balancer.
  3. Step 2 · Load balancer: The request reaches the Application Load Balancer. It handles HTTPS using a TLS certificate, spreads traffic across availability zones, and only sends traffic to healthy targets.
  4. Step 3 · Ingress: The load balancer hands the request to the Kubernetes Ingress. Ingress rules decide where each request goes, based on the host name and the URL path. On EKS, the AWS Load Balancer Controller manages this for you.
  5. Step 4 · Service: The Ingress sends the request to a Service. Pods come and go, but a Service gives them one stable name and address, and spreads requests across them.
  6. Step 5 · Pod: The Service forwards the request to one healthy pod. Pods run your containers, and Kubernetes only sends traffic to pods that pass their readiness check.
  7. Step 6 · Configuration: The application reads its settings from a ConfigMap, and its sensitive values, like the database password, from a Secret. Nothing sensitive is baked into the image.
  8. Step 7 · Database: To answer the search, the app queries the database: Amazon RDS for PostgreSQL, running across two availability zones for high availability.
  9. Step 8 · Response: Finally, the response travels back through the Service, the Ingress and the load balancer to the user, usually in a fraction of a second.
  10. Recap: That is the request path: DNS, load balancer, Ingress, Service, Pod, configuration and database, and back to the user. In the next session, we see how to keep all of this running smoothly in production.

Session 3 of 3

Running Kubernetes in production

A job fair sends traffic up ten times: see autoscaling, storage, security, health checks, metrics, logs and alarms in action.

1 2 3 4 5 6 7 8 Autoscaling HPA · Karpenter Storage PV / PVC · EBS · EFS IAM / IRSA & RBAC Least privilege EKS cluster Pods · nodes Health probes Readiness · liveness Prometheus & Grafana Metrics · dashboards Fluent Bit Log collection CloudWatch Logs · alarms
Live example
Read the full transcript
  1. Welcome: Welcome to session three: running Kubernetes in production. Our example: at nine a.m. a virtual job fair starts, and traffic jumps ten times. Let us see how the platform handles it, safely and automatically.
  2. Step 1 · Autoscaling: The Horizontal Pod Autoscaler watches CPU, memory or custom metrics. When average CPU rises above its target, it adds pods, and when traffic drops, it removes them. If the servers fill up, a node autoscaler such as Karpenter adds more nodes.
  3. Step 2 · Storage: Some data must survive pod restarts. Kubernetes uses persistent volumes and persistent volume claims, backed on AWS by EBS disks or EFS file systems, while large files often go to S3.
  4. Step 3 · Security: IAM roles for service accounts, called IRSA, give each pod only the AWS permissions it needs, with no hard-coded keys. Kubernetes RBAC controls what people and tools can do inside the cluster.
  5. Step 4 · Health probes: Health probes keep the app reliable. A readiness probe decides whether a pod should receive traffic, and a liveness probe restarts a pod that has stopped responding.
  6. Step 5 · Metrics: Prometheus collects metrics from the cluster and the application, and Grafana turns them into dashboards that the team watches during the job fair.
  7. Step 6 · Logs: Fluent Bit runs on every node and collects the logs from all containers, so nothing is lost when a pod is replaced.
  8. Step 7 · Alarms: Amazon CloudWatch stores the logs and metrics, and alarms watch for trouble, such as a rising error rate, and notify the on-call engineer.
  9. Step 8 · Act: Alarms and dashboards drive action: scale further, roll back a bad release, or fix the root cause. After the job fair, the autoscaler scales back down to save cost.
  10. Recap: That is production Kubernetes: autoscaling, storage, least-privilege security, health checks, metrics, logs and alarms, all working together. Congratulations, you have completed the Kubernetes course!

Deep dive

Key concepts, explained with examples

The control plane

The “brain” of the cluster: the API server receives every request, etcd stores the cluster state, the scheduler places pods on nodes and controllers keep reality matching the desired state. On Amazon EKS, AWS runs the control plane for you.

Example: You ask for 3 replicas; a pod crashes; the Deployment controller notices only 2 are running and starts a new one within seconds.

Nodes and the kubelet

Worker nodes are the servers that run your containers. On each node the kubelet starts and watches pods, and kube-proxy handles networking for Services.

Example: An EKS managed node group of three EC2 instances across two availability zones runs all campus-jobs pods.

Pods and Deployments

A pod is the smallest unit: one or more containers that share network and storage. A Deployment manages identical pods and performs rolling updates and rollbacks.

Example: “replicas: 3” keeps three copies of the API running; changing the image tag rolls them to v1.4 one at a time.

Services

Pods are temporary and their IPs change. A Service gives a stable name and virtual IP and load-balances across matching pods. Types: ClusterIP (internal), NodePort and LoadBalancer.

Example: Other apps call http://campus-jobs-api.production – they never need to know individual pod IPs.

Ingress

An Ingress defines HTTP routing rules – host names, paths and TLS – into Services. On EKS the AWS Load Balancer Controller turns an Ingress into an Application Load Balancer.

Example: jobs.example.com/api goes to the API service; jobs.example.com/ goes to the web front-end service.

ConfigMaps and Secrets

ConfigMaps hold non-sensitive settings; Secrets hold passwords, API keys and certificates. Both are injected into pods as environment variables or files, so the same image runs in every environment.

Example: PAGE_SIZE=20 lives in a ConfigMap; DB_PASSWORD lives in a Secret (ideally synced from AWS Secrets Manager).

Namespaces and RBAC

Namespaces separate environments and teams inside one cluster. Role-Based Access Control (RBAC) decides who may do what in each namespace.

Example: Developers can deploy to “staging” but have read-only access to “production”; the CI pipeline has a dedicated deploy role.

IRSA and Pod Identity

IAM roles for service accounts (IRSA) – and the newer EKS Pod Identity – give a pod temporary AWS credentials for exactly one role. No access keys are ever stored in code or images.

Example: The API pod may read the résumés S3 bucket – and nothing else in the AWS account.

Autoscaling

The Horizontal Pod Autoscaler changes the number of pods based on metrics; node autoscalers such as Karpenter or Cluster Autoscaler add or remove servers when pods need more room.

Example: CPU above 60% → HPA scales 3 → 9 pods → Karpenter adds two nodes to fit them.

Probes and observability

Readiness and liveness probes tell Kubernetes when a pod can take traffic or must restart. Prometheus, Grafana, Fluent Bit and CloudWatch give metrics, dashboards, logs and alerts.

Example: A pod that stops responding fails its liveness probe and is restarted automatically – before users notice.

Cheat sheet

Kubernetes objects at a glance

What each object is for, in one line.

Workloads

Deployment · ReplicaSet · Pod

Run and update stateless apps; StatefulSet for databases and queues; Job/CronJob for batch tasks.

Networking

Service · Ingress · NetworkPolicy

Stable addresses, HTTP routing from outside, and firewall rules between pods.

Configuration

ConfigMap · Secret

Settings and sensitive values, kept outside the container image.

Storage

PersistentVolume · PVC · StorageClass

Durable disks and file systems (EBS, EFS) that survive pod restarts.

Scaling

HPA · Karpenter / Cluster Autoscaler

More pods when busy, more nodes when the cluster is full – and back down to save cost.

Security

Namespace · RBAC · ServiceAccount

Isolation, permissions for people and tools, and identities for pods (IRSA / Pod Identity).

Worked examples

Real manifests and a real pipeline

The files behind the animated sessions. Placeholders such as are replaced with your own values.

Example 1 – CI pipeline: test, scan and push to Amazon ECR (GitHub Actions)

# .github/workflows/build-and-deploy.yml
name: build-and-deploy
on:
  push:
    branches: [main]
permissions:
  id-token: write   # OIDC login to AWS - no stored access keys
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'
      - run: mvn -B verify          # compile + unit tests
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<account-id>:role/github-deploy
          aws-region: us-east-1
      - uses: aws-actions/amazon-ecr-login@v2
        id: ecr
      - name: Build, scan and push the image
        env:
          IMAGE: ${{ steps.ecr.outputs.registry }}/campus-jobs-api:${{ github.sha }}
        run: |
          docker build -t "$IMAGE" .
          docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
            aquasec/trivy image --exit-code 1 --severity CRITICAL "$IMAGE"
          docker push "$IMAGE"

Example 2 – Deployment and Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: campus-jobs-api
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels: { app: campus-jobs-api }
  strategy:
    rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }   # zero-downtime releases
  template:
    metadata:
      labels: { app: campus-jobs-api }
    spec:
      serviceAccountName: campus-jobs-api               # IRSA: AWS permissions for this pod
      containers:
        - name: api
          image: <account-id>.dkr.ecr.us-east-1.amazonaws.com/campus-jobs-api:1.4.0
          ports: [{ containerPort: 8080 }]
          envFrom:
            - configMapRef: { name: campus-jobs-config }
            - secretRef: { name: campus-jobs-secrets }
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 512Mi }
          readinessProbe:
            httpGet: { path: /health/ready, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /health/live, port: 8080 }
            initialDelaySeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: campus-jobs-api
  namespace: production
spec:
  selector: { app: campus-jobs-api }
  ports: [{ port: 80, targetPort: 8080 }]

Example 3 – Ingress (AWS Load Balancer Controller) and Horizontal Pod Autoscaler

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: campus-jobs
  namespace: production
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:<account-id>:certificate/<certificate-id>
spec:
  ingressClassName: alb
  rules:
    - host: jobs.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: campus-jobs-api
                port: { number: 80 }
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: campus-jobs-api
  namespace: production
spec:
  scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: campus-jobs-api }
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target: { type: Utilization, averageUtilization: 60 }

Example 4 – Everyday kubectl commands for operating the app

kubectl get pods -n production -l app=campus-jobs-api        # are all pods running?
kubectl rollout status deployment/campus-jobs-api -n production
kubectl describe hpa campus-jobs-api -n production           # why did it scale?
kubectl logs deploy/campus-jobs-api -n production --tail=50  # recent logs
kubectl rollout undo deployment/campus-jobs-api -n production # instant rollback

Hands-on lab

Deploy campus-jobs to your own cluster

You run the full flow on a free local cluster with kind (Kubernetes in Docker) – no cloud bill required – then map each step to its EKS equivalent.

  • Create a local cluster: kind create cluster –name campus
  • Containerise a small API and load the image into kind
  • Apply the Deployment, Service, ConfigMap and Secret manifests
  • Install an ingress controller and route a host name to your Service
  • Install metrics-server, add the HPA and generate load to watch it scale
  • Break a liveness probe on purpose and watch Kubernetes heal the app
  • Write a GitHub Actions workflow that builds, tests and scans your image

Key terms

Vocabulary to master

  • Cluster
  • Control plane
  • etcd
  • Node
  • kubelet
  • Pod
  • Deployment
  • ReplicaSet
  • Service
  • Ingress
  • ConfigMap
  • Secret
  • Namespace
  • RBAC
  • IRSA
  • HPA
  • PV / PVC
  • Probe
  • Helm
  • GitOps
  • Rolling update
  • ECR
  • EKS

Check your understanding

Course quiz

Score 70% or more on every quiz to complete the course and unlock your certificate on My progress.

Further learning

Free official documentation & open-source references

Primary sources and free hands-on resources to go deeper.

Want to learn this with a mentor?

Join the Epitomsoft Academy Cloud & DevOps track: live sessions, real projects and career support.