Search…

Kubernetes workload types

In this series (14 parts)
  1. Why Kubernetes exists
  2. Kubernetes architecture
  3. Core Kubernetes objects
  4. Kubernetes networking
  5. Storage in Kubernetes
  6. Kubernetes configuration and secrets
  7. Resource management and autoscaling
  8. Kubernetes workload types
  9. Kubernetes observability
  10. Kubernetes security
  11. Helm and package management
  12. GitOps with ArgoCD
  13. Kubernetes cluster operations
  14. Service mesh concepts

Every pod in Kubernetes is ephemeral. It can be killed, rescheduled, or replaced at any moment. Raw pods are not useful in production. Controllers are what give pods their operational behavior: how many replicas run, how updates roll out, whether pods need stable identities, and whether they run once or on a schedule.

Kubernetes ships five workload controllers. Each one solves a distinct scheduling problem. Picking the wrong one means fighting the platform instead of using it.

Deployment: stateless applications

A Deployment manages a set of identical, interchangeable pods. It is the default choice for stateless services like API servers, web frontends, and worker processes that do not need persistent local state. The Deployment controller ensures the desired number of replicas are running and handles rolling updates when you change the pod spec.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  labels:
    app: api-server
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api
          image: myregistry/api-server:1.4.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10

Setting maxUnavailable: 0 ensures that no existing pod is terminated until its replacement passes its readiness probe. Setting maxSurge: 1 limits the extra capacity created during rollout. This combination gives you zero-downtime deploys at the cost of needing one extra pod worth of resources during transitions.

stateDiagram-v2
  [*] --> Complete
  Complete --> Progressing: spec update
  Progressing --> Progressing: pods rolling
  Progressing --> Complete: all replicas ready
  Progressing --> Failed: deadline exceeded
  Failed --> Progressing: spec update or rollback

Deployment rollout states. A spec change moves the Deployment to Progressing. It returns to Complete once all new replicas are ready.

If a rollout stalls, kubectl rollout undo deployment/api-server reverts to the previous ReplicaSet. Kubernetes keeps a history of ReplicaSets (controlled by revisionHistoryLimit) so you can roll back quickly.

StatefulSet: stable identity and persistent storage

Some workloads cannot treat pods as interchangeable. Databases, message brokers, and consensus systems need stable network identities and persistent storage that survives rescheduling. StatefulSets provide both.

Each pod in a StatefulSet gets an ordinal index. Pod names follow the pattern <statefulset-name>-<ordinal>, so postgres-0, postgres-1, postgres-2. Combined with a headless Service, each pod gets a stable DNS name like postgres-0.postgres-headless.default.svc.cluster.local. This identity persists across restarts.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: pgdata
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: pgdata
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 50Gi

The volumeClaimTemplates field creates a unique PersistentVolumeClaim for each pod. When postgres-1 is rescheduled, Kubernetes reattaches the same PVC. Data is not lost.

Pods are created in order (0, then 1, then 2) and terminated in reverse. This ordered startup matters for workloads like ZooKeeper where a quorum must form before followers join. If you do not need ordered operations, set podManagementPolicy: Parallel to speed things up.

DaemonSet: one pod per node

DaemonSets ensure exactly one copy of a pod runs on every node in the cluster (or a subset selected by node selectors or tolerations). They are the right choice for node-level infrastructure: log collectors, monitoring agents, network plugins, and storage drivers.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-collector
spec:
  selector:
    matchLabels:
      app: log-collector
  template:
    metadata:
      labels:
        app: log-collector
    spec:
      containers:
        - name: fluentbit
          image: fluent/fluent-bit:3.0
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log

When a new node joins the cluster, the DaemonSet controller automatically schedules a pod on it. When a node is removed, that pod is garbage collected. You do not manage replica counts. The cluster topology is the replica count.

DaemonSets support rolling updates. By default they use OnDelete, which only replaces pods when you manually delete them. Switch to RollingUpdate with maxUnavailable: 1 for automated rollouts.

Job: one-off batch work

A Job creates one or more pods and ensures they run to successful completion. Use Jobs for data migrations, batch processing, report generation, or any task that should run once and then stop.

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  backoffLimit: 3
  activeDeadlineSeconds: 600
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: myregistry/db-migrate:2.1.0
          command: ["./migrate", "--target", "v42"]
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url

backoffLimit: 3 means Kubernetes retries up to three times on failure. activeDeadlineSeconds: 600 kills the Job if it has not completed within 10 minutes. restartPolicy: Never ensures failed pods are not restarted in place, so the Job controller creates new pods for retries.

For parallel batch work, set completions and parallelism:

spec:
  completions: 10
  parallelism: 3

This runs 10 tasks total, 3 at a time. Each pod picks up one unit of work from a queue.

CronJob: scheduled batch work

A CronJob wraps a Job spec with a cron schedule. It is for recurring tasks like nightly backups, hourly aggregations, or periodic cleanup.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: myregistry/pg-backup:1.0.0
              command: ["./backup.sh"]
              env:
                - name: S3_BUCKET
                  value: "my-backups"

concurrencyPolicy: Forbid prevents a new Job from starting if the previous run is still active. Other options are Allow (the default, which lets Jobs overlap) and Replace (which kills the running Job before starting a new one).

The schedule field uses standard cron syntax. 0 2 * * * runs at 2:00 AM UTC every day. Keep in mind that CronJobs depend on the kube-controller-manager clock, so skew or downtime can cause missed runs.

Choosing the right workload type

The decision comes down to two questions. Is your workload long-running or batch? Does it need stable identity?

RequirementController
Stateless, long-running, horizontally scalableDeployment
Stable network identity, persistent per-pod storageStatefulSet
Exactly one pod per nodeDaemonSet
Run to completion, then stopJob
Run to completion on a scheduleCronJob

Start with a Deployment. Most services are stateless. Only reach for a StatefulSet when you have a concrete requirement for ordered startup, stable DNS names, or per-pod persistent volumes. If you find yourself attaching PVCs to a Deployment and writing logic to route traffic to specific pods, you need a StatefulSet.

DaemonSets are infrastructure. If your team manages the cluster itself (logging, networking, security agents), you will use DaemonSets. Application teams rarely create them directly.

Jobs and CronJobs handle batch work. If you are running a one-off migration, use a Job. If that migration needs to happen every night, wrap it in a CronJob.

What comes next

Workload controllers keep pods running, but they do not tell you whether those pods are healthy, performing well, or about to fall over. The next post covers observability, where you will set up metrics, logs, and traces so you can actually see what your workloads are doing in production.

Start typing to search across all content
navigate Enter open Esc close