Definition
- Liveness probe – answers "is this process still healthy, or is it stuck and should be restarted?" If it fails repeatedly, Kubernetes kills and recreates the container.
- Readiness probe – answers "is this process ready to receive traffic right now?" If it fails, Kubernetes removes the Pod from the Service endpoints but does not restart it.
- Startup probe – gates the liveness probe during slow application boot, preventing premature restarts before the app finishes starting.
- Actuator health groups – Spring Boot's mechanism to expose separate /liveness and /readiness endpoints wired to the Kubernetes probes.
Picture a teller at National Bank of India. The liveness check is the manager asking "are you conscious and at your desk?" If the teller has fainted, the manager sends them home and calls a replacement. The readiness check is "are you ready to serve the next customer?" Maybe the teller is awake but still counting the opening cash drawer, so the manager temporarily stops sending customers to that window without firing the teller.
Understanding the Concepts
What is a liveness probe?
The liveness probe guards against a deadlocked or wedged process that is technically alive but will never recover on its own. If the liveness probe fails the configured number of times, Kubernetes concludes the container is unrecoverable and restarts it.
# Liveness probe in a Kubernetes Deployment
spec:
containers:
- name: transfer-service
image: registry.nbi.com/transfer-service:2.5.0
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5
What is a readiness probe?
The readiness probe guards against sending traffic to a Pod that is not yet able to handle it. When readiness fails, Kubernetes takes the Pod out of the Service endpoints so no new traffic arrives, but it leaves the Pod running so it can recover and rejoin.
# Readiness probe in the same Deployment
spec:
containers:
- name: transfer-service
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
timeoutSeconds: 3
How is liveness different from readiness?
The two probes exist because "the process is running" and "the process can serve requests" are different questions, and conflating them causes outages.
| Aspect | Liveness probe | Readiness probe |
|---|---|---|
| Question | Is the process healthy? | Can it serve traffic now? |
| Failure action | Kill and restart container | Remove from Service endpoints |
| May check dependencies? | No, never | Yes |
| Restarts the container? | Yes | No |
| During startup | Gated by startup probe | Runs immediately |
package com.bank.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
// WRONG: checking DB in liveness -- a DB blip restarts the entire fleet
@Component("livenessDb")
public class WrongLivenessCheck implements HealthIndicator {
@Override
public Health health() {
// If the database has a 2-second hiccup, every pod restarts!
boolean dbUp = checkDatabase();
return dbUp ? Health.up().build() : Health.down().build();
}
private boolean checkDatabase() { return true; }
}
package com.bank.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
// CORRECT: checking DB in readiness -- traffic pauses, pod stays alive
@Component("readinessDb")
public class CorrectReadinessCheck implements HealthIndicator {
@Override
public Health health() {
// If DB is down, stop traffic but do not restart
boolean dbUp = checkDatabase();
return dbUp ? Health.up().build()
: Health.down().withDetail("reason", "DB unavailable").build();
}
private boolean checkDatabase() { return true; }
}
How do you wire Spring Boot Actuator to Kubernetes probes?
Spring Boot Actuator supports this directly through health groups and application availability. You enable the probes in application.yml and Spring Boot publishes the liveness and readiness endpoints.
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true # Enables /liveness and /readiness groups
health:
readinessstate:
enabled: true
livenessstate:
enabled: true
server:
shutdown: graceful # Drain in-flight requests on shutdown
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
# Test the endpoints locally
curl http://localhost:8080/actuator/health/liveness
curl http://localhost:8080/actuator/health/readiness
{"status":"UP","groups":["liveness","readiness"]}
{"status":"UP","components":{"readinessState":{"status":"UP"},"db":{"status":"UP"}}}
What is a startup probe and when do you need it?
A startup probe gates the liveness probe during slow application boot. For a JVM application that can take time to warm up, using a startup probe avoids premature restarts.
spec:
containers:
- name: transfer-service
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
failureThreshold: 30 # 30 x 10s = 5 minutes to start
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
failureThreshold: 3
What are common mistakes?
The most dangerous mistake is checking the database in the liveness probe. If the database has a brief hiccup, every Pod fails liveness simultaneously and Kubernetes restarts the entire fleet, turning a minor dependency blip into a full outage.
# WRONG: liveness checks the database
livenessProbe:
httpGet:
path: /actuator/health # Includes ALL indicators including DB!
port: 8080
# CORRECT: liveness checks only the liveness group
livenessProbe:
httpGet:
path: /actuator/health/liveness # Only internal liveness state
port: 8080
How do probes interact with rolling updates?
During a rolling update, Kubernetes only shifts traffic to a new Pod once its readiness probe passes, and it only removes an old Pod after draining. If readiness honestly reflects warm-up, a rolling update never routes a customer to a Pod that cannot serve them.
package com.bank.health;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WarmupReadiness {
private final ApplicationEventPublisher publisher;
public WarmupReadiness(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@PostConstruct
public void warmUp() {
// Fill connection pool, prime caches, load currency rates
loadCurrencyRates();
fillConnectionPool();
// Signal ready only after warm-up completes
AvailabilityChangeEvent.publish(
publisher, this, ReadinessState.ACCEPTING_TRAFFIC);
}
private void loadCurrencyRates() { /* ... */ }
private void fillConnectionPool() { /* ... */ }
}
How do probes interact with graceful shutdown?
Spring Boot automatically manages readiness during graceful shutdown: it flips readiness to REFUSING_TRAFFIC first so the load balancer drains the Pod before the process stops.
# application.yml
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
Shutdown sequence:
1. SIGTERM received
2. preStop hook runs (sleep 5 for LB propagation)
3. Readiness flips to REFUSING_TRAFFIC
4. Service removes Pod from endpoints
5. In-flight requests drain (up to 30s)
6. Process exits
What should a senior engineer know?
A senior engineer knows that accurate probes are not a nicety; they are the mechanism that makes zero-downtime deployment real. The readiness probe must honestly reflect warm-up: connection pools filled, caches primed, dependencies reachable. The liveness probe must never check anything that could cause a cascading restart of the entire fleet.
Visual Overview
Kubernetes Control Plane
│
│ periodically checks
▼
┌────────────────────────────────────────────┐
│ Pod │
│ │
│ Startup Probe ──→ gates liveness │
│ │ │
│ ▼ │
│ Liveness Probe ──→ /actuator/health/liveness│
│ │ (internal only) │
│ │ fail → RESTART container │
│ │ │
│ Readiness Probe ─→ /actuator/health/readiness│
│ │ (may check deps) │
│ │ fail → REMOVE from Service endpoints│
│ │ pass → ADD back to Service endpoints│
└────────────────────────────────────────────┘
Rolling Update:
┌──────┐ readiness=UP ┌─────────┐ traffic
│NEW │ ───────────────→│ Service │ ──────→ users
│POD │ └─────────┘
└──────┘
┌──────┐ readiness=DOWN ┌─────────┐ drain
│OLD │ ───────────────→│ Service │ ──────→ finish
│POD │ └─────────┘ in-flight
└──────┘
Key Takeaway
A liveness probe and a readiness probe answer two different questions, and the whole art is keeping them separate. Liveness asks "is this process stuck and should be restarted?" Readiness asks "can this Pod serve traffic right now?" The most important rule is that the liveness probe must check only internal state and never external dependencies: if it checked the database and the database hiccupped, every Pod would restart simultaneously, turning a blip into a full outage.
In Spring Boot, Actuator's health groups and probes configuration make wiring straightforward. The liveness endpoint reports only internal state, and the readiness endpoint includes dependency checks. A startup probe gates the liveness probe during slow JVM boot so nothing is killed prematurely. Graceful shutdown flips readiness to refusing-traffic first, draining in-flight requests before the process exits.
At National Bank of India, this separation made deploys invisible to customers. New Transfer Service Pods reported not-ready until their connection pool was filled and their currency-rate cache was primed, so Kubernetes only routed traffic once they could truly serve. When the database had a blip, readiness dropped but liveness stayed up, and the Pod rejoined the instant the database recovered.
Remember one sentence: liveness says restart me, readiness says pause traffic to me, and checking a dependency in liveness is the fastest way to turn a small blip into a fleet-wide outage.
---