Back to Kubernetes: Core Concepts
Q8Question 8 of 16

Readiness, Liveness, and Startup Probes in Kubernetes

Key Concepts
  • A readiness probe controls traffic: when it fails the Pod is removed from Service endpoints but NOT restarted.
  • A liveness probe controls life: when it fails past the threshold the kubelet restarts the container.
  • A startup probe protects slow-starting apps by suppressing liveness and readiness checks until the app has booted.
  • Each probe can use httpGet, tcpSocket, or exec as its check mechanism.
  • Timing fields include initialDelaySeconds, periodSeconds, timeoutSeconds, failureThreshold, and successThreshold.
  • An aggressive liveness probe that depends on a slow downstream causes restart storms instead of recovery.

Definition

Readiness controls traffic (remove from Service), liveness controls life (restart the container), and startup buys boot time for slow apps.

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.

YAML
# 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.
YAML
# 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.

BASH
# 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.

BASH
# 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.

YAML
# 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
BASH
# 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) |

YAML
# 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.
YAML
# 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.

YAML
# 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
YAML
# 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.

BASH
# 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

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

---

Share
Use ← → arrow keys to navigate