feat(root): add storage and scheduling curriculum
Two new PARTIAL modules, moving ROOT 08 off PLANNED for the first time: root-08-m01-storage-fundamentals — ephemeral vs. persistent storage, PV/PVC/StorageClass. Lesson + knowledge check + real Labs (emptydir-pod, pv-pvc-mount, pvc-dynamic-task, storageclass-mcq). root-08-m02-scheduling-resource-governance — how the scheduler places Pods and how quotas/limits/priority/disruption budgets shape that placement. Lesson + knowledge check + real Labs (node-label-selector, node-taint-toleration, pod-affinity-mcq, priority-class-task, pod-disruption-budget-task, resource-quota-ns, limitrange-task). cluster-upgrade-mcq and etcd-backup-mcq deliberately left unwired — ROOT 11/DevOps territory, out of this phase's scope, not overlooked. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
|||||||
|
# Ephemeral vs. Persistent Storage
|
||||||
|
|
||||||
|
## The default: a container's filesystem is thrown away
|
||||||
|
|
||||||
|
By default, anything a container writes to its own filesystem disappears the moment that
|
||||||
|
container is replaced — a crash, a rollout, a reschedule, anything. This is a deliberate design
|
||||||
|
choice (it's part of what makes a Pod safely disposable/replaceable), but it means "just write
|
||||||
|
files to disk" silently loses data unless you use one of Kubernetes' actual storage mechanisms.
|
||||||
|
|
||||||
|
## emptyDir: shared, but still temporary
|
||||||
|
|
||||||
|
An `emptyDir` volume gives a Pod's containers a shared directory that survives a container
|
||||||
|
restart WITHIN the same Pod, but is deleted the moment the Pod itself is removed. It's the right
|
||||||
|
tool for two containers in one Pod that need to share files with each other (a sidecar writing
|
||||||
|
logs another container reads, for example) — not for anything that needs to outlive the Pod.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- name: shared-data
|
||||||
|
emptyDir: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PersistentVolume & PersistentVolumeClaim: storage that outlives the Pod
|
||||||
|
|
||||||
|
A **PersistentVolume (PV)** represents an actual piece of storage (a cloud disk, an NFS share,
|
||||||
|
whatever the cluster's infrastructure provides) as a Kubernetes object. A
|
||||||
|
**PersistentVolumeClaim (PVC)** is a Pod's REQUEST for storage matching some criteria (size,
|
||||||
|
access mode) — the Pod references the PVC, not a PV directly, and Kubernetes binds the PVC to a
|
||||||
|
matching PV.
|
||||||
|
|
||||||
|
```
|
||||||
|
Pod ──references──> PVC ("I need 5Gi, ReadWriteOnce") ──binds to──> PV (actual storage)
|
||||||
|
```
|
||||||
|
|
||||||
|
This indirection matters: a Pod's manifest doesn't need to know or care exactly which physical
|
||||||
|
storage backs its PVC — that's resolved separately, and the same PVC keeps working even if the
|
||||||
|
Pod behind it is replaced.
|
||||||
|
|
||||||
|
## StorageClass & dynamic provisioning
|
||||||
|
|
||||||
|
Manually creating a PV for every PVC doesn't scale. A **StorageClass** describes a category of
|
||||||
|
storage (e.g. "fast SSD" vs. "cheap archival") and, when a PVC requests that class, the cluster
|
||||||
|
can automatically provision a matching PV on demand — this is "dynamic provisioning," and it's
|
||||||
|
the normal way storage works on any real cloud-hosted cluster. A StorageClass also controls the
|
||||||
|
**reclaim policy** — what happens to the underlying storage after its PVC is deleted (`Delete`
|
||||||
|
the data, or `Retain` it for manual recovery).
|
||||||
|
|
||||||
|
## The decision in one pass
|
||||||
|
|
||||||
|
| You need... | Use |
|
||||||
|
|---|---|
|
||||||
|
| Temporary scratch space shared between containers in ONE Pod | `emptyDir` |
|
||||||
|
| Storage that survives Pod replacement, manually provisioned | PV + PVC |
|
||||||
|
| The above, provisioned automatically on demand | PVC + StorageClass (dynamic provisioning) |
|
||||||
|
|
||||||
|
## Instructor Notes
|
||||||
|
|
||||||
|
**Approximate timing:** 15 minutes read; 10 minutes live with the PV/PVC/StorageClass diagram.
|
||||||
|
|
||||||
|
**Likely misconceptions to watch for:**
|
||||||
|
- Students often assume `emptyDir` is "persistent" because the name doesn't obviously say
|
||||||
|
otherwise. Be explicit: it survives a CONTAINER restart, not a POD deletion.
|
||||||
|
- The PV/PVC indirection can seem like unnecessary ceremony at first — frame it as the same
|
||||||
|
"declare what you want, let the system resolve it" pattern from ROOT 05's desired-state
|
||||||
|
lesson, applied to storage instead of Pod count.
|
||||||
|
|
||||||
|
**Discussion question:** "If a Pod using a PVC is deleted and replaced, what happens to its
|
||||||
|
data?" (Answer: it's preserved — the PVC and its bound PV are separate objects from the Pod,
|
||||||
|
and a new Pod referencing the same PVC picks up right where the data was left, which is the
|
||||||
|
entire point of using persistent storage over `emptyDir`.)
|
||||||
|
|
||||||
|
**Optional extension:** ask what a `Retain` reclaim policy is protecting against, compared to
|
||||||
|
`Delete` (answer: accidental data loss from deleting a PVC by mistake — `Retain` keeps the
|
||||||
|
underlying storage around for manual recovery instead of immediately destroying it).
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# Knowledge Check: Storage Fundamentals
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**1. Two containers in the same Pod need to share files with each other, but nothing needs to
|
||||||
|
survive the Pod being replaced. What's the simplest correct choice?**
|
||||||
|
|
||||||
|
*Answer:* `emptyDir` — shared within the Pod, deliberately temporary, exactly matching the
|
||||||
|
requirement. Reaching for a PV/PVC here would be more machinery than the problem needs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**2. A Pod using a PVC is deleted and a new Pod is created referencing the SAME PVC. What
|
||||||
|
happens to the data?**
|
||||||
|
|
||||||
|
*Answer:* It's preserved. The PVC and its bound PV exist independently of any specific Pod — the
|
||||||
|
new Pod picks up exactly where the data was left, which is the entire reason to use persistent
|
||||||
|
storage instead of `emptyDir` for anything that needs to outlive a single Pod's lifetime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**3. True or false: a Pod's manifest must specify exactly which PersistentVolume backs its
|
||||||
|
storage request.**
|
||||||
|
|
||||||
|
*Answer:* False. A Pod references a PVC (a request), not a PV directly — the binding between
|
||||||
|
PVC and an actual PV is resolved separately, which is exactly the indirection that lets the same
|
||||||
|
Pod manifest work regardless of which physical storage ends up behind it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**4. What does a StorageClass's reclaim policy actually control, and why would a team choose
|
||||||
|
`Retain` over `Delete` for a production database's storage?**
|
||||||
|
|
||||||
|
*Answer:* It controls what happens to the underlying storage after its PVC is deleted. `Retain`
|
||||||
|
keeps the data around for manual recovery instead of immediately destroying it — a safety
|
||||||
|
margin against an accidental `kubectl delete pvc` on something genuinely important, at the cost
|
||||||
|
of needing manual cleanup later.
|
||||||
|
|
||||||
|
## Instructor Notes
|
||||||
|
|
||||||
|
**Facilitation:** Question 2 is worth confirming lands correctly — it's the single fact that
|
||||||
|
justifies the entire PV/PVC indirection existing at all.
|
||||||
|
|
||||||
|
**Approximate timing:** 5 minutes.
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
# How the Scheduler Places Pods, and How Governance Shapes It
|
||||||
|
|
||||||
|
## The scheduler's job
|
||||||
|
|
||||||
|
Every time a new Pod needs to run, the scheduler decides which node it lands on — you never
|
||||||
|
manually assign a Pod to a node in normal operation (ROOT 05's mental model). By default, the
|
||||||
|
scheduler picks based on available resources (does this node have enough free CPU/memory), but
|
||||||
|
several mechanisms let you INFLUENCE or CONSTRAIN that decision when the default isn't good
|
||||||
|
enough.
|
||||||
|
|
||||||
|
## Steering placement: nodeSelector, taints/tolerations, affinity
|
||||||
|
|
||||||
|
- **nodeSelector** — the simplest constraint: "only schedule this Pod on nodes with THIS
|
||||||
|
label" (e.g. `disktype: ssd`). A blunt but clear tool.
|
||||||
|
- **Taints and tolerations** — the inverse relationship. A NODE is tainted (marked "don't
|
||||||
|
schedule here unless you specifically tolerate this taint") and a Pod TOLERATES a specific
|
||||||
|
taint to be allowed to land there anyway. Commonly used to reserve nodes for specific
|
||||||
|
workloads (e.g. GPU nodes tainted so only GPU-requiring Pods land there) or to keep general
|
||||||
|
workloads off nodes reserved for something else.
|
||||||
|
- **Affinity/anti-affinity** — more expressive rules: "prefer/require scheduling near Pods with
|
||||||
|
label X" (affinity) or "prefer/require scheduling AWAY from Pods with label X"
|
||||||
|
(anti-affinity). A common real use: anti-affinity between replicas of the same application, so
|
||||||
|
a single node failure can't take down every replica at once.
|
||||||
|
|
||||||
|
## Priority: which Pods matter more when resources are tight
|
||||||
|
|
||||||
|
A **PriorityClass** lets you mark some Pods as more important than others. When the cluster is
|
||||||
|
under resource pressure, a higher-priority pending Pod can even cause a lower-priority RUNNING
|
||||||
|
Pod to be evicted (preempted) to make room — this is a real, consequential mechanism, not just a
|
||||||
|
scheduling hint, and misconfiguring it can mean an unimportant batch job unexpectedly evicts
|
||||||
|
something that actually mattered.
|
||||||
|
|
||||||
|
## PodDisruptionBudget: protecting against VOLUNTARY disruption
|
||||||
|
|
||||||
|
A **PodDisruptionBudget (PDB)** limits how many replicas of an application can be taken down
|
||||||
|
AT ONCE during voluntary disruptions — a node drain for maintenance, a cluster upgrade — by
|
||||||
|
declaring a minimum number (or percentage) that must stay available. This does NOT protect
|
||||||
|
against involuntary disruption (a node crashing unexpectedly) — it specifically constrains
|
||||||
|
operations that Kubernetes itself controls the timing of.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: web-pdb
|
||||||
|
spec:
|
||||||
|
minAvailable: 2
|
||||||
|
selector:
|
||||||
|
matchLabels: { app: web }
|
||||||
|
```
|
||||||
|
|
||||||
|
Without a PDB, a node drain during routine maintenance could legally take down every replica of
|
||||||
|
`web` simultaneously if they all happened to be on nodes being drained together — a PDB is what
|
||||||
|
prevents that from being "technically allowed."
|
||||||
|
|
||||||
|
## ResourceQuota & LimitRange: governance at the Namespace level
|
||||||
|
|
||||||
|
A **ResourceQuota** caps the TOTAL resources (CPU, memory, object counts) a Namespace can
|
||||||
|
consume — useful for multi-team clusters where one team's runaway workload shouldn't be able to
|
||||||
|
starve everyone else. A **LimitRange** sets DEFAULT and/or min/max resource requests/limits for
|
||||||
|
individual containers within a Namespace, so a Pod created without explicit resource settings
|
||||||
|
still gets sensible ones rather than none at all.
|
||||||
|
|
||||||
|
## Instructor Notes
|
||||||
|
|
||||||
|
**Approximate timing:** 20 minutes read; 15 minutes live given the number of concepts covered.
|
||||||
|
|
||||||
|
**Likely misconceptions to watch for:**
|
||||||
|
- Taints/tolerations are frequently confused with affinity — clarify the directionality: a
|
||||||
|
taint REPELS by default (node says no); a toleration is what lets a specific Pod through
|
||||||
|
anyway. Affinity is the Pod expressing a preference, not the node restricting access.
|
||||||
|
- Students sometimes assume a PDB prevents ALL disruption. Be explicit that it only constrains
|
||||||
|
VOLUNTARY disruption (things Kubernetes/an operator controls the timing of) — a hardware
|
||||||
|
failure ignores PDBs entirely, because nobody is "asking permission" for a crash.
|
||||||
|
|
||||||
|
**Discussion question:** "Why would a real team use anti-affinity between replicas of the same
|
||||||
|
application instead of just trusting the scheduler's default spread?" (Answer: the default
|
||||||
|
scheduler does consider spread as one factor among several, but doesn't GUARANTEE it the way an
|
||||||
|
explicit anti-affinity rule does — for something where "don't lose all replicas to one node
|
||||||
|
failure" is a hard requirement, not just a nice-to-have, an explicit rule is safer than an
|
||||||
|
implicit tendency.)
|
||||||
|
|
||||||
|
**Optional extension:** ask what would happen to a Namespace's Pods if a ResourceQuota were
|
||||||
|
added AFTER Pods without explicit resource requests already existed there (a good bridge to why
|
||||||
|
LimitRange defaults matter — a Quota alone doesn't retroactively fix Pods created without
|
||||||
|
resource requests, it just constrains what NEW Pods can consume going forward).
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
# Knowledge Check: Scheduling & Resource Governance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**1. A node is tainted `dedicated=gpu:NoSchedule`. A Pod with no matching toleration is
|
||||||
|
pending and never gets scheduled anywhere else, even though other untainted nodes have free
|
||||||
|
capacity. What's actually going on?**
|
||||||
|
|
||||||
|
*Answer:* Nothing is wrong — the taint only affects the tainted node specifically; it doesn't
|
||||||
|
explain why the Pod isn't landing elsewhere. If it's stuck pending entirely, the cause is
|
||||||
|
something else (insufficient resources cluster-wide, a nodeSelector/affinity rule that can't be
|
||||||
|
satisfied anywhere) — the taint itself only ever repels from the ONE node it's applied to.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**2. During a planned node drain for maintenance, would you expect a PodDisruptionBudget to
|
||||||
|
prevent an UNRELATED node from crashing unexpectedly at the same time?**
|
||||||
|
|
||||||
|
*Answer:* No. A PDB only constrains VOLUNTARY disruptions — operations Kubernetes/an operator
|
||||||
|
controls the timing of, like a drain. An unrelated hardware failure is involuntary and ignores
|
||||||
|
PDBs entirely; nothing is "asking permission" for a crash.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**3. True or false: a higher-priority pending Pod can cause a lower-priority, already-RUNNING
|
||||||
|
Pod to be evicted to make room.**
|
||||||
|
|
||||||
|
*Answer:* True — this is what Pod priority/preemption actually does under resource pressure,
|
||||||
|
not just a scheduling hint. Misconfigured priorities can have real, disruptive consequences for
|
||||||
|
this reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**4. A team notices new Pods in their Namespace keep getting created with no CPU/memory
|
||||||
|
requests at all, even though nobody set that intentionally. What governance mechanism would fix
|
||||||
|
this going forward, and what would it NOT fix?**
|
||||||
|
|
||||||
|
*Answer:* A LimitRange with default requests/limits would apply sensible defaults to future
|
||||||
|
Pods created without explicit values. It would NOT retroactively change Pods that already exist
|
||||||
|
— it only affects new Pod creation from the point it's added.
|
||||||
|
|
||||||
|
## Instructor Notes
|
||||||
|
|
||||||
|
**Facilitation:** Question 2 reinforces a genuinely important, often-missed distinction
|
||||||
|
(voluntary vs. involuntary disruption) — worth confirming it lands, not just that the answer was
|
||||||
|
guessed correctly.
|
||||||
|
|
||||||
|
**Approximate timing:** 5-7 minutes given the density of this module's lesson.
|
||||||
Reference in New Issue
Block a user