feat(curriculum): Phase 6C module upgrades, security assessment bank, ROOT 11 orientation
Upgrades six PARTIAL modules to the gold-module standard (lesson/demonstration/ knowledge-check/guided-exercise/lab(s)/reflection), preserving every existing lab activity_id and scenario_id: root-06-m03 (jobs/cronjobs/daemonsets), root-07-m04 (ingress/networkpolicy), root-07-m02 (configuration/secrets, converted in place from a bare 5-lab bank), root-09-m02 (serviceaccounts/rbac), root-09-m03 (network/supply-chain security), root-10-m03 (troubleshooting framework). root-05-m01 (20-task kubectl-fluency bank) gets a minimal bookending lesson + reflection rather than a full redesign, since each task already carries its own explanation. root-12-m01, root-capstone-m01, and root-09-m04 are promoted to READY via documented exceptions (no new activities) where adding one would misrepresent their intentional design. Wires a new rootnode-security-assessment (exam, 60min, hint_policy disabled) using the existing Phase 3B assessment engine: 3 required hands-on CORE scenarios + 4 random-draw pools covering SecurityContext, RBAC, network policy, and the 7 previously-unwired CKS MCQs. Wires the previously-unwired readiness-probe-task scenario as an OPTIONAL practice lab in root-10-m01, using the existing is_required model. Adds root-11-m01-devops-platform-thinking — an honest, lesson-only ROOT 11 orientation module naming DevOps/platform engineering practices already exercised in ROOT 05-10/12, explicit that GitOps/CI-CD/IaC/observability need lab infrastructure this platform doesn't yet provide. No fake labs. Adds infra/scripts/validate-course-content.js: cross-checks the DB against scenario/content files for duplicate ids, orphans, invalid references, cycles, sequence collisions, instructor-notes leakage, and (new) self-paced content deferring explanation to a live instructor. 0 errors against the resulting 22 courses/modules, 163 activities, 98 scenario files. No application code changed — every gap was satisfiable with existing tables (course_modules, module_activities, assessment_definitions) and the existing content-delivery pipeline. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+72
@@ -0,0 +1,72 @@
|
||||
# Demonstration: Watching a Job Actually Complete
|
||||
|
||||
## Creating the Job
|
||||
|
||||
```
|
||||
$ kubectl apply -f pi-job.yaml
|
||||
job.batch/pi-job created
|
||||
|
||||
$ kubectl get jobs
|
||||
NAME COMPLETIONS DURATION AGE
|
||||
pi-job 0/1 3s 3s
|
||||
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pi-job-8x2kq 1/1 Running 0 3s
|
||||
```
|
||||
|
||||
The Job's own status (`0/1` completions) is tracked separately from its Pod's status — this
|
||||
matters because a Job can retry a FAILED Pod attempt without that looking like the healthy
|
||||
restart behavior you'd see on a Deployment.
|
||||
|
||||
## Once the Pod's process actually exits successfully
|
||||
|
||||
```
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pi-job-8x2kq 0/1 Completed 0 8s
|
||||
|
||||
$ kubectl get jobs
|
||||
NAME COMPLETIONS DURATION AGE
|
||||
pi-job 1/1 7s 8s
|
||||
```
|
||||
|
||||
`STATUS: Completed` — not `Running`, not `CrashLoopBackOff`. This is the ONE status a
|
||||
long-running Deployment's Pod is never supposed to reach on its own; for a Job, it's success.
|
||||
The Job object itself now reports `1/1` and stops — nothing recreates this Pod, because
|
||||
"finished successfully" is the desired end state here, unlike a Deployment where exiting is
|
||||
always treated as something to fix.
|
||||
|
||||
## What a FAILING Job attempt looks like, briefly
|
||||
|
||||
```
|
||||
$ kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
bad-job-abc12 0/1 Error 0 5s
|
||||
bad-job-def34 0/1 Error 0 2s
|
||||
|
||||
$ kubectl describe job bad-job
|
||||
...
|
||||
Events:
|
||||
Warning BackoffLimitExceeded Job has reached the specified backoff limit
|
||||
```
|
||||
|
||||
Two DIFFERENT Pods (`abc12`, `def34`), not one Pod restarting twice — each failed attempt gets
|
||||
its own new Pod, up to `backoffLimit`. Once that limit is hit, the Job stops retrying and
|
||||
reports the failure via Events rather than trying forever.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo script:** Run the successful case live; showing the FAILING case is optional if time is
|
||||
short, but the `Completed` vs `Running`/`Error` distinction is worth pausing on regardless.
|
||||
|
||||
**Approximate timing:** 8 minutes live; 5 minutes self-paced.
|
||||
|
||||
**Common misconception to preempt:** students who've only seen Deployments sometimes expect
|
||||
`kubectl get pods` to show the Job's Pod disappear once it's done. It doesn't, by default — it
|
||||
stays as `Completed` until manually cleaned up or a TTL is configured, which is worth mentioning
|
||||
briefly (not a hands-on topic here).
|
||||
|
||||
**Optional extension:** ask what `kubectl get pods` would show for a CronJob's Pods across
|
||||
several scheduled runs (answer: one Completed Pod per past run, accumulating — the same
|
||||
"nothing auto-cleans this up" behavior, multiplied by the schedule).
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Guided Exercise: Choosing the Right Workload Primitive
|
||||
|
||||
Paper-and-thinking exercise before the Labs.
|
||||
|
||||
## The scenario
|
||||
|
||||
Your team needs four things running in the cluster:
|
||||
|
||||
1. A nightly database backup that must run at 2am and finish before morning.
|
||||
2. A log-shipping agent that must run on every node, including nodes added later.
|
||||
3. A one-time script to backfill a new database column, run once during this deploy.
|
||||
4. The main web application, always up, serving live traffic.
|
||||
|
||||
## Your task
|
||||
|
||||
For each of the four, name the correct primitive (Deployment, Job, CronJob, or DaemonSet) and
|
||||
answer: what would go wrong if you used a Deployment for it instead?
|
||||
|
||||
## A worked answer to check yourself against
|
||||
|
||||
1. **CronJob.** A Deployment has no concept of "run on a schedule" — you'd need external
|
||||
tooling to trigger it repeatedly, reinventing what CronJob already does. Using a bare Job
|
||||
would run it once; CronJob is what recreates that Job on schedule.
|
||||
2. **DaemonSet.** A Deployment's replica count is a number, not "one per node" — nothing
|
||||
guarantees even coverage, and a newly added node wouldn't automatically get a copy the way a
|
||||
DaemonSet guarantees.
|
||||
3. **Job.** A Deployment expects its Pod to run forever; a script that's SUPPOSED to exit when
|
||||
done would be treated as a crash and restarted forever, which is wrong for a one-time task.
|
||||
4. **Deployment.** This is the actual always-on, N-replicas case the other three are explicitly
|
||||
NOT designed for.
|
||||
|
||||
## Self-check
|
||||
|
||||
If you answered "Job" for #4 or "Deployment" for #1-3, revisit the lesson's decision table
|
||||
before the Labs — getting this choice right the first time avoids building something that
|
||||
technically runs but behaves wrong in a way that's easy to miss until it causes a real problem
|
||||
(a backup CronJob accidentally deployed as a Job that only ever runs once, for example).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Good as a quick individual exercise before revealing the answer — the "what
|
||||
would go wrong" framing forces reasoning about the CONSEQUENCE of a wrong choice, not just
|
||||
recall of a definition.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live with discussion; 8 minutes self-paced.
|
||||
|
||||
**Optional extension:** ask what would need to be true for #1 (the nightly backup) to actually
|
||||
be SAFE to run as a plain Job triggered manually once, instead of a CronJob — a good check that
|
||||
students understand CronJob's value is specifically the SCHEDULING, not something inherent to
|
||||
backups.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Reflection: The Right Primitive Is a Correctness Decision, Not a Style Choice
|
||||
|
||||
You just built a one-shot Job and a scheduled CronJob for real. Take a few minutes to connect
|
||||
this to production thinking.
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. If your team accidentally deployed the database backup script as a Deployment instead of a
|
||||
CronJob, what's the FIRST symptom you'd likely notice — and would it look like a bug in the
|
||||
backup script itself, or something else?
|
||||
2. A CronJob that fails silently every night for a week is a real, common incident pattern. What
|
||||
would you want monitored to catch this quickly?
|
||||
3. Why does "it ran once and worked" not prove a CronJob is configured correctly?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Workload-primitive mistakes often look like application bugs at first.** A backup script
|
||||
"crashing" every few minutes on a Deployment isn't a bug in the script — the script did exactly
|
||||
what it was told to do (exit when finished) and the Deployment is "correctly" restarting it
|
||||
according to its own contract. Recognizing "wrong primitive," not "buggy code," as the root
|
||||
cause saves real debugging time.
|
||||
|
||||
**A schedule you never verify is a schedule you can't trust.** A CronJob failing every single
|
||||
run since deployment is easy to miss if nobody checks — unlike a Deployment's crash-looping
|
||||
Pods, which are visible in `kubectl get pods` by default, a CronJob's failed history requires
|
||||
someone to actually look at `kubectl get jobs` or the CronJob's own status. Real teams monitor
|
||||
CronJob success/failure explicitly rather than assuming silence means success.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 is worth pushing on — many students'
|
||||
first instinct is to debug the "crashing" script rather than question the workload type it's
|
||||
running under.
|
||||
|
||||
**Approximate timing:** 8 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Optional extension:** ask students to name one alerting rule they'd add for a critical nightly
|
||||
CronJob (e.g. "alert if no successful completion recorded in the last 25 hours").
|
||||
Reference in New Issue
Block a user