Kubernetes Workloads
Each major section below ends with a quick knowledge check — try to answer before revealing. Track your progress as you go:
Pod Lifecycle
stateDiagram-v2
[*] --> Pending: Pod created, waiting for scheduler
Pending --> Running: Node assigned, containers starting
Running --> Succeeded: All containers exited 0 (Job complete)
Running --> Failed: Container exited non-zero, restartPolicy=Never
Running --> Running: Container crash, restartPolicy=Always (CrashLoopBackOff if repeated)
Pending --> Failed: Image pull error, resource unavailable
graph LR
classDef phase fill:#3498db,stroke:#2980b9,color:#fff
classDef cstate fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef good fill:#2ecc71,stroke:#27ae60,color:#fff
classDef bad fill:#e74c3c,stroke:#c0392b,color:#fff
classDef warn fill:#f39c12,stroke:#d68910,color:#000
PENDING["Pending Scheduled, pulling image"]:::phase
RUNNING["Running At least 1 container running"]:::phase
SUCCEEDED["Succeeded All containers exited 0"]:::good
FAILED["Failed Container exited non-zero"]:::bad
UNKNOWN["Unknown Node unreachable"]:::warn
PENDING --> RUNNING
RUNNING --> SUCCEEDED
RUNNING --> FAILED
RUNNING --> UNKNOWN
Step through the phases one at a time:
restartPolicy: Never means kubelet won't try again — also terminal. Contrast with restartPolicy: Always, where the same crash keeps the pod phase at Running while the container itself cycles through CrashLoopBackOff.
Container states within a Running pod:
| State | Meaning |
|---|---|
Waiting |
Starting, pulling image, or CrashLoopBackOff |
Running |
Process is executing |
Terminated |
Process exited (check exitCode: 0=success, 137=OOMKilled, 1=error) |
CrashLoopBackOff — container keeps crashing. Kubernetes applies exponential backoff (10s → 20s → 40s → 80s → 160s → 5min cap) between restart attempts. Not a stuck state — it will keep retrying. Check logs with kubectl logs <pod> --previous.
A container keeps crashing but the pod's restartPolicy is Always. Does the pod phase flip to Failed?
restartPolicy: Always, kubelet keeps restarting the container — the pod phase stays Running the whole time, even while a single container cycles through CrashLoopBackOff with growing backoff delays. Failed only happens when a container exits non-zero under restartPolicy: Never.Probes
Probes are health checks kubelet runs against your container. Three types, each with a distinct purpose.
graph TD
classDef probe fill:#8e44ad,stroke:#6c3483,color:#fff
classDef action fill:#e74c3c,stroke:#c0392b,color:#fff
classDef ok fill:#2ecc71,stroke:#27ae60,color:#fff
classDef note fill:#f39c12,stroke:#d68910,color:#000
subgraph StartupProbe["Startup Probe"]
SP["Runs FIRST during container startup Blocks liveness+readiness until it passes For slow-starting apps (legacy, DB migrations)"]:::probe
SP -->|"fails failureThreshold times"| KILL1["Container killed and restarted"]:::action
SP -->|"passes"| SP_OK["Liveness + Readiness probes begin"]:::ok
end
subgraph LivenessProbe["Liveness Probe"]
LP["Runs throughout pod lifetime Is the process still alive and not deadlocked?"]:::probe
LP -->|"fails consecutively"| KILL2["Container killed and restarted kubelet respects restartPolicy"]:::action
LP -->|"passes"| LP_OK["Container stays running"]:::ok
end
subgraph ReadinessProbe["Readiness Probe"]
RP["Runs throughout pod lifetime Is the process ready to serve traffic?"]:::probe
RP -->|"fails"| REMOVE["Pod IP removed from Service endpoints Traffic stops flowing to this pod"]:::action
RP -->|"passes"| ADD["Pod IP added to Service endpoints Traffic flows to pod"]:::ok
end
Step through the evaluation order:
failureThreshold times in a row. kubelet kills the container and restarts it per restartPolicy. The pod itself isn't removed — this is a container-level action.
Probe implementation types:
# HTTP GET — most common for web services
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10 # wait before first probe (startup time)
periodSeconds: 10 # probe every 10s
failureThreshold: 3 # fail 3 times before action
timeoutSeconds: 5
# TCP Socket — for non-HTTP (gRPC, databases)
readinessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 5
# Exec — run command inside container
livenessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
periodSeconds: 30
# gRPC (K8s 1.24+)
livenessProbe:
grpc:
port: 8080
service: "liveness"
Key probe interactions:
- Liveness failure → container restart (pod stays, container restarts)
- Readiness failure → pod removed from service endpoints (pod stays running, just gets no traffic)
- Startup probe present → liveness and readiness are BLOCKED until startup passes
A container's readiness probe starts failing, but its liveness probe keeps passing. Does kubelet restart the container?
QoS Classes
Kubernetes assigns a QoS class to every pod. This determines OOM kill priority when a node runs out of memory.
graph TD
classDef guaranteed fill:#2ecc71,stroke:#27ae60,color:#fff
classDef burstable fill:#f39c12,stroke:#d68910,color:#000
classDef besteffort fill:#e74c3c,stroke:#c0392b,color:#fff
subgraph G["Guaranteed — OOM killed LAST"]
G1["requests == limits for ALL containers cpu: 500m request = 500m limit memory: 256Mi request = 256Mi limit"]:::guaranteed
end
subgraph B["Burstable — OOM killed if over request"]
B1["requests < limits (or only requests set) cpu: 200m request, 1000m limit memory: 128Mi request, 512Mi limit"]:::burstable
end
subgraph BE["BestEffort — OOM killed FIRST"]
BE1["No requests, no limits set Kernel kills these first under memory pressure"]:::besteffort
end
G --> B --> BE
BE -.- NOTE["OOM kill order: BestEffort first, then Burstable over their request, then Guaranteed (almost never)"]
Production recommendation: Set both requests and limits for all containers. Use Guaranteed class for critical services (controllers, databases). Never run production workloads as BestEffort.
A container sets a memory request of 128Mi and a memory limit of 512Mi, with no CPU request/limit set at all. What QoS class does the pod get?
Rolling Updates and Rollbacks
graph LR
classDef v1 fill:#3498db,stroke:#2980b9,color:#fff
classDef v2 fill:#2ecc71,stroke:#27ae60,color:#fff
classDef terminating fill:#e74c3c,stroke:#c0392b,color:#fff
classDef rs fill:#9b59b6,stroke:#8e44ad,color:#fff
subgraph Start["Start: 3x v1 running"]
V1A["v1 pod"]:::v1
V1B["v1 pod"]:::v1
V1C["v1 pod"]:::v1
end
subgraph Step1["maxSurge=1, maxUnavailable=0 Create 1 new v2 pod (surge)"]
V1D["v1 pod"]:::v1
V1E["v1 pod"]:::v1
V1F["v1 pod"]:::v1
V2A["v2 pod (new)"]:::v2
end
subgraph Step2["v2 pod passes readiness Terminate 1 v1 pod"]
V1G["v1 pod"]:::v1
V1H["v1 pod"]:::v1
V1I["v1 pod terminating"]:::terminating
V2B["v2 pod ✅"]:::v2
end
subgraph End["All 3 replaced with v2"]
V2C["v2 pod"]:::v2
V2D["v2 pod"]:::v2
V2E["v2 pod"]:::v2
end
subgraph ReplicaSets["Deployment keeps both ReplicaSets"]
RS1["ReplicaSet v1 replicas: 0 (kept for rollback)"]:::rs
RS2["ReplicaSet v2 replicas: 3"]:::rs
end
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # max pods ABOVE desired (create new before deleting old)
maxUnavailable: 0 # max pods BELOW desired (zero-downtime: never remove before replacement ready)
Rollback: The old ReplicaSet is never deleted (kept at 0 replicas). Rollback simply scales old RS back up and new RS back down:
kubectl rollout undo deployment/my-app # rollback to previous
kubectl rollout undo deployment/my-app --to-revision=3 # rollback to specific revision
kubectl rollout history deployment/my-app # see revision history
kubectl rollout status deployment/my-app # watch rollout progress
kubectl rollout pause deployment/my-app # pause mid-rollout (canary hold)
kubectl rollout resume deployment/my-app # resume
After a Deployment rolls out v2, is the v1 ReplicaSet deleted?
kubectl rollout undo fast: rollback just scales the old ReplicaSet back up and the new one back down, no pod specs need to be recreated from scratch.StatefulSet vs Deployment
graph TD
classDef deploy fill:#3498db,stroke:#2980b9,color:#fff
classDef stateful fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef pvc fill:#2ecc71,stroke:#27ae60,color:#fff
classDef storage fill:#e67e22,stroke:#d35400,color:#fff
subgraph DeploymentModel["Deployment: interchangeable pods"]
D1["app-7f9d4-abc random name"]:::deploy
D2["app-7f9d4-def random name"]:::deploy
D3["app-7f9d4-ghi random name"]:::deploy
SHARED_STORAGE["Shared storage or no persistent storage"]:::storage
D1 --- SHARED_STORAGE
D2 --- SHARED_STORAGE
end
subgraph StatefulSetModel["StatefulSet: stable identity"]
SS1["postgres-0 stable name, index 0"]:::stateful
SS2["postgres-1 stable name, index 1"]:::stateful
SS3["postgres-2 stable name, index 2"]:::stateful
PVC1["PVC: postgres-data-postgres-0 bound only to postgres-0"]:::pvc
PVC2["PVC: postgres-data-postgres-1 bound only to postgres-1"]:::pvc
PVC3["PVC: postgres-data-postgres-2"]:::pvc
SS1 --> PVC1
SS2 --> PVC2
SS3 --> PVC3
end
StatefulSet guarantees:
| Guarantee | Detail |
|---|---|
| Stable pod names | <name>-0, <name>-1, <name>-2 — never random |
| Stable DNS | postgres-0.postgres-svc.ns.svc.cluster.local (requires Headless Service) |
| Stable storage | Each pod gets its own PVC via volumeClaimTemplates. PVC survives pod deletion. |
| Ordered startup | Pods start in order: 0 → 1 → 2. Each must be Running+Ready before next starts. |
| Ordered shutdown | Pods stop in reverse order: 2 → 1 → 0. |
When to use StatefulSet: Databases (PostgreSQL, MySQL), distributed systems with leader election (Kafka, Zookeeper, etcd), any app that needs stable network identity or per-instance storage.
A StatefulSet pod postgres-1 is deleted and recreated. Does it get a new PVC?
postgres-data-postgres-1) survives pod deletion and the recreated postgres-1 reattaches to the exact same volume — that's the whole point of per-pod PVCs via volumeClaimTemplates. Deleting the PVC itself is a separate, manual action.ReplicaSet vs StatefulSet — Deep Comparison
ReplicaSet (via Deployment) — Stateless Pods
A ReplicaSet ensures N identical, interchangeable pod replicas are running. You never create a ReplicaSet directly — you use a Deployment which manages ReplicaSets for you (rolling updates, rollback).
graph TD
DEP["Deployment<br/>manages ReplicaSets"] --> RS_OLD["ReplicaSet v1<br/>replicas: 0 (kept for rollback)"]
DEP --> RS_NEW["ReplicaSet v2<br/>replicas: 3"]
RS_NEW --> P1["api-7f9d4-abc<br/>random suffix"]
RS_NEW --> P2["api-7f9d4-def<br/>random suffix"]
RS_NEW --> P3["api-7f9d4-ghi<br/>random suffix"]
P1 & P2 & P3 --> PVC_SHARED["Shared PVC (optional)<br/>ReadWriteMany<br/>e.g. EFS"]
classDef pod fill:#3498db,stroke:#2980b9,color:#fff
classDef rs fill:#e67e22,stroke:#d35400,color:#fff
class P1,P2,P3 pod
class RS_OLD,RS_NEW rs
Pod identity: none. Pod api-7f9d4-abc is identical to api-7f9d4-def. If one dies, the ReplicaSet creates a replacement with a new random name — no data is lost because stateless pods don't hold state.
Scale up/down: all pods started/stopped in parallel, any order. Scale from 3→5: 2 new pods created simultaneously.
StatefulSet — Stateful Pods with Identity
graph TD
STS["StatefulSet: postgres"] --> HS["Headless Service<br/>clusterIP: None<br/>postgres-svc"]
STS --> P0["postgres-0<br/>stable index"]
STS --> P1["postgres-1<br/>stable index"]
STS --> P2["postgres-2<br/>stable index"]
P0 --> PVC0["PVC: data-postgres-0<br/>always bound to postgres-0"]
P1 --> PVC1["PVC: data-postgres-1<br/>always bound to postgres-1"]
P2 --> PVC2["PVC: data-postgres-2<br/>always bound to postgres-2"]
HS --> DNS0["DNS: postgres-0.postgres-svc.default.svc.cluster.local"]
HS --> DNS1["DNS: postgres-1.postgres-svc.default.svc.cluster.local"]
classDef pod fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef pvc fill:#2ecc71,stroke:#27ae60,color:#fff
class P0,P1,P2 pod
class PVC0,PVC1,PVC2 pvc
Headless Service (clusterIP: None) — required for StatefulSet DNS. Unlike a regular Service (which load-balances), a Headless Service creates individual DNS records for each pod. postgres-0.postgres-svc always resolves to postgres-0's IP — clients can target a specific pod (e.g., write to primary, read from replicas).
Ordered Startup and Deletion
sequenceDiagram
participant STS as StatefulSet Controller
participant P0 as postgres-0
participant P1 as postgres-1
participant P2 as postgres-2
Note over STS: Scale up (0 --> 3)
STS->>P0: create postgres-0
P0-->>STS: Running + Ready
STS->>P1: create postgres-1 (only after P0 ready)
P1-->>STS: Running + Ready
STS->>P2: create postgres-2 (only after P1 ready)
P2-->>STS: Running + Ready
Note over STS: Scale down (3 --> 1)
STS->>P2: delete postgres-2 (highest index first)
P2-->>STS: terminated
STS->>P1: delete postgres-1
P1-->>STS: terminated
Note over P0: postgres-0 remains
Why ordered? Databases need the primary (index 0) to be healthy before replicas join. Shutting down the replica before the primary prevents split-brain.
Scaling a StatefulSet down from 3 to 1, which pod is deleted first?
postgres-2, then postgres-1. Scale-down always proceeds in reverse order, opposite of scale-up, so the primary (index 0) is the last one standing and never the one torn down mid-shrink.Which Applications Use Which
graph TD
APP["Your Application"] --> Q1{"Does each instance<br/>need its own<br/>persistent data?"}
Q1 -->|No| Q2{"Multiple instances<br/>interchangeable?"}
Q1 -->|Yes| STATEFUL["StatefulSet"]
Q2 -->|Yes| DEPLOYMENT["Deployment + ReplicaSet"]
Q2 -->|No singleton| SINGLETON["Deployment replicas:1<br/>or StatefulSet replicas:1"]
classDef dep fill:#3498db,stroke:#2980b9,color:#fff
classDef sts fill:#9b59b6,stroke:#8e44ad,color:#fff
class DEPLOYMENT dep
class STATEFUL,SINGLETON sts
Deployment (ReplicaSet) — Stateless Workloads
| Application | Why Deployment |
|---|---|
| REST API servers | All pods identical, share DB connection, any pod can handle any request |
| GraphQL / gRPC services | Stateless request handling |
| Web frontends | No per-pod data |
| Worker processes (SQS consumer) | Any worker can process any message |
| Batch processors | Jobs consume from a queue, no local state |
| ML inference servers (vLLM, TorchServe) | Model loaded in each pod's memory but no unique per-pod data to persist |
| API gateways, proxies | Pure request routing, stateless |
StatefulSet — Stateful Workloads
| Application | Why StatefulSet | Key requirement |
|---|---|---|
| PostgreSQL | Each replica has its own WAL + data directory | Per-pod PVC, ordered startup (primary first) |
| MySQL / MariaDB | Master-replica with per-node data | Stable names for replication config |
| Redis (cluster mode) | Each shard owns specific key slots | Stable identity for cluster topology |
| Apache Kafka | Each broker owns specific partitions on disk | Stable broker ID = stable pod name |
| Zookeeper | Quorum requires stable myid | zoo-0, zoo-1, zoo-2 fixed identity |
| Elasticsearch | Each node stores specific shards | Per-pod PVC, stable cluster node names |
| Cassandra | Each node owns a token range | Stable gossip identity |
| etcd | Raft consensus requires stable peer URLs | etcd-0.etcd etc. |
The Key Question to Ask
"If I delete this pod and a new one starts with a different name and no local data,
does the application still work correctly?"
YES → Deployment
NO → StatefulSet
Redis Standalone (replicas:1, in-memory cache): Deployment — if it restarts, it starts fresh. The cache warms up again. Acceptable.
Redis Cluster (sharded, data must persist): StatefulSet — each node owns data, must reconnect to cluster with same identity.
PostgreSQL primary: StatefulSet — data on disk, WAL, per-node state. Restarting with a different name would break replication setup.
A worker pod deletes itself and a replacement starts with a completely different name and no data carried over from the old one. Does the app still work correctly?
Comparison Table
| Feature | Deployment (ReplicaSet) | StatefulSet |
|---|---|---|
| Pod names | Random (app-abc123-xyz) |
Stable (app-0, app-1) |
| Pod DNS | Single Service (load-balanced) | Headless Service (per-pod DNS) |
| Storage | Shared PVC or no PVC | Per-pod PVC via volumeClaimTemplates |
| PVC on pod delete | PVC deleted with pod | PVC survives pod deletion |
| Scale up order | Parallel (all at once) | Sequential (0, 1, 2...) |
| Scale down order | Any order | Reverse (N, N-1, N-2...) |
| Rolling update | New RS created, old RS scaled down | Pod-by-pod, highest index first |
| Use case | Stateless APIs, workers | Databases, queues, consensus systems |
Init Containers
Init containers run sequentially before app containers start. Each must exit 0 before the next runs. If any init container fails, the pod restarts.
sequenceDiagram
participant K as kubelet
participant I1 as init-container-1
participant I2 as init-container-2
participant APP as app container
K->>I1: start (wait-for-db)
Note over I1: loops until postgres:5432 is reachable
I1-->>K: exit 0
K->>I2: start (run-migrations)
I2-->>K: exit 0 (migrations done)
K->>APP: start main application
Note over APP: DB is ready AND migrated before app starts
spec:
initContainers:
# 1. Wait for dependency to be ready
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z postgres-svc 5432; do echo waiting; sleep 2; done']
# 2. Run DB migration before app starts
- name: migrate
image: my-app:v2
command: ["/app/server", "migrate"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
containers:
- name: app
image: my-app:v2
# starts only after both init containers succeed
Common init container patterns:
| Pattern | Init container does | Why |
|---|---|---|
| Wait for dependency | nc -z svc port loop |
Prevent app crash on startup |
| DB migration | ./migrate up |
Run once before N replicas start |
| Secret injection | Fetch secret from Vault, write to emptyDir | Avoid mounting Vault agent sidecar |
| Config rendering | Render templates with env vars | App expects rendered config, not templates |
| Permission fix | chown shared volume |
App needs specific UID on data directory |
Init containers share volumes with app containers — this is how they pass data:
volumes:
- name: config
emptyDir: {}
initContainers:
- name: render-config
image: renderer:v1
command: ["render", "--output", "/config/app.yaml"]
volumeMounts:
- name: config
mountPath: /config
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/app # reads rendered config written by init container
A pod has two init containers. The first succeeds, the second fails. Does the app container start?
Ephemeral Containers — Debug Running Pods
Ephemeral containers can be injected into a running pod without restarting it. Used for debugging when the app container has no shell (distroless, scratch).
# Inject a debug container into a running pod
kubectl debug -it my-pod \
--image=nicolaka/netshoot \ # has tcpdump, curl, dig, netstat
--target=app \ # shares app container's process namespace
-- bash
# Inside netshoot: debug the running app
ps aux # see app processes (shared PID namespace)
netstat -tlnp # see what ports app is listening on
curl localhost:8080/health
tcpdump -i any port 8080
# Debug with a copy of the pod (for crashlooping pods)
kubectl debug my-crashlooping-pod \
--copy-to=debug-copy \
--image=busybox \
--container=app \ # override the crashing container's image
-- sleep 3600
# Now exec into debug-copy to inspect filesystem, env vars, etc.
graph LR
POD["Running Pod<br/>(distroless — no shell)"] -->|"kubectl debug inject"| EPH["Ephemeral container<br/>nicolaka/netshoot<br/>shares: network NS, PID NS"]
EPH -->|"can see"| PROC["App processes"]
EPH -->|"can curl"| NET["App network"]
EPH -->|"cannot modify"| POD
NOTE["Pod is NOT restarted<br/>Ephemeral container auto-removed when exited"]
Ephemeral vs exec:
kubectl exec— needs a shell already in the containerkubectl debug— injects a new container with tools, works on distroless images
You inject an ephemeral debug container into a running pod with kubectl debug. Does the pod restart?
DaemonSet
Runs exactly one pod per node (or per matching node). When new nodes join the cluster, DaemonSet automatically schedules a pod on them.
graph LR
classDef ds fill:#1abc9c,stroke:#16a085,color:#fff
classDef node fill:#34495e,stroke:#2c3e50,color:#fff
DS["DaemonSet: fluentd-logger"]:::ds
subgraph N1["Node 1"]
P1["fluentd pod (auto-scheduled)"]:::ds
end
subgraph N2["Node 2"]
P2["fluentd pod"]:::ds
end
subgraph N3["Node 3 (new node joins)"]
P3["fluentd pod (auto-added)"]:::ds
end
DS --> P1 & P2 & P3
Common DaemonSet use cases: Log collectors (Fluentd, Fluent Bit), metrics agents (node-exporter), security agents (Falco), network plugins (CNI DaemonSets like aws-node), storage drivers.
DaemonSets can be constrained to specific nodes using nodeSelector or affinity — e.g., run GPU monitoring DaemonSet only on GPU nodes.
Does a DaemonSet have a replicas field you set to control how many pods run?
replicas field at all — the number of pods is implicitly one per matching node. Add a node and a pod appears there automatically; there's nothing to scale manually.Jobs and CronJobs
graph TD
classDef job fill:#e67e22,stroke:#d35400,color:#fff
classDef cron fill:#9b59b6,stroke:#8e44ad,color:#fff
classDef pod fill:#3498db,stroke:#2980b9,color:#fff
classDef done fill:#2ecc71,stroke:#27ae60,color:#fff
subgraph JobFlow["Job: run to completion"]
J["Job: db-migrate completions: 1 parallelism: 1"]:::job
J --> JP["Pod created"]:::pod
JP --> DONE["Pod exits 0 Job status: Complete"]:::done
JP --> FAIL["Pod exits non-zero Job retries (backoffLimit: 4)"]:::job
end
subgraph CronFlow["CronJob: scheduled Jobs"]
CJ["CronJob: nightly-report schedule: 0 2 * * *"]:::cron
CJ -->|"2 AM daily"| JOB1["Job created (new Job each trigger)"]:::job
CJ -->|"next night"| JOB2["Job created"]:::job
end
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
spec:
completions: 1 # how many successful completions needed
parallelism: 1 # how many pods run in parallel
backoffLimit: 4 # retry up to 4 times on failure
activeDeadlineSeconds: 300 # kill if takes > 5 min
template:
spec:
restartPolicy: Never # Job pods must use Never or OnFailure
containers:
- name: migrate
image: my-app:v2
command: ["/app/server", "migrate"]
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid # don't start new job if previous still running
successfulJobsHistoryLimit: 3 # keep last 3 successful job records
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: reporter
image: my-app:v2
command: ["/app/server", "report"]
A CronJob's schedule fires while the previous run's Job is still active, and concurrencyPolicy: Forbid is set. What happens?
Forbid means the CronJob controller won't create a new Job while the previous one is still running. It waits for the next scheduled trigger rather than queuing the missed run.Workload Types at a Glance
Four workload kinds, one question each: what makes them different in practice.
<name>-0, -1, ... never change, each gets its own PVC that survives pod deletion, ordered startup/shutdown, Headless Service for per-pod DNS. Right for databases and quorum systems (Kafka, Zookeeper, etcd) — anything where "which pod" or "which disk" matters.
replicas field — the node count is the pod count. A new node joining the cluster gets a pod scheduled on it automatically, no manual scaling. Right for node-level agents: log shippers, metrics exporters, CNI plugins, security agents.
completions and parallelism instead of a steady replica count, and retries failed pods up to backoffLimit before giving up. A CronJob is just a Job factory on a schedule — each trigger creates a brand-new Job object, it doesn't reuse the last one.