feat(root): author workloads curriculum
root-06-m02-deployments-scaling-updates — a new, full gold-standard- pattern module: why Deployments/ReplicaSets exist, how a rolling update actually progresses, and what rollback restores. Lesson, demonstration (a healthy rollout vs. one stalled on ImagePullBackOff), knowledge check, guided exercise (planning a safe rollout strategy), real Labs (rolling-update-task, deployment-rollback), and a reflection. root-06-m03-jobs-cronjobs-daemonsets — a new PARTIAL module: Jobs, CronJobs, and DaemonSets as distinct workload shapes from a Deployment's always-on model. Lesson + knowledge check + real Labs (job-task, cronjob-task). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+85
@@ -0,0 +1,85 @@
|
||||
# Jobs, CronJobs, and DaemonSets: The Other Workload Shapes
|
||||
|
||||
Everything in the previous module assumed a long-running application that should always have N
|
||||
replicas up. Not every workload looks like that. Kubernetes has three other primitives for
|
||||
genuinely different shapes of work — using a Deployment for any of these would be the wrong
|
||||
tool, not just a less elegant one.
|
||||
|
||||
## Job: run to completion, exactly once (or a fixed number of times)
|
||||
|
||||
A Job creates one or more Pods and considers itself done when they exit successfully — it does
|
||||
NOT restart a Pod that exits with success, and it DOES retry one that fails, up to a configured
|
||||
limit. This is fundamentally different from a Deployment, which expects its Pods to run forever
|
||||
and treats any exit as something to replace.
|
||||
|
||||
Use a Job for: a database migration, a batch data-processing task, a one-off cleanup script —
|
||||
anything with a defined "finished" state.
|
||||
|
||||
```yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: migrate-db
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: migrate
|
||||
image: myapp:latest
|
||||
command: ["./migrate.sh"]
|
||||
restartPolicy: Never
|
||||
backoffLimit: 3
|
||||
```
|
||||
|
||||
## CronJob: a Job, on a schedule
|
||||
|
||||
A CronJob doesn't run Pods itself — it creates a new Job on a cron schedule (`schedule:
|
||||
"0 2 * * *"` for "every day at 2am"), and that Job behaves exactly as described above. Use a
|
||||
CronJob for anything that should run periodically and finish: nightly backups, scheduled
|
||||
reports, periodic cleanup.
|
||||
|
||||
A CronJob that reliably fails to complete (an image that always crashes, for example) will keep
|
||||
creating new Jobs on schedule, each failing the same way — this is a real, common way to
|
||||
silently exhaust cluster resources (job/pod history accumulating) if nobody's watching.
|
||||
|
||||
## DaemonSet: exactly one Pod per node, automatically
|
||||
|
||||
A DaemonSet doesn't declare a replica count at all — it ensures exactly one matching Pod runs
|
||||
on every node in the cluster (or every node matching a selector), and automatically adds a Pod
|
||||
when a new node joins. Use a DaemonSet for per-node infrastructure: a log-shipping agent, a
|
||||
node-level monitoring exporter, a network plugin component — things that fundamentally need to
|
||||
exist once per machine, not "N copies somewhere."
|
||||
|
||||
## The decision in one pass
|
||||
|
||||
| You need... | Use |
|
||||
|---|---|
|
||||
| Always-on, N identical copies, restarted forever | Deployment |
|
||||
| Run once (or a few times) until it finishes successfully | Job |
|
||||
| The above, but on a recurring schedule | CronJob |
|
||||
| Exactly one copy on every node | DaemonSet |
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Demo plan (if extending this PARTIAL module later):** live-create a Job with a command that
|
||||
deliberately fails once then succeeds (`backoffLimit: 2`), and watch `kubectl get pods` show
|
||||
the failed attempt alongside the eventual successful one — makes the retry-until-backoffLimit
|
||||
behavior concrete.
|
||||
|
||||
**Likely misconceptions to watch for:**
|
||||
- Students often assume a Job that finishes successfully gets cleaned up automatically. It
|
||||
doesn't by default — the completed Pod/Job objects remain until manually deleted or a
|
||||
`ttlSecondsAfterFinished` is configured (not covered here, worth a one-line mention if asked).
|
||||
- "DaemonSet" is sometimes confused with "one Pod, cluster-wide" (singular). It's the opposite —
|
||||
one Pod PER NODE, which can be many Pods total in a multi-node cluster.
|
||||
|
||||
**Discussion question:** "Why doesn't a Deployment support 'run once and be done'?" (Answer: a
|
||||
Deployment's whole contract is maintaining a standing replica count forever — repurposing it for
|
||||
one-shot work would fight that contract rather than use a primitive designed for it.)
|
||||
|
||||
**Optional extension:** ask what would go wrong if a CronJob's schedule ran more often than a
|
||||
single Job execution takes to complete (answer: overlapping Job runs, unless
|
||||
`concurrencyPolicy` is set to `Forbid` or `Replace` — a real gotcha worth naming even without
|
||||
building a full lab around it).
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# Knowledge Check: Choosing the Right Workload Primitive
|
||||
|
||||
---
|
||||
|
||||
**1. You need to run a database migration script exactly once when a new version deploys. Which
|
||||
primitive fits, and why not a Deployment?**
|
||||
|
||||
*Answer:* A Job. A Deployment expects its Pods to run forever and replaces any that exit; a
|
||||
migration script is supposed to exit successfully when done, which a Deployment would treat as
|
||||
a failure to "fix" by restarting it — the wrong behavior for a task that's supposed to finish.
|
||||
|
||||
---
|
||||
|
||||
**2. A CronJob scheduled for "every 5 minutes" keeps creating Jobs that fail immediately due to
|
||||
a bad image tag. What actually happens over the next hour if nobody notices?**
|
||||
|
||||
*Answer:* A new failing Job (and its Pod) gets created roughly every 5 minutes, accumulating
|
||||
failed Job/Pod objects and consuming cluster resources — nothing stops this automatically. This
|
||||
is exactly the kind of slow, easy-to-miss resource exhaustion a real on-call engineer should
|
||||
watch for.
|
||||
|
||||
---
|
||||
|
||||
**3. True or false: a DaemonSet's Pod count scales with a `replicas` field, the same as a
|
||||
Deployment.**
|
||||
|
||||
*Answer:* False. A DaemonSet has no `replicas` field — its Pod count is implicitly "one per
|
||||
matching node," and it grows or shrinks automatically as nodes join or leave the cluster.
|
||||
|
||||
---
|
||||
|
||||
**4. You need a log-shipping agent that must run on every node, including any new node added
|
||||
later. Which primitive, and what would go wrong if you used a Deployment with a high replica
|
||||
count instead?**
|
||||
|
||||
*Answer:* A DaemonSet. A Deployment's replicas are placed by the scheduler wherever it decides
|
||||
is best — nothing guarantees one per node, and a newly-added node wouldn't automatically get a
|
||||
copy the way a DaemonSet guarantees.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth emphasizing — it's a genuinely realistic "how does a
|
||||
cluster quietly get into trouble" scenario, not just trivia.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
Reference in New Issue
Block a user