Warrior, this chapter is critical for production Kubernetes.
Kubernetes can restart a crashed container, but there's a deeper problem:
How does Kubernetes know whether your application is actually healthy?
A process can be running but still be broken.
For example:
Node.js process
│
▼
Running ✅
│
└── Database connection broken ❌Kubernetes needs a way to distinguish:
"Process exists"from:
"Application is healthy and ready to serve traffic"That's what probes do.
Kubernetes provides three major types:
Startup Probe
↓
Is my application finished starting?
Readiness Probe
↓
Can my application receive traffic?
Liveness Probe
↓
Is my application still healthy/alive?The easiest way to remember:
Startup = started? Readiness = ready? Liveness = alive?
Imagine your Node.js API takes:
45 secondsto start because it loads:
Without proper probes, Kubernetes might assume:
Container started
↓
Send traffic immediatelyBut the application isn't ready.
Users get:
502
503
Connection errorsA readiness probe solves this.
Kubernetes
│
▼
Pod
│
┌─────┴─────┐
│ Container │
└─────┬─────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
Startup Readiness Liveness
│ │ │
Started? Ready? Alive?A liveness probe answers:
"Is this container still functioning?"
Suppose your application gets stuck:
Node.js
↓
Process still exists
↓
But event loop is stuckThe process may technically still be running.
Kubernetes can use the liveness probe to detect the unhealthy state and restart the container.
Conceptually:
Liveness
│
├── Healthy → Keep running
│
└── Failed repeatedly → Restart containerReadiness answers:
"Should this Pod receive traffic right now?"
This is different from liveness.
Suppose:
Pod is alive
↓
but database isn't connectedYou may not want the Service to send user requests to it.
Readiness can report:
Not ReadyThen the Pod is removed from the Service's ready endpoints.
Important:
A failed readiness probe normally does NOT restart the container.
It primarily affects traffic routing.
This distinction is extremely important.
"Should Kubernetes restart this container?""Should Kubernetes send traffic to this Pod?"Example:
Pod
│
├── Liveness ❌
│ ↓
│ Restart
│
└── Readiness ❌
↓
Stop receiving trafficStartup probe answers:
"Has my application finished starting?"
This is especially useful for slow-starting applications.
For example:
Container starts
│
├── 5 sec
├── 10 sec
├── 20 sec
├── 30 sec
└── 45 sec → Application readyDuring startup:
Startup probe = failingOnce it succeeds:
Startup probe = successfulthen liveness/readiness checks can take over.
Imagine:
livenessProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 5Your application needs 30 seconds to start.
Kubernetes checks:
5 sec → fail
10 sec → fail
15 sec → fail
20 sec → failDepending on the configured failure threshold, Kubernetes might restart the container before it finishes starting.
Result:
Start
↓
Probe fails
↓
Restart
↓
Start
↓
Probe fails
↓
RestartPotentially:
CrashLoopBackOffA startup probe can prevent this.
A probe can check the application in several ways.
GET /healthCan I connect to port 3000?Run a command inside containerFor applications exposing suitable gRPC health checking.
We'll focus first on HTTP, TCP, and command probes.
For a Node.js API, this is very common.
Suppose your application exposes:
GET /healthwhich returns:
200 OKKubernetes:
livenessProbe:
httpGet:
path: /health
port: 3000Conceptually:
Kubernetes
│
│ GET /health
▼
Node.js
│
└── 200 OKFor Express:
app.get("/health", (req, res) => {
res.status(200).json({
status: "ok"
});
});Then:
GET /healthreturns:
{
"status": "ok"
}Kubernetes sees:
HTTP 200
↓
HealthyreadinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
successThreshold: 1Let's understand each field.
initialDelaySeconds#initialDelaySeconds: 5Means:
Wait approximately 5 seconds before starting the probe checks.
Useful when an application needs some time to initialize.
But with a startup probe, you often don't need to rely heavily on a large initial delay for slow applications.
periodSeconds#periodSeconds: 10Means:
Run the probe approximately every 10 seconds.
Conceptually:
0 sec
│
10 sec → check
│
20 sec → check
│
30 sec → check
│
40 sec → checktimeoutSeconds#timeoutSeconds: 2Means:
The probe must respond within about 2 seconds.
If it takes longer:
Probe = failurefailureThreshold#failureThreshold: 3Means:
How many consecutive failures are required before Kubernetes considers the probe failed according to the probe's semantics.
For readiness:
Failure
Failure
Failure
↓
Pod becomes NotReadyFor liveness:
Failure
Failure
Failure
↓
Container restartsuccessThreshold#successThreshold: 1Specifies how many consecutive successful checks are needed to transition a failed probe back to success.
For many HTTP readiness/liveness configurations, the practical value is:
1livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3Meaning approximately:
Start application
↓
Wait 10 sec
↓
Check /health every 10 sec
↓
If 3 consecutive checks fail
↓
Kubernetes may restart containerreadinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3If readiness fails:
Pod
↓
NotReady
↓
Service stops sending normal trafficThe container can continue running.
startupProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 5
failureThreshold: 12This effectively gives the application multiple probe attempts during startup.
Roughly:
5 sec × 12 attemptsbefore startup is considered failed, subject to probe behavior.
Once startup succeeds:
Startup ✅
↓
Liveness + Readiness take overHere's a Node.js Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-api
spec:
replicas: 3
selector:
matchLabels:
app: node-api
template:
metadata:
labels:
app: node-api
spec:
containers:
- name: node-api
image: my-node-api:1.0
ports:
- containerPort: 3000
startupProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 5
failureThreshold: 12
readinessProbe:
httpGet:
path: /ready
port: 3000
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: 3000
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"This is much closer to a production-style workload than a simple Pod definition.
/health vs /ready#It's often useful to have different endpoints.
/health#Could answer:
"Is the application process fundamentally healthy?"Example:
{
"status": "ok"
}/ready#Could answer:
"Can this instance handle requests right now?"For example, the readiness endpoint might verify important dependencies:
Application
│
├── Database connection
├── Required configuration
└── Critical dependencyBut be careful about making health checks too expensive.
This is an architectural decision.
You might have:
/ready
↓
Database checkIf the database goes down:
Readiness ❌
↓
Pod removed from Service endpointsThis can be useful in some architectures.
But blindly checking every dependency in every probe can create problems.
For example:
Database temporarily slow
↓
Every Pod becomes NotReady
↓
No traffic
↓
Recovery becomes harderSo health endpoints should be designed deliberately.
This is a very important production principle.
Don't make your liveness endpoint perform:
10 database queries
+
Redis call
+
External API call
+
LLM requestThat's dangerous.
Imagine:
External API down
↓
Liveness fails
↓
Kubernetes restarts all Pods
↓
Pods restart
↓
External API still down
↓
Restart againYou've turned a dependency outage into a restart storm.
A liveness check should generally answer whether the application itself is functioning, not whether every external dependency in the world is healthy.
Readiness can be more dependency-aware.
For example:
Database unavailable
↓
Pod alive
↓
Pod not ready
↓
Don't send new trafficThis can be preferable to repeatedly restarting healthy application processes.
A useful mental model:
Container starts
│
▼
Startup Probe
│
┌──────┴──────┐
│ │
Fail Success
│ │
Restart ▼
Readiness
│
┌──────┴──────┐
│ │
Ready NotReady
│ │
▼ ▼
Service No traffic
traffic
│
▼
Liveness
│
┌─────┴─────┐
│ │
Healthy Failed
│ │
▼ ▼
Continue RestartThis is one of the most useful diagrams in this chapter.
Example:
httpGet:
path: /health
port: 3000
scheme: HTTPYou can specify:
HTTP
HTTPSdepending on how the application is configured.
You can also specify HTTP headers when needed.
Sometimes your application doesn't expose a health HTTP endpoint.
You can use:
livenessProbe:
tcpSocket:
port: 3000
periodSeconds: 10Kubernetes attempts to establish a TCP connection.
If the port isn't accepting connections:
Probe failsTCP probes tell you that something is accepting a connection, but they don't necessarily prove the application is functioning correctly.
You can execute a command inside the container:
livenessProbe:
exec:
command:
- cat
- /tmp/healthyIf the command exits successfully:
Probe succeedsOtherwise:
Probe failsThis can be useful for certain applications but should be used thoughtfully.
This is one of the most important interactions.
Suppose:
3 PodsInitially:
Pod A → Ready
Pod B → Ready
Pod C → ReadyService:
Service
├── A
├── B
└── CNow Pod B readiness fails:
Pod B → NotReadyService effectively has:
Service
├── A
└── CPod B is still running, but normal Service traffic isn't directed to it while it's not ready.
Now you can understand why readiness probes matter during a rolling update.
Suppose:
Version 1
Pod A
Pod B
Pod CDeployment starts:
Version 2
Pod DBut Pod D is still starting:
Pod D
NotReadyService continues sending traffic to:
A
B
COnce D becomes ready:
A
B
C
DThen Kubernetes can gradually remove old Pods.
This is how readiness helps make rolling updates safer.
Kubernetes may consider the container ready according to its default readiness behavior once the container is running.
That means a newly started application could potentially receive traffic before it's truly prepared to handle requests.
For production services, a properly designed readiness check is often valuable.
Don't confuse:
RestartPolicywith:
ProbesRestartPolicy defines restart behavior when a container terminates.
Probes determine health conditions and can trigger actions such as:
Liveness failure → container restart
Readiness failure → remove from ready endpoints
Startup failure → container restartSuppose:
Pod
↓
CrashLoopBackOffFirst:
kubectl describe pod <pod-name>Look at:
EventsYou may see:
Liveness probe failedThen:
kubectl logs <pod-name>And for the previous container:
kubectl logs <pod-name> --previousAlso inspect:
kubectl get pod <pod-name>Application:
3000Probe:
port: 8080Result:
Probe failsApplication has:
/healthProbe checks:
/healthyResult:
404Probe fails.
Example:
Application startup = 30 sec
Liveness starts immediately
Probe every 2 secPotentially:
Start
↓
Fail
↓
Restart
↓
Start
↓
FailUse startup probes or appropriate initial startup configuration.
Don't make liveness call:
Database
Redis
External APIs
LLMunless there's a deliberate reason.
Remember Chapter 13?
We learned:
CPU
Memory
Requests
LimitsNext you'll learn:
HPAHealth probes complement autoscaling.
For example:
HPA
↓
More Pods
Readiness
↓
Only healthy Pods receive trafficTogether:
Traffic increases
↓
HPA creates Pods
↓
New Pods start
↓
Readiness = NotReady
↓
Application initializes
↓
Readiness = Ready
↓
Service sends trafficThis is a powerful production pattern.
It determines whether a container is still functioning and can cause the container to be restarted when the probe repeatedly fails.
It determines whether a Pod should receive traffic. A failed readiness probe normally removes the Pod from ready Service endpoints without restarting the container.
It determines whether a slow-starting application has completed initialization and protects it from premature liveness/readiness failures.
Liveness
→ Restart?
Readiness
→ Traffic?For applications that take significant time to initialize.
The Pod is marked not ready and normally stops receiving traffic through Services.
Kubernetes can restart the container.
A startup probe is often appropriate.
Normally, no.
Usually keep it lightweight and focused on whether the application itself is functioning; blindly making it depend on external systems can cause unnecessary restarts.
STARTUP
↓
"Has the application started?"
READINESS
↓
"Can it receive traffic?"
LIVENESS
↓
"Is it still functioning?"Probe types:
HTTP
TCP
Command
gRPCImportant behavior:
Startup failure
↓
Restart
Readiness failure
↓
No normal Service traffic
Liveness failure
↓
Restart containerYou can now visualize a much more realistic system:
USER
│
▼
INTERNET
│
▼
INGRESS
│
▼
SERVICE
│
┌──────┼──────┐
▼ ▼ ▼
Pod A Pod B Pod C
│ │ │
Ready Ready NotReady
│ │ X
│ │
└──────┴───────┐
│
Application
│
┌─────────┴─────────┐
│ │
Liveness Readiness
│ │
Restart? Traffic?And for startup:
Container starts
│
▼
Startup Probe
│
▼
Application initialized
│
├───────────────┐
▼ ▼
Readiness Liveness
│ │
Traffic? Alive?This is where Kubernetes becomes truly elastic.
You'll learn:
HPA
CPU-based scaling
Memory-based scaling
minReplicas
maxReplicas
target utilization
metrics-server
kubectl autoscale
autoscaling/v2
Scaling behavior
Scale up / scale down
HPA + Deployment + ServiceAnd we'll build this:
Traffic
│
▼
Service
│
┌──────────┼──────────┐
▼ ▼ ▼
Pod 1 Pod 2 Pod 3
▲
│
HPA
│
CPU/Memory Metrics
│
▼
Increase/Decrease
Pod replicasThe key idea:
Deployment tells Kubernetes how many Pods you want; HPA dynamically changes that number based on workload metrics.
Mark it as complete to track your mastery.