From dbcc21088915e066e4515efb4f7556da184bd06f Mon Sep 17 00:00:00 2001 From: ced4568 Date: Wed, 26 Aug 2026 09:06:19 -0500 Subject: [PATCH] feat(root): author networking and configuration curriculum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit root-07-m03-services-and-service-discovery — a new, full gold-standard- pattern module: the Service/Endpoints/DNS request path, choosing the right Service type. Lesson, demonstration (a Service with zero Endpoints — the single fastest diagnostic check for this failure class), knowledge check, guided exercise (ClusterIP vs. NodePort vs. LoadBalancer for three real components), a real Lab (expose-service-basics), and a reflection. root-07-m04-ingress-networkpolicy-foundations — a new PARTIAL module: Ingress and NetworkPolicy at the architectural level (the security- control framing of NetworkPolicy is deliberately deferred to ROOT 09). Lesson + knowledge check + real Labs (ingress-task, network-policy). Co-Authored-By: Claude Sonnet 5 --- .../01-lesson-services.md | 87 ++++++++++++++++++ .../02-demonstration-zero-endpoints.md | 82 +++++++++++++++++ .../03-knowledge-check.md | 47 ++++++++++ .../04-guided-exercise-service-type.md | 49 ++++++++++ .../05-lab-intro.md | 40 +++++++++ .../06-reflection.md | 40 +++++++++ .../01-lesson-ingress-networkpolicy.md | 90 +++++++++++++++++++ .../02-knowledge-check.md | 46 ++++++++++ 8 files changed, 481 insertions(+) create mode 100644 content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/01-lesson-services.md create mode 100644 content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/02-demonstration-zero-endpoints.md create mode 100644 content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/03-knowledge-check.md create mode 100644 content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/04-guided-exercise-service-type.md create mode 100644 content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/05-lab-intro.md create mode 100644 content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/06-reflection.md create mode 100644 content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/01-lesson-ingress-networkpolicy.md create mode 100644 content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/02-knowledge-check.md diff --git a/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/01-lesson-services.md b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/01-lesson-services.md new file mode 100644 index 0000000..83d87ea --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/01-lesson-services.md @@ -0,0 +1,87 @@ +# The Service/Endpoints/DNS Request Path + +## The problem: Pod IPs are not stable + +Every Pod gets its own IP address, but that IP is NOT durable — when a Pod is replaced (a +rollout, a crash recovery, a reschedule), the new Pod gets a brand new IP. If another part of +your application had that old IP hardcoded, it would silently start talking to nothing. This is +the exact problem in `expose-service-basics`'s scenario: "other pods cannot reliably connect +because pod IPs change when they restart." + +## A Service: a stable address in front of a moving set of Pods + +A Service is a stable virtual IP and DNS name that automatically routes to whichever Pods +currently match its label selector — regardless of how many there are or how often they change. +You never talk to a Pod's IP directly in normal operation; you talk to the Service, and the +Service's Endpoints (a live, continuously-updated list of the matching Pods' IPs) determines +where traffic actually goes. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: redis-svc +spec: + selector: + app: redis # <- matches Pods by label, same mechanism from ROOT 05 + ports: + - port: 6379 +``` + +This is the same label-selector mechanism you learned in ROOT 05 — a Service doesn't know or +care about specific Pod names, only which Pods currently carry a matching label. + +## The three Service types you'll use most + +- **ClusterIP** (the default) — reachable only from inside the cluster. Use this for + internal-only communication, like one microservice talking to another or to a database. +- **NodePort** — additionally exposes the Service on a static port on every node's own IP, + reachable from outside the cluster. Mostly used for development/testing or when there's no + cloud load balancer available. +- **LoadBalancer** — asks the cloud provider to provision a real external load balancer pointed + at the Service. This is the standard way to expose something to the public internet on a + cloud-hosted cluster. + +Choosing wrong isn't just a style preference — a ClusterIP Service is architecturally +unreachable from outside the cluster no matter how correctly everything else is configured. + +## DNS: how Pods actually find a Service by name + +Every Service automatically gets a DNS name inside the cluster +(`..svc.cluster.local`, or usually just `` from within +the same Namespace) — this is why application code can use a plain hostname like `redis-svc` +instead of an IP address at all. Kubernetes' internal DNS resolves that name to the Service's +stable virtual IP, which then routes to whichever Pods are currently healthy. + +## The full request path, end to end + +``` +Client -> DNS lookup "redis-svc" -> Service's stable virtual IP + -> Service consults its Endpoints (live list of matching, ready Pods) + -> traffic forwarded to one of those Pod IPs +``` + +Every one of these steps can fail independently, which is exactly why "the app can't reach +redis" requires checking each layer rather than guessing — the next activity shows you what +that looks like when the Endpoints layer is empty. + +## Instructor Notes + +**Approximate timing:** 15 minutes read, 10-12 minutes live with the request-path diagram. + +**Demo plan:** Before the next activity, it's worth live-running `kubectl get endpoints +` against a healthy Service so students see a populated Endpoints list before +they see an empty one in the demonstration. + +**Likely misconceptions to watch for:** +- Students often think a Service "is" a load balancer process running somewhere. It's closer to + a routing rule maintained by `kube-proxy` on every node — worth clarifying that there's no + single "Service Pod" to go find and debug. +- ClusterIP vs. NodePort vs. LoadBalancer is sometimes memorized without the "why" — the + knowledge check's Service-type question is designed to test the underlying reasoning, not + just recall. + +**Discussion question:** "If a Service's selector matches zero Pods, does the Service itself +fail to exist?" (Answer: no — the Service object exists fine; its Endpoints list is just empty, +which looks like connection failure from the client's side without any error at the Service +level itself. This is exactly the next activity's topic.) diff --git a/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/02-demonstration-zero-endpoints.md b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/02-demonstration-zero-endpoints.md new file mode 100644 index 0000000..5319547 --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/02-demonstration-zero-endpoints.md @@ -0,0 +1,82 @@ +# Demonstration: A Service With Zero Endpoints + +This is one of the most common "it just doesn't work" incidents in real Kubernetes operation — +and it produces no error message at all at the Service level. + +## The setup + +A Service `api-svc` selects Pods labeled `app: api`. A Deployment was recently updated and its +Pods now carry the label `app: api-v2` instead (a rename during a refactor, deliberately or +accidentally). + +## What you'd see from the client side + +``` +$ curl http://api-svc:8080/health +curl: (7) Failed to connect to api-svc port 8080: Connection refused +``` + +Just a generic connection failure — nothing here says "your Service's selector doesn't match +any Pods." + +## What the Service object itself shows + +``` +$ kubectl get service api-svc +NAME TYPE CLUSTER-IP PORT(S) AGE +api-svc ClusterIP 10.96.34.201 8080/TCP 2h +``` + +Completely normal-looking. The Service exists, has a stable IP, is not in any kind of "error" +state — because from the Service's own perspective, nothing is wrong. It's doing exactly what +it's configured to do: route to whatever matches its selector, which currently happens to be +nothing. + +## Where the real evidence is: Endpoints + +``` +$ kubectl get endpoints api-svc +NAME ENDPOINTS AGE +api-svc 2h +``` + +This is the actual signal. `` means the Service's selector currently matches zero Pods — +this is the single fastest check for "is this a Service-to-Pod mismatch" any time something +reachable-in-principle looks like a connection failure. + +## Confirming the mismatch + +``` +$ kubectl get pods --show-labels +NAME READY STATUS LABELS +api-abc123 1/1 Running app=api-v2 +``` + +The Pod is healthy and running — `app=api-v2`, not `app=api`. The Service's selector +(`app: api`) matches nothing, and nothing about the Pod itself is broken. + +## The general diagnostic shape + +`curl`/application-level connection failure tells you *that* something's wrong, but nothing +about *where*. `kubectl get endpoints ` is the fastest way to distinguish "the Service +has no matching Pods" from "the Pods exist and are ready but something else is wrong" — +reach for it early whenever a Service-fronted connection fails for no obvious reason. + +## Instructor Notes + +**Demo script:** Live-create this exact mismatch (a Service with one selector, a Deployment +with a slightly different label) and walk through `kubectl get service` -> `kubectl get +endpoints` -> `kubectl get pods --show-labels` in front of the class, narrating what each +command is and isn't telling you. + +**Approximate timing:** 8-10 minutes live; 5-7 minutes self-paced. + +**Common misconception to preempt:** students often check the Service object first and, seeing +nothing obviously wrong, assume the problem must be elsewhere entirely (DNS, firewall, the +application itself) — reinforce that `kubectl get endpoints` is the specific, fast check for +this exact failure mode, before reaching for anything more complex. + +**Optional extension:** ask what `kubectl describe service api-svc` would additionally show +compared to `kubectl get service` (answer: the same selector the Service is using, which lets +you directly compare it against `kubectl get pods --show-labels`'s actual Pod labels side by +side, rather than inferring the mismatch). diff --git a/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/03-knowledge-check.md b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/03-knowledge-check.md new file mode 100644 index 0000000..9fb4819 --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/03-knowledge-check.md @@ -0,0 +1,47 @@ +# Knowledge Check: Services & Service Discovery + +--- + +**1. A Deployment has three replicas but `kubectl get endpoints` shows `` for its Service. +Which part of the system should you inspect first, and why?** + +*Answer:* Compare the Service's selector (`kubectl describe service`) against the actual Pods' +labels (`kubectl get pods --show-labels`) — a Service with a healthy backend Deployment but zero +Endpoints almost always means the selector doesn't match the Pods' current labels, not that the +Pods themselves are unhealthy. + +--- + +**2. You need a microservice reachable ONLY from other services inside the same cluster, never +from the public internet. Which Service type, and why not LoadBalancer?** + +*Answer:* ClusterIP — it's reachable only from inside the cluster by design, which is exactly +the requirement. A LoadBalancer would provision a real external endpoint reachable from the +public internet, which is more exposure than the requirement calls for, and depends on cloud +provider support this scenario doesn't need. + +--- + +**3. True or false: application code that connects to `redis-svc` by that plain hostname is +relying on Kubernetes' internal DNS to resolve it.** + +*Answer:* True. Every Service automatically gets a DNS name; that's specifically what lets code +use a stable hostname instead of hardcoding a Pod IP that could change at any time. + +--- + +**4. A Service's Endpoints list is non-empty, but the application still can't connect. Does +this rule out a Service-level problem entirely?** + +*Answer:* Not entirely — a non-empty Endpoints list means at least one Pod currently matches the +selector, but the connection could still fail for other reasons (wrong port number on the +Service, a NetworkPolicy blocking the traffic, the application inside the Pod not actually +listening). A populated Endpoints list narrows the problem, it doesn't prove the whole path +works. + +## Instructor Notes + +**Facilitation:** Question 1 is the single most transferable diagnostic habit from this module — +worth explicitly calling out as "the first thing to check" for this entire class of problem. + +**Approximate timing:** 5 minutes. diff --git a/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/04-guided-exercise-service-type.md b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/04-guided-exercise-service-type.md new file mode 100644 index 0000000..4bfcadc --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/04-guided-exercise-service-type.md @@ -0,0 +1,49 @@ +# Guided Exercise: Choosing the Right Service Type + +Paper-and-thinking exercise before the Lab. + +## The scenario + +You're deploying three components for a class project on a cloud-hosted cluster: + +1. A Postgres database that only your own backend application should ever talk to. +2. A backend API that the frontend (also running in the cluster) calls internally. +3. The frontend web application itself, which needs to be reachable by users on the public + internet. + +## Your task + +For each of the three components, decide: ClusterIP, NodePort, or LoadBalancer — and justify +each choice in one sentence referencing WHO needs to reach it. + +## A worked answer to check yourself against + +1. **Postgres — ClusterIP.** Only your own backend (running inside the same cluster) needs to + reach it; there is no reason for it to be reachable from outside the cluster at all, and + exposing a database externally is a real, avoidable security risk. +2. **Backend API — ClusterIP.** The frontend calls it from inside the cluster too — same + reasoning as the database. External users never talk to the backend directly; they only ever + go through the frontend. +3. **Frontend — LoadBalancer** (on a real cloud-hosted cluster). Public internet users need to + reach it, and a cloud provider's LoadBalancer is the standard mechanism for that. NodePort + would technically work but is typically used for development/testing rather than a real + public-facing service on a cloud cluster. + +## Self-check + +If you chose LoadBalancer for the database or the backend API, reconsider — "internet-reachable +by default" is not the safe default for anything that doesn't need to be, and getting this +choice right the first time avoids a real security exposure later, not just a style issue. + +## Instructor Notes + +**Facilitation:** Push on the "why not just LoadBalancer everything, it works" instinct some +students will have — this is a good early moment to connect Service-type choice to the security +mindset that ROOT 09 builds on more fully (exposure should be intentional, not the path of +least resistance). + +**Approximate timing:** 10 minutes live with discussion; 10 minutes self-paced. + +**Optional extension:** ask what would need to be added (not covered in this module) to let +public users reach the frontend at a real domain name over HTTPS rather than a bare +LoadBalancer IP — a natural forward pointer to Ingress, covered in the next module. diff --git a/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/05-lab-intro.md b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/05-lab-intro.md new file mode 100644 index 0000000..34e5b0e --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/05-lab-intro.md @@ -0,0 +1,40 @@ +# Launch Lab: Exposing an Application + +Time to create a real Service in your disposable Lab and connect it to a real Deployment. + +## What you're doing + +A `redis` Deployment is running, but other Pods can't reliably reach it because Pod IPs change +whenever a Pod restarts — exactly the problem the lesson opened with. Your task is to create a +stable ClusterIP Service in front of it. + +## Before you start + +Recall the request path from the lesson: your Service's `selector` has to match the Deployment +Pods' actual labels, or you'll end up with the empty-Endpoints situation from the +demonstration. Check the Deployment's Pod labels before writing your Service's selector, don't +guess at them. + +## Your task in this Lab + +Create a ClusterIP Service exposing the `redis` Deployment on the correct port. Once it's +created, verify it the same way the demonstration did: `kubectl get service`, then `kubectl get +endpoints` to confirm it actually has a non-empty Endpoints list, not just that the Service +object exists. + +This Lab has its own in-context hints if you get stuck. + +When you're done, return to this module for a reflection on why service discovery is +infrastructure, not magic. + +## Instructor Notes + +**Classroom flow:** Encourage students to run `kubectl get endpoints` as their OWN verification +step even though the Lab's automated validation will check this too — building the habit of +checking Endpoints, not just "the Service exists," is the actual transferable skill here. + +**Approximate timing:** 10-15 minutes. + +**Common mistake to watch for:** a selector that matches on the wrong label key/value, or a +port/targetPort mismatch (the Service's `port` is what clients connect to; `targetPort` is what +the container actually listens on — conflating the two is a very common first-time error). diff --git a/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/06-reflection.md b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/06-reflection.md new file mode 100644 index 0000000..fef9c2c --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/06-reflection.md @@ -0,0 +1,40 @@ +# Reflection: Service Discovery Is Infrastructure, Not Magic + +You just created a Service that gives a moving set of Pods a stable address. Take a few minutes +to connect this to real operational thinking. + +## Reflection questions + +1. Before this module, "the app can just connect to redis by name" might have seemed obvious or + automatic. What specific mechanism actually makes that true, and what would break it? +2. If your Service's selector had been wrong (matching zero Pods), what would the SYMPTOM have + looked like from the application's side, versus what the actual CAUSE would have been? +3. Why is checking `kubectl get endpoints` before assuming a networking/DNS/firewall problem a + good default habit, given how common a selector mismatch actually is? + +## Real Ops takeaway + +**"It's probably a networking issue" is often wrong, and Endpoints is how you find out fast.** A +huge fraction of real "service X can't reach service Y" incidents in Kubernetes are Service +selector mismatches, not actual network problems — checking `kubectl get endpoints` first, before +escalating to firewall rules or DNS debugging, saves real time precisely because this failure +mode produces zero errors at the layers people instinctively check first. + +**Stable addressing is what makes rolling updates and self-healing actually usable.** Everything +you learned in ROOT 06 about Pods being replaced during a rollout or after a crash only works +smoothly in practice because Services give callers a stable target that survives those +replacements — without this layer, every Pod replacement would require updating every caller's +configuration by hand. + +This module is complete. Continue to the next module. + +## Instructor Notes + +**Discussion questions for a live classroom:** use question 2 as a discussion prompt — the +symptom/cause gap is exactly why this class of bug is often under-diagnosed in real teams. + +**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing. + +**Optional extension:** ask students to write the one-line runbook entry they'd want a future +on-call engineer to have for "Service X seems unreachable" — a good exercise in turning a +lesson into something operationally reusable. diff --git a/content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/01-lesson-ingress-networkpolicy.md b/content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/01-lesson-ingress-networkpolicy.md new file mode 100644 index 0000000..24cd372 --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/01-lesson-ingress-networkpolicy.md @@ -0,0 +1,90 @@ +# Ingress and NetworkPolicy: What They Architecturally Do + +## Ingress: routing external HTTP(S) traffic by host/path + +You already know a LoadBalancer Service can expose something to the public internet. But a real +application usually needs more than "one raw IP to one Service" — you might want +`api.example.com` and `app.example.com` to route to different backend Services, all through one +entry point, with TLS termination handled in one place instead of per-Service. + +**Ingress** is the Kubernetes object for exactly this: a set of HTTP routing rules (by hostname +and/or URL path) that an Ingress controller reads and turns into actual load-balancer/proxy +configuration. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: app-ingress +spec: + rules: + - host: app.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend-svc + port: { number: 80 } +``` + +An important distinction: Ingress is a set of ROUTING RULES, not a load balancer itself — an +Ingress controller (a separate piece of software running in the cluster) is what actually reads +these rules and does the routing. Without a controller installed, an Ingress object exists in +the API but nothing acts on it. + +## NetworkPolicy: traffic reachability as something you declare, not assume + +By default, every Pod in a Kubernetes cluster can reach every other Pod — there is no built-in +network isolation between them at all. This surprises people coming from a "obviously the +network is locked down unless configured otherwise" assumption. It is exactly backwards from +that assumption: **everything is reachable until you say otherwise.** + +A NetworkPolicy changes this for the Pods it selects, by declaring which traffic IS allowed — +everything not explicitly allowed to a Pod covered by at least one NetworkPolicy is then denied. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-frontend-to-backend +spec: + podSelector: + matchLabels: { app: backend } + ingress: + - from: + - podSelector: + matchLabels: { app: frontend } +``` + +This example allows traffic to `backend` Pods only from `frontend` Pods — once this policy +exists and selects `backend`, every OTHER Pod trying to reach `backend` is now blocked, even +though nothing was true about that before the policy existed. + +## Why this matters architecturally, before it matters as a security control + +Right now, think of NetworkPolicy purely as "declaring intended reachability, not assuming it." +ROOT 09 comes back to NetworkPolicy specifically as a SECURITY control (default-deny postures, +least-privilege network access) — this module is deliberately just the architectural mental +model: Kubernetes doesn't isolate network traffic for you by default, and NetworkPolicy is the +mechanism for declaring the reachability you actually intend. + +## Instructor Notes + +**Approximate timing:** 15 minutes read; 10 minutes live. + +**Likely misconceptions to watch for:** +- Students often assume Ingress by itself "does" the routing. Emphasize the controller + requirement — an Ingress object with no controller installed does nothing. +- The "everything reachable by default" fact genuinely surprises most students the first time + they hear it — it's worth stating plainly and letting it land rather than rushing past it. + +**Discussion question:** "If NO NetworkPolicy exists anywhere in a Namespace, what's the actual +reachability between Pods in that Namespace?" (Answer: completely open — every Pod can reach +every other Pod, matching the "everything reachable by default" rule.) + +**Optional extension:** ask what would happen if you created a NetworkPolicy that selects +`frontend` Pods but declares no `ingress` rules at all (answer: `frontend` Pods would become +completely unreachable by anything — an empty ingress list inside a NetworkPolicy that HAS an +ingress policyType means "deny all incoming," not "no policy applies"). diff --git a/content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/02-knowledge-check.md b/content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/02-knowledge-check.md new file mode 100644 index 0000000..a55d9cc --- /dev/null +++ b/content/root/root-07-kubernetes-networking-config/m04-ingress-networkpolicy-foundations/02-knowledge-check.md @@ -0,0 +1,46 @@ +# Knowledge Check: Ingress & NetworkPolicy Foundations + +--- + +**1. You create an Ingress object with correct routing rules, but no traffic is ever routed +according to them. What's the most likely reason?** + +*Answer:* No Ingress controller is installed/running in the cluster. Ingress objects are just +routing rules; a separate controller has to actually read and act on them, or nothing happens. + +--- + +**2. True or false: by default, a Pod in Namespace A cannot reach a Pod in Namespace B unless a +NetworkPolicy explicitly allows it.** + +*Answer:* False — the opposite is true by default. With NO NetworkPolicy in place at all, +every Pod can reach every other Pod regardless of Namespace. NetworkPolicy is what RESTRICTS +this default-open behavior, not what's required to enable connectivity in the first place. + +--- + +**3. A NetworkPolicy selects Pods labeled `app: backend` and declares one ingress rule allowing +traffic from Pods labeled `app: frontend`. What happens to traffic from a Pod labeled +`app: admin-tool` trying to reach `backend`?** + +*Answer:* It's blocked. Once a NetworkPolicy selects a Pod, only traffic matching one of its +explicit rules is allowed — everything else to that Pod is denied, including traffic that would +have worked fine before the policy existed. + +--- + +**4. Why is Ingress the right tool for exposing an HTTP application by hostname, rather than +creating a separate LoadBalancer Service per hostname?** + +*Answer:* A LoadBalancer Service typically provisions its own external IP/load balancer per +Service — one per hostname would mean paying for and managing N separate external endpoints. +Ingress lets one entry point route to many backend Services by hostname/path, which is both +cheaper and centralizes concerns like TLS termination. + +## Instructor Notes + +**Facilitation:** Question 2 catches a very common and consequential misconception — worth +confirming out loud that everyone actually updated their mental model, not just guessed +correctly. + +**Approximate timing:** 5 minutes.