You already built the /health/live and /health/ready endpoints. Now meet the thing actually calling them — and the two very different things it does when one fails.
Lesson 268 taught you how to build health check endpoints in ASP.NET Core — AddHealthChecks(), MapHealthChecks(), custom IHealthCheck implementations, and the crucial distinction between a liveness endpoint ("is this process itself alive") and a readiness endpoint ("can this instance currently serve traffic well"). That's the application's half of the story. It exposes two HTTP endpoints and answers honestly when asked.
But nothing asks yet. An endpoint that nobody calls is just dead code sitting in your routing table. In a real containerized deployment, something external has to actually poll those endpoints on a schedule, interpret the response, and act on it. That something is the container orchestrator — and by a wide margin, the orchestrator you'll encounter in real production .NET deployments is Kubernetes.
In this lesson, you'll learn how Kubernetes configures and calls the health endpoints you already know how to build, exactly what it does — precisely and distinctly — when a liveness check fails versus when a readiness check fails, and a third kind of probe, the startup probe, that protects slow-starting applications from being killed before they ever got a fair chance.
A health probe, from Kubernetes's point of view, is a small, repeated question it asks a running container: "are you okay?" It asks by making an HTTP request to a path and port you tell it about — the same /health/live and /health/ready endpoints you already built in lesson 268 — on a regular schedule, forever, for as long as the container is running. The container's health check middleware answers with an HTTP status code. Kubernetes reads that answer and, depending on which kind of probe it was, does something very specific about it.
In Kubernetes, a probe is a diagnostic performed periodically against a container, configured on the Pod spec (typically inherited from a Deployment). Kubernetes supports three probe types that matter for a typical ASP.NET Core service: livenessProbe, readinessProbe, and startupProbe — each one an independent, separately configured HTTP check, each one producing a different consequence when it fails. All three can point at the exact same health check endpoints your app already exposes; what differs is not the endpoint, but what Kubernetes does with the answer.
/health/live/health/readyThis should feel familiar — it's exactly the liveness/readiness distinction from lesson 268, seen now from the other side of the wire. You already know why those two questions are different questions. This lesson is about what actually asks them, and what actually happens to the pod once an answer comes back.
Kubernetes runs your container as an opaque process. By default, all it can observe from outside is: "is the process still running, or has it exited?" That single signal is nowhere near enough. A .NET process can be technically running while its request-handling thread pool is completely deadlocked, or while a bad deployment left it stuck in an infinite startup loop, or while it's simply unable to reach the database it depends on. In every one of those cases, "the process hasn't exited" is true — and completely useless as a health signal.
Worse, those situations don't call for the same fix. A deadlocked process needs to be killed and restarted — nothing else will unstick it. An instance that temporarily can't reach the database doesn't need to be restarted at all; restarting a healthy .NET process does nothing to fix a database outage, and just adds unnecessary startup churn on top of an already-degraded dependency. Treating both problems identically — restart everything, always — is either too aggressive or not aggressive enough, depending on which one you're actually facing.
This is precisely why lesson 268 had you build two separate endpoints instead of one. Kubernetes's probe system is the consumer that makes that separation actually pay off: it asks the liveness question and the readiness question independently, on independent schedules, and wires each one to the specific remedy that actually fits the problem it's meant to catch — restart for "the process is stuck," rotation-removal for "the process is fine but temporarily can't serve well." The application tells the truth about two different things; the platform reacts to each truth correctly.
Your application code never needed to know Kubernetes exists. It just answers two honest HTTP questions. The orchestrator supplies the judgment about what each answer means.
A probe is configured with a small, consistent set of fields, whichever of the three kinds it is. Here's a realistic liveness and readiness pair for a containerized ASP.NET Core API, in a Kubernetes Deployment manifest:
containers:
- name: orders-api
image: registry.example.com/orders-api:1.4.2
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3| Field | Meaning |
|---|---|
httpGet.path / port | Which endpoint to call and on which port — points at the exact routes you mapped with MapHealthChecks() in lesson 268. |
initialDelaySeconds | How long the kubelet waits after the container starts before it sends the first probe at all. |
periodSeconds | How often the probe repeats, once it's started — every 10 seconds, every 5 seconds, and so on. |
failureThreshold | How many consecutive failed checks are required before Kubernetes considers the probe itself to have failed — a single blip doesn't count. |
failureThreshold matters more than it might look at first glance: a probe isn't "failed" the instant one HTTP call times out or returns a non-2xx status. A container under a momentary CPU spike might miss one check and answer the next one fine. Requiring several consecutive failures before Kubernetes reacts absorbs that normal noise, so a genuinely transient blip doesn't trigger a restart or a rotation-removal on its own.
initialDelaySeconds before sending the first probe of each kind — giving the app a moment to actually start listening.periodSeconds, the node's kubelet sends an HTTP GET to the configured path and port, for each probe independently.Picture two failures hitting the same order-processing API, at the same time, on two different pods:
Pod A: a background task deadlocked while holding a lock.
No incoming HTTP request will ever complete again.
/health/live now times out.
→ livenessProbe fails 3 times in a row (failureThreshold: 3)
→ Kubernetes RESTARTS the container
→ fresh process, lock released, service resumes
Pod B: the database connection pool is exhausted for a moment.
The process is fine; /health/live still returns 200 instantly.
/health/ready returns 503 because DatabaseHealthCheck (lesson 268) fails.
→ readinessProbe fails 3 times in a row
→ Kubernetes marks Pod B "not ready"
→ Service stops sending it new traffic — container is NOT restarted
→ connection pool recovers a few seconds later
→ /health/ready starts returning 200 again
→ Kubernetes marks Pod B "ready" — traffic resumes automaticallyMeaning: two pods, two different failures, two entirely different — and entirely correct — orchestrator responses, driven by nothing more than which endpoint the failing check happened to be reported on. Restarting Pod B would have been useless (it wouldn't fix the database), and quietly leaving Pod A in rotation would have meant sending real user requests into a process that can never respond to them.
Suppose a notification service loads a large template cache and validates its message-broker connection on startup — a process that can reliably take anywhere from 3 to 40 seconds, depending on network conditions and cache size. With only a liveness probe configured and a modest initialDelaySeconds, this is a real, common failure mode: the liveness probe starts checking before startup has finished, sees a non-responding process (correctly — it's still warming up, not broken), and Kubernetes restarts a perfectly healthy container that just needed more time. If startup is slow enough, this can loop indefinitely — the container never survives long enough to finish starting.
This is exactly the problem the third probe type — the startup probe — exists to solve.
startupProbe:
httpGet:
path: /health/live
port: 8080
periodSeconds: 5
failureThreshold: 30 # up to 150 seconds of startup grace
livenessProbe:
httpGet:
path: /health/live
port: 8080
periodSeconds: 10
failureThreshold: 3When a startupProbe is configured, Kubernetes runs only the startup probe first. The liveness probe (and the readiness probe) don't begin checking at all until the startup probe has succeeded once. Only after that first success does Kubernetes switch over to the regular liveness schedule. In effect, the startup probe buys the container a generous, separately-configured grace period — up to failureThreshold × periodSeconds, 150 seconds in the example above — before the impatient, tightly-scheduled liveness probe is even allowed to start judging it.
Notice the startup probe can point at the very same /health/live endpoint as the liveness probe — the only thing that differs is the schedule around it. This is a genuinely useful, distinct concept for any application with slow or variable startup time: warming a cache, running database migrations on boot, establishing broker connections, JIT-heavy first-request warmup. Without it, you're forced into an uncomfortable trade-off on the liveness probe alone — a generous initialDelaySeconds that's wasted time on every normal, fast restart, or a tight one that risks killing slow-but-healthy startups. The startup probe lets you have both: fast liveness reaction time in steady state, and generous patience during the one phase that actually needs it.
Picture a restaurant with several kitchen stations, each staffed by one cook, and a manager doing regular walk-throughs.
Liveness is the manager checking "is this cook still conscious and at their station?" If a cook has collapsed, there's exactly one sensible response: send in a replacement cook — restart. No amount of waiting fixes an unconscious cook.
Readiness is a different question: "is this cook currently able to plate a dish well right now?" A cook who just burned their hand and needs two minutes at the sink is fully conscious — not a liveness problem at all — but shouldn't have new orders routed to them for those two minutes. The manager simply stops sending them tickets until they signal they're ready again. No one drags them out of the kitchen.
Startup is the manager giving a brand-new cook, on their very first shift, extra time to find where everything is before starting the "are they at their station" checks at all — rather than judging them by the same strict schedule as everyone who's already been working for hours.
The technical mapping holds precisely: liveness failure → replace the worker (restart the container). Readiness failure → stop routing new work to them, but leave them in place (remove from Service rotation, no restart). Startup grace → don't even start judging until they've had a fair chance to get going.
failureThreshold consecutive liveness failures, the kubelet on that node kills the container process directly and starts a fresh one in its place, according to the Pod's restart policy. This happens node-locally — it doesn't require rescheduling the pod elsewhere.failureThreshold consecutive readiness failures, the kubelet marks the pod's Ready condition false. Kubernetes's control plane then removes that pod's IP from the Service's set of active endpoints — the load-balancing target list a Service maintains for routing traffic. The container process itself is never touched.Ready and re-added to the Service's endpoint list — traffic resumes automatically, with no restart having happened at any point in the cycle.This is the single most important distinction in this lesson, and it's worth stating with no hedging: a readiness probe failure never restarts the container, no matter how long it stays failing. It can leave a pod out of rotation indefinitely. Only a liveness probe failure triggers a restart. If your mental model has readiness "eventually escalating" into a restart, that model is wrong — the two mechanisms are completely independent, and neither one turns into the other.
initialDelaySeconds alone just delays when checking begins — once it begins, the container has to succeed on the very next check or start racking up failures against the regular, tight failureThreshold. A startup probe gives repeated, patient checking during the whole startup window, and only hands off to the stricter liveness schedule once startup has actually succeeded. It's not a fixed delay; it's a genuinely different, more forgiving probing phase.
The health check (lesson 268) is application code that answers a question truthfully. The probe (this lesson) is orchestrator configuration that asks the question on a schedule and reacts to the answer. Neither one is useful alone — an unasked health check endpoint does nothing, and a probe pointed at an endpoint that always blindly returns 200 gives Kubernetes no real signal to act on.
Configuring livenessProbe to hit /health/ready instead of /health/live. Since the readiness endpoint includes dependency checks (lesson 268), a temporary database blip now fails the liveness probe too — and Kubernetes restarts a perfectly healthy process to "fix" a database outage that a restart can't touch.
Keep the two probes pointed at their matching endpoints — liveness at the narrow, dependency-free check; readiness at the one that includes real dependencies.
A liveness probe with a short initialDelaySeconds and small failureThreshold, on an app whose startup time genuinely varies. Under load, or during a slightly slower cold start, the app can get killed before it ever finishes starting — sometimes repeatedly, in a crash-restart loop that never resolves.
Add a startupProbe sized generously for your actual worst-case startup time, and keep the liveness probe's own schedule tight and responsive for steady-state operation.
Reacting to a single failed check. Under any real load, a momentary GC pause or a brief CPU spike can make one HTTP call slow enough to time out, without anything actually being wrong. A threshold of 1 turns ordinary noise into unnecessary restarts or unnecessary traffic removal.
Use a small-but-nonzero threshold (2 or 3 is typical) so an isolated blip doesn't trigger anything, while a genuinely sustained problem still gets caught quickly.
initialDelaySeconds on the liveness probe alone can be enough. Add one the moment startup time becomes unpredictable./health endpoint, you cannot configure distinct liveness and readiness probes correctly — you'd be forced to point both at the same answer, recreating the exact conflation problem lesson 268 warned about, just one layer further down the stack.
livenessProbe, readinessProbe, and (optionally) startupProbe endpoints on independently configured schedules — initialDelaySeconds, periodSeconds, failureThreshold.You've seen how Kubernetes turns the endpoints from lesson 268 into automatic, correctly-differentiated action. Let's confirm the distinction really landed.
1. A pod's readinessProbe has been failing for two full minutes, while its livenessProbe has been succeeding the entire time. What has Kubernetes done?
Correct: B
Why B is correct: A readiness probe failure never restarts anything — it only marks the pod not-ready and removes it from the Service's endpoint list, so no new traffic is routed to it. Since the liveness probe keeps succeeding, Kubernetes has no reason to touch the container itself at all.
Why A and C are incorrect: Both describe a restart or a rescheduling action, which is exclusively tied to liveness probe failures — never readiness.
Why D is incorrect: Readiness failures absolutely cause a real, immediate orchestrator action — removal from the Service's rotation — it's just not a restart.
Reinforcement: Liveness → restart. Readiness → traffic removal, no restart. The two consequences never merge or escalate into each other.
2. Why does putting a database connectivity check on the livenessProbe (instead of the readinessProbe) cause a real operational problem during a brief database outage?
Correct: B
Why B is correct: A liveness failure means "restart." Restarting a process whose only problem is an unreachable database accomplishes nothing — the new process will hit the exact same database outage — and just adds needless startup churn during an already-degraded period.
Why A is incorrect: They are deliberately not interchangeable — that's the entire point of configuring them against separate endpoints.
Why C is incorrect: The two probes are configured and evaluated independently; one misconfigured probe doesn't disable the other.
Why D is incorrect: A liveness failure triggers a restart, not deletion of the pod.
Reinforcement: Dependency checks belong on readiness, where the response — pull from traffic — actually addresses what's wrong.
3. A service takes anywhere from 5 to 60 seconds to start, depending on cache-warming time, and its liveness probe has periodSeconds: 10 and failureThreshold: 3 (a 30-second liveness window). What is the best fix for preventing premature restarts during slow startups?
Correct: C
Why C is correct: A startupProbe is exactly the mechanism designed for this: it delays the liveness (and readiness) schedule until the app has actually finished starting, without permanently weakening liveness's ability to react quickly once the app is in steady-state.
Why A is incorrect: Removing liveness entirely gives up the real, ongoing benefit of automatic recovery from a stuck process later on.
Why B is incorrect: A permanently high failureThreshold weakens liveness detection during normal operation too, not just startup — it solves the startup problem by breaking the steady-state benefit.
Why D is incorrect: The readinessProbe's own schedule doesn't affect when liveness checking begins — they're independent, and this doesn't address premature restarts at all.
Reinforcement: The startup probe exists precisely to separate "how patient should we be while starting" from "how quickly should we react once running."
4. Which statement correctly describes what a container orchestrator like Kubernetes observes about a running container without any health probes configured at all?
Correct: B
Why B is correct: Without probes, an orchestrator's only default signal is process liveness at the OS level — is the process still there. It has no way to know the process is deadlocked-but-running, or temporarily unable to reach a dependency, which is exactly the gap probes are built to close.
Why A is incorrect: Resource usage alone doesn't reveal application-level health — a deadlocked thread can sit at near-zero CPU indefinitely.
Why C is incorrect: Kubernetes never auto-discovers and calls arbitrary endpoints; probes must be explicitly configured, pointed at specific paths.
Why D is incorrect: Probes are optional; a container with none configured still runs, it just gets no automated health-driven restart or traffic-removal behavior.
Reinforcement: Probes exist because "the process hasn't exited" is a far weaker signal than "the process is actually healthy."
You now understand exactly what Kubernetes does with the health endpoints you already know how to build — and why liveness and readiness deserve genuinely different, deliberately mismatched remedies.
dotnetmadeeasy.com — Learn C# and .NET, the right way.