Kubernetes Workloads

Run production apps, not just containers

01 / 17

A 60-minute session covering Deployments, Services, ConfigMaps, Secrets, resource limits, and rolling updates through one end-to-end Kubernetes app lab.

60minutes total
45minutes theory + guided walkthrough
10minutes lab runbook
5minutes quiz + recap
Session Map

Agenda and learning outcomes

02 / 17

Agenda

0-5 min
Why workloads matter in real clusters
5-20 min
Pods, Deployments, ReplicaSets, StatefulSets, DaemonSets
20-30 min
Services, Ingress, and stable networking
30-40 min
ConfigMaps, Secrets, requests/limits, rollout strategy
40-55 min
Lab: deploy a full app to Kubernetes
55-60 min
Quiz and takeaways

By the end, learners should be able to

  • Explain why Pods alone are not an operations model
  • Choose the right workload controller for stateless vs stateful vs node-level jobs
  • Expose applications safely with the right Service type
  • Externalize config and protect secrets
  • Set resource requests and limits to avoid noisy-neighbor failures
  • Use rolling updates and rollout history to ship safely
Core Idea

Pods are runtime units, not deployment strategy

03 / 17

Pod

  • Smallest deployable unit in Kubernetes
  • One or more tightly coupled containers
  • Ephemeral by design: if it dies, Kubernetes can replace it with a new Pod
  • IP address changes across restarts

Why not create Pods directly?

  • No built-in desired state beyond that one object
  • No rollout strategy, rollback history, or replica management
  • Operationally fragile for production workloads
  • Correct default: use a controller such as Deployment, StatefulSet, or DaemonSet

Rule of thumb

Pod = runtime envelope Deployment = stateless app controller StatefulSet = stable identity + storage DaemonSet = one Pod per node
Controllers

Choose the right workload primitive

04 / 17

Deployment

  • Best default for stateless services
  • Manages ReplicaSets
  • Supports rolling updates and rollback

StatefulSet

  • For databases and clustered stateful systems
  • Stable pod names such as `db-0`, `db-1`
  • Stable persistent volumes

DaemonSet

  • Runs one pod on every node
  • Ideal for log collectors and monitoring agents
  • Examples: Fluent Bit, node exporters

ReplicaSet in context

ReplicaSets are rarely authored directly. A Deployment creates and owns them to guarantee that the requested replica count is continuously enforced.

Networking

Services give ephemeral Pods a stable address

05 / 17

Why Services exist

  • Pods come and go, so Pod IPs are not a reliable endpoint
  • A Service adds a stable virtual IP and DNS name
  • Kube-proxy routes traffic to healthy matching Pods
ClientCalls `api-service`
ServiceStable IP + DNS
Pods`api-abc`, `api-def`

Service types

  • ClusterIP: internal-only, default choice for service-to-service traffic
  • NodePort: exposes a port on every node, useful for demos and testing
  • LoadBalancer: cloud-managed external traffic entrypoint
  • Ingress: L7 routing by host/path to multiple Services
Configuration

Separate application config from the image

06 / 17

ConfigMap

  • Non-sensitive configuration
  • Feature flags, base URLs, environment labels, config files
  • Can be injected as environment variables or mounted as files

Secret

  • Sensitive values such as tokens, passwords, certificates
  • Base64 encoded in manifest form
  • Use RBAC, encryption at rest, and external secret managers in production

Production guidance

Kubernetes Secrets are not magically safe because they are encoded. Treat them as sensitive data, enable encryption at rest, and prefer systems like External Secrets Operator with Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault.

Reliability

Resource requests and limits protect the cluster

07 / 17

Requests

  • Minimum CPU and memory reserved for scheduling
  • Used by the scheduler to place Pods
  • Without requests, scheduling becomes guesswork

Limits

  • Upper bound for CPU and memory consumption
  • CPU can be throttled
  • Memory overuse can lead to OOMKilled containers
resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"
Delivery

Rolling updates reduce deployment risk

08 / 17

What a Deployment gives you

  • Gradual replacement of old Pods with new Pods
  • Health-driven progression using readiness probes
  • Rollout history and rollback if the new version fails

Key commands

kubectl apply -f app.yaml
kubectl rollout status deploy/web
kubectl rollout history deploy/web
kubectl rollout undo deploy/web
Use readiness probes before traffic Never ship by deleting Pods manually Observe rollout status every time
Lab

Scenario: deploy a full app to Kubernetes

09 / 17

Application topology

FrontendPublic demo UI service
APIInternal fake-service API
Runtime ConfigEnvironment-driven app behavior
ConfigMapApp mode, URLs, feature flags
SecretDemo API token
ServiceStable frontend and API endpoints

Lab flow

  • Bootstrap namespace and shared config
  • Deploy API and internal Service
  • Deploy frontend and external Service
  • Test rollout, rollback, and cleanup

Runnable manifests are in day25/k8s-lab/.

Lab Architecture

One namespace, two Deployments, two Services

10 / 17
BrowserPort-forward or NodePort
frontend-serviceNodePort 30090
frontendfake-service v0.26.2
ConfigMapAPI URL + message
api-serviceClusterIP 9090
apifake-service v0.26.1
SecretDEMO_API_KEY
Namespaceworkloads-lab
Rolloutv0.26.1 -> v0.26.2

Manifest set

namespace.yaml config.yaml secret.yaml api-deployment.yaml api-service.yaml frontend-deployment.yaml frontend-service.yaml

Apply order

kubectl apply -f k8s-lab/namespace.yaml
kubectl apply -f k8s-lab/config.yaml
kubectl apply -f k8s-lab/secret.yaml
kubectl apply -f k8s-lab/api-deployment.yaml
kubectl apply -f k8s-lab/api-service.yaml
kubectl apply -f k8s-lab/frontend-deployment.yaml
kubectl apply -f k8s-lab/frontend-service.yaml
Lab Runbook

Step 0: prepare the namespace and check the cluster

11 / 17
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: workloads-lab

What each command does

  • Verify cluster context and node health first
  • Create an isolated namespace for the lab
  • Set the default namespace once before applying the rest

Everything after this stays isolated in workloads-lab.

Lab Runbook

Step 1: create shared config and secret data

12 / 17
# config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: workloads-lab
data:
  API_BASE_URL: "http://api-service:9090"
  FRONTEND_MESSAGE: "Frontend calling internal API"
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
  namespace: workloads-lab
type: Opaque
stringData:
  DEMO_API_KEY: "lab-token-123"

Key idea

  • Create shared values before workloads
  • ConfigMap carries URL and UI message
  • Secret carries the demo API token

Run

kubectl apply -f k8s-lab/config.yaml
kubectl apply -f k8s-lab/secret.yaml
kubectl get configmap,secret
Lab Runbook

Step 2: deploy the API and explain the Deployment fields

13 / 17
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: workloads-lab
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: nicholasjackson/fake-service:v0.26.1
          env:
            - name: NAME
              value: "api"
            - name: MESSAGE
              value: "Kubernetes API v1"
            - name: LISTEN_ADDR
              value: "0.0.0.0:9090"
            - name: DEMO_API_KEY
              valueFrom:
                secretKeyRef:
                  name: app-secret
                  key: DEMO_API_KEY
          ports:
            - containerPort: 9090
          readinessProbe:
            httpGet:
              path: /
              port: 9090
            initialDelaySeconds: 5
            periodSeconds: 5
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
---
# api-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: api-service
  namespace: workloads-lab
spec:
  selector:
    app: api
  ports:
    - port: 9090
      targetPort: 9090
  type: ClusterIP

What to notice

  • Deployment owns availability
  • Probe gates traffic
  • Secret is injected explicitly
  • api-service:9090 is the stable endpoint

Run

kubectl apply -f k8s-lab/api-deployment.yaml
kubectl apply -f k8s-lab/api-service.yaml
kubectl rollout status deploy/api
Lab Runbook

Step 3: deploy the frontend and expose the app externally

14 / 17
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: workloads-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: frontend
          image: nicholasjackson/fake-service:v0.26.2
          env:
            - name: NAME
              value: "frontend"
            - name: MESSAGE
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: FRONTEND_MESSAGE
            - name: LISTEN_ADDR
              value: "0.0.0.0:9090"
            - name: UPSTREAM_URIS
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: API_BASE_URL
          ports:
            - containerPort: 9090
---
# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
  namespace: workloads-lab
spec:
  selector:
    app: frontend
  ports:
    - port: 9090
      targetPort: 9090
      nodePort: 30090
  type: NodePort

What to notice

  • Frontend reads message and API URL from ConfigMap
  • Browser reaches the frontend over NodePort
  • Frontend reaches API over ClusterIP

Run

kubectl apply -f k8s-lab/frontend-deployment.yaml
kubectl apply -f k8s-lab/frontend-service.yaml
kubectl port-forward svc/frontend-service 8088:9090
Lab Runbook

Step 4: scale, roll forward, rollback, and clean up

15 / 17
kubectl scale deployment/api --replicas=3
kubectl get pods -w

kubectl set image deployment/api \
  api=nicholasjackson/fake-service:v0.26.2
kubectl rollout status deployment/api
kubectl rollout history deployment/api

kubectl rollout undo deployment/api

kubectl delete namespace workloads-lab

What to notice

  • scale changes desired state
  • set image starts the rollout
  • rollout undo reverses a bad release
  • Namespace delete removes the lab cleanly

Check

  • Watch the new ReplicaSet appear
  • Confirm v0.26.2 is live
  • Run rollback once
Watch Outs

Common mistakes in workload design

16 / 17

Mistake 1

Creating naked Pods and calling that a deployment model.

Mistake 2

Hardcoding passwords or URLs into images instead of externalizing them.

Mistake 3

Skipping requests, limits, readiness checks, and rollout observation.

Quiz

5-minute knowledge check

17 / 17
1. Why is a Deployment preferred over creating Pods directly for stateless apps?
2. Which Service type would you use for internal pod-to-pod communication?
3. What is the difference between a ConfigMap and a Secret?
4. What happens when a container exceeds its memory limit?
5. Which command lets you roll back a failed Deployment rollout?

Takeaways

Deployment for stateless
Service for stable access
Config outside image
Limits for safety
Rolling updates for safer change