Pods and Workloads

intermediate kubernetes pods deployments statefulsets

A Pod is the smallest thing we can deploy in Kubernetes. It wraps one or more containers that share the same network and storage. Most of the time, it’s just one container per Pod. Think of a Pod as a thin wrapper around our container that gives Kubernetes something to manage.

Pod Lifecycle

Every Pod goes through these phases:

  • Pending — accepted by the cluster, but containers aren’t running yet (maybe pulling images or waiting for scheduling)
  • Running — at least one container is running
  • Succeeded — all containers finished successfully (exit code 0)
  • Failed — all containers terminated, at least one failed
  • Unknown — can’t get the Pod’s status (usually a node communication issue)
# A basic Pod definition — we rarely write these directly
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
    - name: app
      image: nginx:1.27
      ports:
        - containerPort: 80

Multi-Container Patterns

Sometimes we need more than one container in a Pod. The containers share localhost and can share volumes.

Sidecar — a helper container that runs alongside our main app. Common examples: log collector, service mesh proxy (Envoy), or a config reloader.

Init Container — runs before the main container starts. Useful for setup tasks like waiting for a database to be ready or running migrations.

spec:
  initContainers:
    - name: wait-for-db
      image: busybox
      command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']
  containers:
    - name: app
      image: my-app:latest

Deployments — The Go-To Workload

We almost never create Pods directly. Instead, we use a Deployment, which manages Pods for us. It gives us:

  • Desired state — “I want 3 replicas running at all times”
  • Rolling updates — updates Pods one by one so there’s zero downtime
  • Rollback — if something goes wrong, roll back to the previous version
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3                    # keep 3 Pods running
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:v2
          ports:
            - containerPort: 8080

A Deployment creates a ReplicaSet under the hood, which is the thing that actually ensures the right number of Pods are running. We don’t manage ReplicaSets directly — the Deployment handles it.

# Common Deployment commands
kubectl rollout status deployment/my-app    # watch the rollout
kubectl rollout history deployment/my-app   # see revision history
kubectl rollout undo deployment/my-app      # rollback to previous version

StatefulSets — When Identity Matters

Deployments treat all Pods as interchangeable. But some workloads need stable identity — databases, message queues, etc. StatefulSets give each Pod:

  • A stable hostname (my-db-0, my-db-1, my-db-2)
  • Persistent storage that sticks with the Pod even if it restarts
  • Ordered startup/shutdown (Pod 0 starts first, then Pod 1, etc.)

We use these for things like PostgreSQL clusters, Kafka brokers, or Redis Sentinel.

DaemonSets — One Pod Per Node

A DaemonSet ensures that every node (or a subset) runs exactly one copy of a Pod. When a new node joins the cluster, the DaemonSet automatically schedules a Pod on it. Perfect for:

  • Log collectors (Fluentd, Filebeat)
  • Monitoring agents (Prometheus Node Exporter)
  • Network plugins (Calico, Cilium)

Jobs and CronJobs

Job — runs a Pod to completion and then stops. Great for batch tasks like data processing or database migrations.

CronJob — a Job on a schedule, just like a Linux cron. Runs at specified times.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"        # every day at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: backup
              image: backup-tool:latest
          restartPolicy: OnFailure

In simple language, Pods are the atomic unit, but we almost always manage them through higher-level workloads. Deployments for stateless apps, StatefulSets for databases, DaemonSets for per-node agents, and Jobs for one-off tasks.