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.
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
| Workload | Purpose | Key Characteristic |
|---|---|---|
| Deployment | Stateless services | Rolling updates, scale-out |
| StatefulSet | Stateful services | Stable network ID, ordered ops |
| DaemonSet | Per-node agents | One Pod per node |
| Job | One-time tasks | Terminates on completion |
| CronJob | Scheduled tasks | Creates 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 updatereadinessProbe: only send traffic to ready PodsterminationGracePeriodSeconds: 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
| Criterion | Deployment | StatefulSet |
|---|---|---|
| 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
| Value | Behavior |
|---|---|
| Allow | Start new job even if previous is still running (default) |
| Forbid | Skip new job if previous is still running |
| Replace | Cancel 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.