Definition
Kubernetes probes are periodic health checks the kubelet runs against a container.
- Readiness probe – decides whether a Pod should receive traffic; when it fails the Pod is removed from Service endpoints but is NOT restarted.
- Liveness probe – decides whether a container is still healthy; when it fails past its threshold the kubelet kills and restarts the container.
- Startup probe – protects slow-starting apps by disabling liveness and readiness checks until the application finishes booting.
- Probe mechanisms – httpGet (HTTP endpoint), tcpSocket (TCP port check), or exec (run a command inside the container).
- Timing fields – initialDelaySeconds, periodSeconds, timeoutSeconds, failureThreshold, and successThreshold control when and how often probes run.
At the National Bank of India, the readiness probe is the OPEN/CLOSED sign on a teller counter. If the teller is on a tea break, the sign flips to CLOSED and customers are routed to other counters, but the teller is not fired. The liveness probe is the branch manager checking if the teller has fainted. If the teller does not respond at all, the manager replaces them. The startup probe is the grace period for a new teller still logging into the system in the morning. Nobody judges them until they say I am ready.
Understanding the Concepts
What are Kubernetes probes?
Kubernetes probes are periodic health checks that the kubelet runs against each container in a Pod. They answer a specific question about the container's state, and depending on which probe fails, Kubernetes takes a completely different action. Understanding which action each probe triggers is the core of the topic.
# A Pod with all three probes configured
apiVersion: v1
kind: Pod
metadata:
name: transfer-service
spec:
containers:
- name: transfer
image: nbi-registry.example.com/transfer-service:2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
failureThreshold: 3
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 5
failureThreshold: 30
What are the three probe mechanisms?
Each probe can use one of three check mechanisms.
- httpGet calls an HTTP endpoint and treats any 2xx or 3xx status as healthy. This is the most common choice for web services.
- tcpSocket checks whether a TCP port accepts a connection. It is useful for databases or services without an HTTP endpoint.
- exec runs a command inside the container and treats exit code 0 as healthy.
# httpGet probe - checks an HTTP health endpoint
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
# tcpSocket probe - checks if the database port is open
readinessProbe:
tcpSocket:
port: 5432
# exec probe - runs a command inside the container
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
What happens when a readiness probe fails?
When a readiness probe fails, the kubelet marks the Pod as NotReady. The EndpointSlice controller removes that Pod's IP from the Service, so traffic stops flowing to it immediately. The container keeps running. It is not restarted.
This is the correct behaviour for a banking service that is temporarily overloaded or waiting on a slow database connection pool. You do not want to kill it. You just want to stop sending it work until it recovers.
# Check Pod readiness - 0/1 means readiness is failing
kubectl get pods
# NAME READY STATUS RESTARTS
# transfer-service-6d4f9c7b8a-x9k 0/1 Running 0
# The Service has no endpoints for this Pod
kubectl get endpoints transfer-service
# NAME ENDPOINTS
# transfer-service 10.0.1.5:8080,10.0.1.6:8080 <- the failing Pod is missing
What happens when a liveness probe fails?
When a liveness probe fails past the failureThreshold, the kubelet kills the container and restarts it according to the Pod's restartPolicy. This is for unrecoverable states like a deadlocked thread pool or a hung process.
# Events show the liveness failure before the restart
kubectl describe pod transfer-service-6d4f9c7b8a-x9k
# Events:
# Warning Unhealthy Liveness probe failed: HTTP probe failed with statuscode: 503
# Normal Killing Container transfer failed liveness probe, will be restarted
kubectl get pods
# NAME READY STATUS RESTARTS
# transfer-service-6d4f9c7b8a-x9k 1/1 Running 1 <- restart count increased
What does the startup probe solve?
A Spring Boot banking app with Flyway migrations might take 90 seconds to boot. If the liveness probe has a 10-second initial delay, it kills the app before it ever starts. The startup probe runs first. While it is still failing, liveness and readiness are suppressed. Once the startup probe succeeds, the kubelet switches to the normal probes.
# Give the app up to 150 seconds to boot (30 failures x 5 seconds)
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 5
failureThreshold: 30 # 30 x 5 = 150 seconds budget
# Without startup probe - liveness kills the app during boot
# Events:
# Warning Unhealthy Liveness probe failed (app still booting)
# Normal Killing Container restarted -> CrashLoopBackOff
# With startup probe - boot completes, then normal probes take over
# Events:
# Normal Started Container started
# Normal Startup probe succeeded after 80s
How is readiness different from liveness?
This is the most important distinction. Each probe triggers a completely different action.
| Aspect | Readiness Probe | Liveness Probe |
|---|---|---|
| Question it answers | Should this Pod receive traffic? | Is this container alive? |
| Action on failure | Remove from Service endpoints | Kill and restart the container |
| Container keeps running? | Yes | No (restarted) |
| Good for | Temporary overload, slow downstream | Deadlock, hung process |
| Should check downstream? | Yes (DB pool, Redis) | No (self-check only) |
# WRONG - liveness depends on the database
# If the database is slow, liveness fails, Pods restart endlessly
livenessProbe:
httpGet:
path: /actuator/health # includes DB, Redis, everything
port: 8080
# CORRECT - liveness is a trivial self-check
livenessProbe:
httpGet:
path: /actuator/health/liveness # only checks the process itself
port: 8080
# Readiness includes downstream health
readinessProbe:
httpGet:
path: /actuator/health/readiness # includes DB pool, Redis
port: 8080
What are the timing fields?
Every probe has five timing parameters that control its behaviour.
- initialDelaySeconds is how long to wait before the first check. Default is 0.
- periodSeconds is how often to repeat the check. Default is 10.
- timeoutSeconds is how long to wait for a response before counting it as a failure. Default is 1.
- failureThreshold is how many consecutive failures before acting. Default is 3.
- successThreshold is how many consecutive successes before considering healthy. Default is 1 and must be 1 for liveness.
# Timing example for a banking transfer service
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
What are common mistakes?
The most dangerous mistake is making the liveness probe depend on a slow downstream. If the transfer service's liveness checks the database and the database is slow, every Pod gets restarted, which makes the overload worse instead of better.
# WRONG - liveness checks the database
# Database is slow -> liveness fails -> all Pods restart -> restart storm
livenessProbe:
httpGet:
path: /health # returns 503 when DB is slow
port: 8080
failureThreshold: 2
periodSeconds: 5
# CORRECT - liveness is trivial, readiness checks the database
livenessProbe:
httpGet:
path: /health/live # always 200 unless the process is truly dead
port: 8080
failureThreshold: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready # checks DB pool, Redis, downstream
port: 8080
failureThreshold: 3
periodSeconds: 5
What should a senior engineer know?
A senior engineer keeps readiness and liveness semantically separate. Readiness reflects downstream health because a saturated connection pool is a temporary condition. Liveness is a trivial self-check that answers whether the process can serve anything at all.
The startup probe's budget is failureThreshold * periodSeconds. Setting failureThreshold: 30 with periodSeconds: 5 gives 150 seconds, which covers most Spring Boot apps with Flyway migrations and cache warm-up without loosening the steady-state liveness threshold.
For a banking service, the readiness probe should include the database connection pool status, the Redis connection, and any circuit breaker state, so that a Pod under pressure is pulled from traffic gracefully.
# Verify probe configuration on a running Pod
kubectl get pod transfer-service-6d4f9c7b8a-x9k -o jsonpath='{.spec.containers[0].livenessProbe}' | jq
# Check events for probe failures
kubectl describe pod transfer-service-6d4f9c7b8a-x9k | grep -A5 Events
Visual Overview
Probe Decision Flow
====================
Container starts
│
▼
┌─────────────────┐
│ Startup Probe │──── failing ──→ liveness & readiness suppressed
│ (runs first) │
└────────┬────────┘
│ succeeds once
▼
┌─────────────────────────────────────────┐
│ Normal operation: both probes active │
│ │
│ Readiness Probe Liveness Probe │
│ │ │ │
│ │ fails │ fails │
│ ▼ ▼ │
│ Remove from Service Kill & restart │
│ endpoints (no traffic) the container │
│ │ │ │
│ │ succeeds again │ new start │
│ ▼ ▼ │
│ Re-add to endpoints Back to probes │
└─────────────────────────────────────────┘
Key Takeaway
Kubernetes gives you three independent health probes, and each one triggers a completely different action. A readiness probe answers whether a Pod should receive traffic right now, and when it fails the Pod is removed from the Service's endpoint list but is never restarted. A liveness probe answers whether a container is fundamentally alive, and when it fails past its threshold the kubelet kills and restarts the container. A startup probe answers whether a slow application has finished booting, and while it is still failing the kubelet suppresses the other two probes so a long boot never causes a restart loop.
The most important senior insight is to keep readiness and liveness semantically different. Readiness should reflect downstream health such as the database connection pool, because a saturated pool is a temporary condition where you want to stop sending traffic but keep the process alive. Liveness should be a trivial self-check, because wiring a slow database into the liveness probe triggers endless restarts that make the outage worse.
In the banking project at the National Bank of India, the Transfer Service used exactly this split. The readiness group included the database and Redis, so when HikariCP was saturated during a burst of transfers the Pod was pulled out of the Service and traffic flowed to healthy Pods. The liveness probe was a bare liveness-state endpoint. The startup probe had a budget of 150 seconds for Flyway migrations and cache warm-up.
Remember one sentence: readiness controls traffic, liveness controls life, and startup buys boot time.
---