feat(root): author networking and configuration curriculum
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 <[email protected]>
This commit is contained in:
+87
@@ -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
|
||||
(`<service-name>.<namespace>.svc.cluster.local`, or usually just `<service-name>` 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
|
||||
<service-name>` 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.)
|
||||
+82
@@ -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 <none> 2h
|
||||
```
|
||||
|
||||
This is the actual signal. `<none>` 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 <service>` 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).
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Knowledge Check: Services & Service Discovery
|
||||
|
||||
---
|
||||
|
||||
**1. A Deployment has three replicas but `kubectl get endpoints` shows `<none>` 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.
|
||||
+49
@@ -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.
|
||||
+40
@@ -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).
|
||||
+40
@@ -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.
|
||||
+90
@@ -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").
|
||||
+46
@@ -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.
|
||||
Reference in New Issue
Block a user