← Open in the full interactive course (progress tracking, search & more)

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.

What Is It?

The Simple Explanation

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.

The Technical Definition

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.

livenessProbe

readinessProbe

This 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.

Why Does It Exist?

The Problem — an Orchestrator Runs Containers It Cannot See Inside

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.

The Solution — Ask Two Different Questions, Take Two Different Actions

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.

Big Picture

WHO ASKS, WHAT HAPPENS
Your ASP.NET Core app
Exposes /health/live and /health/ready (lesson 268) — answers honestly, knows nothing about Kubernetes
Kubernetes kubelet
On each node, polls those endpoints on a schedule you configure — this lesson
Failed livenessProbe
kubelet kills and restarts the container
Failed readinessProbe
Service stops sending it traffic — container keeps running

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.

How It Works

Configuring an httpGet Probe

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
FieldMeaning
httpGet.path / portWhich endpoint to call and on which port — points at the exact routes you mapped with MapHealthChecks() in lesson 268.
initialDelaySecondsHow long the kubelet waits after the container starts before it sends the first probe at all.
periodSecondsHow often the probe repeats, once it's started — every 10 seconds, every 5 seconds, and so on.
failureThresholdHow 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.

Step by Step — What Kubernetes Actually Does

FROM CONTAINER START TO STEADY-STATE PROBING
1. CONTAINER STARTS
2. KUBELET POLLS ON A SCHEDULE
3. RESPONSE IS EVALUATED
4. FAILURETHRESHOLD IS REACHED — DIFFERENT ACTION PER PROBE KIND

Simple Example

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 automatically

Meaning: 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.

Real-World Example — a Slow-Starting Notification Service

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: 3

When 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.

Analogy

A Restaurant Kitchen, Checked by a Manager

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.

Under the Hood

WHO IS ACTUALLY MAKING THESE CALLS
1. THE kubelet — NOT THE SERVICE, NOT A LOAD BALANCER
2. LIVENESS FAILURE → THE kubelet RESTARTS THE CONTAINER
3. READINESS FAILURE → THE ENDPOINTS CONTROLLER UPDATES THE SERVICE
4. RECOVERY IS SYMMETRIC FOR READINESS

Common Confusion

1. "Both probes eventually restart the container" — no, only one of them ever does

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.

2. "The startup probe is just a longer initialDelaySeconds" — it's stricter than that sounds

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.

3. "Probes and health checks are the same thing" — they're two halves of one system

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.

Common Mistakes

Mistake 1 — Pointing the liveness probe at the readiness endpoint

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.

Mistake 2 — No startup probe on a slow-starting app, with a tight liveness schedule

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.

Mistake 3 — failureThreshold of 1

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.

When Should I Use It?

Tying it together: Everything in this lesson only works because lesson 268 already built two separate, honest endpoints. If your app only exposes a single combined /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.

Mental Model

livenessProbe fails → "this process is stuck" → restart the container
readinessProbe fails → "this instance can't serve well right now" → pull it from traffic, leave it running
startupProbe → "give this container time to finish starting before judging it at all"

Remember: the app answers honestly (lesson 268); Kubernetes decides what the answer means (this lesson). Restart fixes a broken process. Traffic removal fixes a busy or temporarily-degraded one. Never confuse which fix belongs to which problem.

Key Takeaway


Check Your Understanding

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?

Show answer

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?

Show answer

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?

Show answer

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?

Show answer

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.