TestForge Blog
← All Posts

Kubernetes Dev & Ops in Practice 3 — Workload Pattern Selection Guide

When to use Kubernetes Deployment, StatefulSet, DaemonSet, Job, and CronJob — with criteria and real-world configurations. Covers key characteristics and operational considerations for each workload type.

TestForge Team ·

Why Workload Selection Matters

Choosing the wrong workload type causes real problems. Running a database as a Deployment instead of a StatefulSet means a Pod restart could attach to a different volume — or, worse, two instances could write to the same data and create a split-brain scenario. Conversely, running a stateless app as a StatefulSet adds unnecessary complexity.


1. Workload Types at a Glance

WorkloadPurposeKey Characteristic
DeploymentStateless servicesRolling updates, scale-out
StatefulSetStateful servicesStable network ID, ordered ops
DaemonSetPer-node agentsOne Pod per node
JobOne-time tasksTerminates on completion
CronJobScheduled tasksCreates Jobs on a cron schedule

2. Deployment — The Stateless Default

Use for web servers, API servers, and workers.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # zero-downtime rollout
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api
          image: my-api:1.2.3
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "200m"
              memory: 256Mi
            limits:
              cpu: "500m"
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
      terminationGracePeriodSeconds: 30

Key Settings

  • maxUnavailable: 0 + maxSurge: 1: fully zero-downtime rolling update
  • readinessProbe: only send traffic to ready Pods
  • terminationGracePeriodSeconds: finish in-flight requests before shutdown

3. StatefulSet — Stateful Services

Use for Redis, Kafka, Elasticsearch, PostgreSQL, and similar stateful workloads.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: production
spec:
  serviceName: redis-headless  # Headless Service required
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:7.2
          ports:
            - containerPort: 6379
          volumeMounts:
            - name: data
              mountPath: /data
          command: ["redis-server", "--appendonly", "yes"]
  volumeClaimTemplates:  # Each Pod gets its own PVC
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 10Gi

Headless Service — Required for StatefulSet

apiVersion: v1
kind: Service
metadata:
  name: redis-headless
  namespace: production
spec:
  clusterIP: None  # Pods are directly resolvable via DNS
  selector:
    app: redis
  ports:
    - port: 6379

With a Headless Service, each Pod is addressable as redis-0.redis-headless.production.svc.cluster.local, which is essential for cluster configuration.

Deployment vs StatefulSet Decision Matrix

CriterionDeploymentStatefulSet
Needs same volume after restart
Ordered roles (primary/replica)
Stable DNS name per Pod
Frequent horizontal scaling△ (caution)

4. DaemonSet — One Per Node

Use for log collectors (Fluent Bit), node monitoring agents (node-exporter), and network plugins that must run exactly once on every node.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: platform
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule  # Also deploy on control-plane nodes
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:3.0
          volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers

Deploy Only to Specific Nodes

spec:
  template:
    spec:
      nodeSelector:
        role: gpu-node  # GPU driver daemon on GPU nodes only

5. Job — One-Off Batch Tasks

Use for database migrations, data cleanup, and seed data loading.

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  namespace: production
spec:
  backoffLimit: 3
  activeDeadlineSeconds: 600  # Terminate if not done in 10 minutes
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migration
          image: my-app:1.2.3
          command: ["python", "manage.py", "migrate"]
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: url

Parallel Job — Large-Scale Data Processing

spec:
  completions: 10  # Require 10 total completions
  parallelism: 3   # Run 3 at a time
  backoffLimit: 5

6. CronJob — Scheduled Batch Tasks

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
  namespace: production
spec:
  schedule: "0 9 * * 1-5"  # Weekdays at 9 AM
  timeZone: "Asia/Seoul"
  concurrencyPolicy: Forbid  # Skip if previous run is still active
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: reporter
              image: my-reporter:latest
              command: ["python", "generate_report.py"]

concurrencyPolicy Options

ValueBehavior
AllowStart new job even if previous is still running (default)
ForbidSkip new job if previous is still running
ReplaceCancel previous job and start new one

Use Forbid for long-running batch jobs to prevent overlapping executions.


7. Init Container — Pre-flight Checks

spec:
  initContainers:
    - name: wait-for-db
      image: busybox
      command:
        - sh
        - -c
        - |
          until nc -z postgres-service 5432; do
            echo "Waiting for database..."; sleep 2
          done
    - name: run-migration
      image: my-app:1.2.3
      command: ["python", "manage.py", "migrate"]
  containers:
    - name: app
      image: my-app:1.2.3

Init Containers run in order. If any fails, the main container does not start.


Summary

Workload Decision Tree

Stateless service?         → Deployment
Needs state or ordering?   → StatefulSet
One per every node?        → DaemonSet
One-time task?             → Job
Recurring scheduled task?  → CronJob

Next: Network Design in Practice — Service type differences, Ingress configuration, and controlling inter-service communication with NetworkPolicy.